gamutable/src/components/gamuTable.vue
2023-01-12 14:36:08 +01:00

1022 lines
23 KiB
Vue

<template>
<div class="gamutable">
<div class="gamutable--surTable">
<select id="parPage" v-model="parPageSelect">
<option v-for="v in tparpage" :key="v">{{ v }}</option>
</select>
<input
class="gamutable--rechercher"
type="text"
v-model="search"
placeholder="Rechercher"
/>
<button
class="btn gamutable--resetOrderBy"
type="button"
@click.stop="resetTri()"
title="Réinitialiser les tris des colonnes"
>
<i class="fa fas fa-eraser rouge"></i>
<i class="fa fa-filter fas"></i>
</button>
<button
class="btn var_gamutable"
type="button"
@click.stop="chargerJson()"
title="Forcer le rechargement"
>
<i class="fa fa-refresh fas fa-sync"></i>
</button>
<button
class="btn gamutable--vueTable"
title="Switcher en Vue tableau"
@click.stop="changerVue('tableau')"
v-if="vuebloc"
v-show="quelleVue === 'bloc'"
>
<i class="fas fa fa-table"></i>
</button>
<button
class="btn gamutable--vueBloc"
title="Switcher en Vue Bloc"
@click.stop="changerVue('bloc')"
v-if="vuebloc"
v-show="quelleVue === 'tableau'"
>
<i class="fas fa fa-th-large"></i>
</button>
<button
class="btn gamutable--exportCSV"
type="button"
@click.stop="exportCSV()"
v-show="namecsv"
title="Exporter le tableau affiché en csv"
>
<i
class="fa fa-file-excel-o fas fa-file-csv"
aria-hidden="true"
></i>
</button>
<button
class="btn"
type="button"
@click.stop="genererPDF()"
v-show="fichierpdf"
title="Générer le PDF du tableau affiché"
>
<i
class="fas fa-file-pdf rouge"
aria-hidden="true"
alt="pdf"
></i>
</button>
<button
class="btn gamutable--exportCSV"
type="button"
@click.stop="exportCSV('table')"
v-show="namecsv"
title="Exporter le tableau complet en csv"
>
<i
class="fas fas fa-file-excel"
aria-hidden="true"
alt="csv"
></i>
</button>
<button
class="btn"
type="button"
@click.stop="genererPDF('table')"
v-show="fichierpdf"
title="Générer le PDF du tableau complet"
>
<i
class="far fa-file-pdf rouge"
aria-hidden="true"
alt="pdf"
></i>
</button>
<span v-show="chargement" class="rouge">
<i
class="
fa fa-refresh fa-spin fa-fw
rouge
fas
fa-sync fa-spin
"
></i>
<span class="texteMajBDD">
Mise à jour de la base de donnée
</span>
</span>
<span
v-show="!chargement"
class="btn verte"
style="cursor: auto"
title="Base de donnée synchronisée !"
>
<i class="fa fas fa-database"></i>
</span>
<span class="gamutable-nbrMax" :data-nbrmax="table.length"
>{{ tableau.length }} / {{ table.length }} éléments</span
>
<span class="includespip" v-html="includespip"> </span>
</div>
<div class="vueBlocs" v-if="quelleVue === 'bloc'">
<div
class="vueBlocs-unbloc"
v-for="ligne in tableau"
:key="ligne.id"
v-html="replaceBloc(ligne.html)"
></div>
</div>
<table class="table table--zebra" v-if="quelleVue === 'tableau'">
<thead>
<tr>
<th
v-for="(label, head, i) in header"
:key="'head_' + i"
:class="[head, classes[head]]"
>
<span class="gt_labels">
<span v-html="label"></span>
<span class="iconeTri">
<i
class="fa fa-sort-asc fa-sort-up"
:class="ordreActif(head, 'asc')"
aria-hidden="true"
@click.stop="tri(head, 'asc')"
></i>
<i
class="fa fa-sort-desc fa-sort-down"
:class="ordreActif(head, 'desc')"
aria-hidden="true"
@click.stop="tri(head, 'desc')"
></i>
</span>
</span>
</th>
</tr>
<tr v-if="filtreCol.length" class="filtreColonne">
<th
v-for="(label, head, i) in header"
:key="'filtreCol_' + i"
>
<div
v-if="checkbox[head] !== undefined"
:id="'filtreCol_' + head"
:class="classes[head]"
class="flex justify-between"
>
<input
class="ml-2"
type="checkbox"
@click.stop="validerCheckboxCol(head)"
/>
<button
@click.stop="
checkboxValider(head, checkbox[head])
"
>
<i class="fas fa-share-square"></i>
</button>
</div>
<div
v-if="filtreCol.indexOf(head) !== -1"
:id="'filtreCol_' + head"
:class="classes[head]"
>
<vue-select
v-if="filtreColType[head] === 'select'"
v-model="filtreColSelected[head]"
:options="
filtreColVal[head].sort(ordonnerSelect)
"
hide-selected
multiple
taggable
close-on-select
clear-on-close
searchable
@selected="selectValCol"
@search:focus="endLoadingVueSelect"
@removed="endLoadingVueSelect"
>
<template #tag="{ option, remove }">
<div class="tag--un">
{{ option }}
<span
class="tag--remove pointer"
title="Cliquer pour supprimer "
@click.stop="remove"
>x</span
>
</div>
</template>
</vue-select>
<input
v-if="filtreColType[head] === 'input'"
class="gamutable__input--filtrer"
v-model="filtreColSelected[head]"
type="text"
placeholder="Rechercher"
@keydown="endLoadingVueSelect"
/>
<button
v-if="
filtreColType[head] === 'input' &&
filtreColSelected[head].length !== 0
"
@click.stop="deleteInputSearch(head)"
title="Vider ce champ"
class="gamutable__input--filtrer"
>
X
</button>
</div>
</th>
</tr>
</thead>
<tbody>
<tr
v-for="l in tableau"
:key="l.html.id"
:class="selectTr.indexOf(l.html.id) !== -1 ? 'select' : ''"
>
<td
v-for="(td, name, i) in l.html"
:key="'td_' + i"
:class="[
afficher_crayons(name, l),
name,
classes[name],
l.classes !== undefined ? l.classes[name] : '',
]"
@click="selectLigne(l.html.id, name)"
>
<div v-if="checkbox[name] !== undefined">
<div
v-if="td.split('-')[0] === 'dataid'"
class="text-center"
>
<label>
<input
type="checkbox"
v-model="Tcheckbox[name]"
:value="td.split('-')[1]"
/>
</label>
</div>
<div v-else v-html="td"></div>
</div>
<div v-else v-html="td"></div>
</td>
</tr>
</tbody>
</table>
<div class="gamutable--sousTable">
<div class="gamutable-nbrMax">
{{ tableau.length }} / {{ table.length }} éléments
</div>
<div class="gamutable--pagination">
<div class="page-item">
<button
type="button"
class="page-link"
v-if="page != 1"
@click="page = 1"
>
Premier
</button>
<button
type="button"
class="page-link"
v-if="page != 1"
@click="page--"
>
Précédent
</button>
</div>
<div class="page-item">
<button
type="button"
class="page-link"
:class="{ on: pageNumber === page }"
v-for="pageNumber in pages.slice(
page - 4 < 0 ? 0 : page - 4,
page + 3
)"
@click="page = pageNumber"
>
{{ pageNumber }}
</button>
</div>
<div class="page-item">
<button
type="button"
@click="page++"
v-if="page < pages.length"
class="page-link"
>
Suivant
</button>
<button
type="button"
@click="page = pages.length"
v-if="page < pages.length"
class="page-link"
>
Dernier
</button>
</div>
</div>
</div>
</div>
`,
</template>
<script setup>
import { watch, ref, onMounted, computed, nextTick, inject } from "vue";
import VueSelect from "vue-next-select";
import {
orderBy,
ordonnerSelect,
navigate,
recupJson,
trouver_index,
} from "./helpers";
const $papa = inject("$papa");
const props = defineProps({
tparpage: {
type: Array,
default: function () {
return [10, 20, 50, "Tous"];
},
},
apiuri: {
type: String,
required: true,
},
pdfuri: {
type: String,
},
namepdf: {
type: String,
},
fichierpdf: {
type: String,
},
argpdf: {
type: String,
},
champcsv: {
type: String,
},
delimitercsv: {
type: String,
},
namecsv: {
type: String,
},
url_sort_asc: {
type: String,
},
url_sort_desc: {
type: String,
},
urlvuebloc: {
type: String,
},
vueblocdefaut: {
type: String,
default: "tableau",
},
filtrecolmulti: {
type: String,
},
nomblocajaxreload: {
type: String,
},
stockage: {
type: String,
},
includespip: {
type: String,
},
filtrer: {
type: String,
},
_id: {
type: String,
},
filtreselect: {
type: String,
},
});
let table = ref([]);
let header = ref([]);
let crayons = ref([]);
let classes = ref([]);
let checkbox = ref([]);
let Tcheckbox = ref([]);
let ordreCol = ref([]);
let filtreCol = ref([]);
let filtreColExist = ref(false);
let filtreColType = ref([]);
let filtreColSelected = ref({});
let filtreColModif = ref(0);
let filtreColVal = ref({});
let search = ref(props.filtrer);
let page = ref(1);
let parPage = ref(
sessionStorage.getItem("nbItems")
? sessionStorage.getItem("nbItems")
: props.tparpage[0]
);
let parPageSelect = ref(
sessionStorage.getItem("nbItemsChaine")
? sessionStorage.getItem("nbItemsChaine")
: props.tparpage[0]
);
let pages = ref([]);
let triOrders = ref([]);
let triProps = ref([]);
let selectTr = ref([]);
let champ_search = ref("html");
let chargement = ref(true);
let nameLocalStorage = ref(calculer_nameLocalStorage());
let quelleVue = ref(props.vueblocdefaut);
let vuebloc = ref(false);
let model = ref([]);
let options = ref([]);
let searchInputHead = ref("");
let searchInputVal = ref("");
let loadingVueSelect = ref(true);
let ajaxCrayons = ref(false);
let maj = ref(0);
onMounted(() => {
localforage.setDriver(localforage[props.stockage.toUpperCase()]);
chargerJson();
if (props.urlvuebloc) {
fetch(props.urlvuebloc)
.then((response) => response.text())
.then((data) => {
vuebloc.value = data;
});
}
});
//~~~~~~~~~~~~~~~~~~~~~~~~~
// Computed
//~~~~~~~~~~~~~~~~~~~~~~~~~
const tableau = computed(() => {
setPages();
if (!search.value && !filtreColModif.value) {
return pagination(table.value);
}
let ttt = table.value.filter((ligne) => {
let rsearch =
Object.values(ligne[champ_search.value])
.toString()
.toLowerCase()
.indexOf(search.value.toLowerCase()) < 0
? false
: true;
if (!rsearch) {
return false;
}
Object.keys(filtreColSelected.value).forEach((colName) => {
if (rsearch) {
let colValue = filtreColSelected.value[colName];
if (colValue !== null) {
if (!Array.isArray(colValue)) {
colValue = [colValue];
}
let TcolValue = [];
colValue.forEach((s) => {
if (Number.isInteger(s)) {
TcolValue.push(s);
}
if (s.length > 0) {
TcolValue.push(s.toLowerCase());
}
});
if (TcolValue.length) {
if (filtreColType.value[colName] === "select") {
let Trsearch = TcolValue.some((uneValeur) => {
if (Number.isInteger(uneValeur)) {
if (
parseInt(
ligne[champ_search.value][colName]
) === uneValeur
) {
return true;
}
} else {
if (
ligne[champ_search.value][colName] !==
undefined &&
!(
uneValeur.indexOf(
ligne[champ_search.value][
colName
]
.toString()
.toLowerCase()
.toString()
) === -1 ||
!ligne[champ_search.value][colName]
.toString()
.toLowerCase()
)
) {
return true;
}
}
});
if (!Trsearch) {
rsearch = false;
}
} else {
if (
ligne[champ_search.value][colName]
.toString()
.toLowerCase()
.indexOf(TcolValue.toString()) === -1
) {
rsearch = false;
}
}
}
}
}
});
return rsearch;
});
return pagination(ttt);
});
//~~~~~~~~~~~~~~~~~~~~~~~~~
// les watchers
//~~~~~~~~~~~~~~~~~~~~~~~~~
watch(parPageSelect, (e) => {
if (!parseInt(e)) {
parPage.value = table.value.length;
} else {
parPage.value = e;
}
sessionStorage.setItem("nbItems", parPage.value);
sessionStorage.setItem("nbItemsChaine", parPageSelect.value);
});
watch(table, () => {
saveHeader();
let $table = [];
$table = table.value;
localforage.setItem(nameLocalStorage.value, JSON.stringify($table));
});
watch(tableau, () => {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Si on veut filtrer la liste des options dynamique en fonction
// du tri du tableau
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if (props.filtrecolmulti === "non") {
filtreCol.value.forEach((col) => {
let Tval = [];
tableau.value.forEach((t) => {
let valCol = t[champ_search.value][col];
if (Tval.indexOf(valCol) === -1) {
Tval.push(valCol);
filtreColValOk.value = true;
}
});
filtreColVal.value[col] = Tval;
});
}
});
watch(filtreColSelected, () => {
if (!loadingVueSelect.value) {
let ObfiltreSelect = [];
if (filtreColSelected.value) {
Object.entries(filtreColSelected.value).forEach(
([champ, valeurs]) => {
if (!Array.isArray(valeurs)) {
valeurs = [valeurs];
}
ObfiltreSelect.push({ champ, valeurs });
}
);
}
if (!ajaxCrayons) {
localStorage.setItem(
"filtreselect_" + nameLocalStorage.value,
JSON.stringify(ObfiltreSelect)
);
}
}
});
//~~~~~~~~~~~~~~~~~~~~~~~~~
// les methodes
//~~~~~~~~~~~~~~~~~~~~~~~~~
function gererConfig(config) {
header.value = config.header;
if (config.crayons !== undefined) {
crayons.value = config.crayons;
}
if (config.maj !== undefined) {
maj.value = config.maj?.lastMAJ;
}
if (config.classes !== undefined) {
classes.value = config.classes;
}
if (config.checkbox !== undefined) {
checkbox.value = config.checkbox;
Object.keys(checkbox.value).forEach((head) => {
Tcheckbox.value[head] = [];
});
}
if (config.ordreCol !== undefined) {
ordreCol.value = config.ordreCol;
}
let filtreCol = [];
if (config.filtreCol !== undefined) {
filtreCol = config.filtreCol;
}
return filtreCol;
}
function gererData(filtreColRecup, data, id = null) {
if (parseInt(id) > 0) {
if (data.length > 0) {
let i = trouver_index(table.value, id);
table.value[i] = data[0];
} else {
let i = trouver_index(table.value, id);
table.value.splice(i, 1);
}
localforage.setItem(
nameLocalStorage.value,
JSON.stringify(table.value)
);
} else if (id === "maj") {
console.log("table.value = ", table.value);
if (data[0] && data[0].search) {
champ_search.value = "search";
}
if (data.length > 0) {
data.forEach((ligne, index) => {
let i = trouver_index(table.value, ligne.html.id);
table.value[i] = data[index];
});
}
localforage.setItem(
nameLocalStorage.value,
JSON.stringify(table.value)
);
} else {
table.value = data;
if (data[0] && data[0].search) {
champ_search.value = "search";
}
}
if (filtreColRecup !== undefined) {
filtreColType.value = filtreColRecup;
filtreCol.value = [];
Object.keys(filtreColType.value).forEach((col) => {
let Tval = [];
table.value.forEach((t) => {
let valCol = t[champ_search.value][col];
if (valCol) {
if (Tval.indexOf(valCol) === -1) {
Tval.push(valCol);
}
}
});
filtreCol.value.push(col);
filtreColVal.value[col] = Tval.sort();
filtreColSelected.value[col] = [];
});
}
}
function chargerJson(id) {
console.log("chargerJson pour ", id);
chargement.value = true;
let url = props.apiuri;
if (parseInt(id) > 0) {
url += "&id=" + id;
} else if (id === "maj") {
url += "&maj=" + maj.value;
} else {
let config = localStorage.getItem("header_" + nameLocalStorage.value);
config = recupJson(config);
if (config && config.header !== undefined) {
let filtreColRecup = gererConfig(config);
localforage
.getItem(nameLocalStorage.value)
.then(function (data) {
data = recupJson(data);
gererData(filtreColRecup, data, id);
if (props.nomblocajaxreload) {
ajaxReload(props.nomblocajaxreload);
}
})
.catch(function (err) {
console.log(err);
});
}
}
fetch(url)
.then((response) => response.json())
.then((data) => {
let config = data.shift();
let filtreColRecup = gererConfig(config);
gererData(filtreColRecup, data, id);
nextTick(() => {
chargement.value = false;
let filtreselectLS = localStorage.getItem(
"filtreselect_" + nameLocalStorage.value
);
let Tfiltres = [];
if (props.filtreselect) {
Tfiltres = recupJson(
decodeURIComponent(props.filtreselect)
);
localStorage.setItem(
"filtreselect_" + nameLocalStorage.value,
JSON.stringify(Tfiltres)
);
} else if (filtreselectLS) {
Tfiltres = JSON.parse(filtreselectLS);
}
Tfiltres.forEach((col) => {
filtreColSelected.value[col.champ] = [
...filtreColSelected.value[col.champ],
...col.valeurs,
];
});
filtreColModif.value++;
ajaxCrayons = false;
if (parseInt(props._id) > 0) {
let _id = parseInt(props._id);
let parPageL = parseInt(parPage.value);
if (parseInt(parPageL)) {
let ordre = 0;
Object.values(table.value).forEach((d, i) => {
if (_id === d.html.id) {
ordre = i + 1;
}
});
if (ordre > parPageL) {
let numPage = parseInt(ordre / parPageL) + 1;
page.value = numPage;
}
}
selectLigne(_id, "id");
}
if (props.nomblocajaxreload) {
ajaxReload(props.nomblocajaxreload);
}
if (ordreCol.value) {
Object.entries(ordreCol.value).forEach(([col, sens]) => {
tri(col, sens);
});
}
$("td.crayon-init").removeClass("crayon-init");
});
})
.catch((error) => console.log(error));
}
function saveHeader() {
let $header = {
header: header.value,
crayons: crayons.value,
classes: classes.value,
filtreCol: filtreColType.value,
ordreCol: ordreCol.value,
maj: maj.value,
};
localStorage.setItem(
"header_" + nameLocalStorage.value,
JSON.stringify($header)
);
}
function deleteInputSearch(head) {
loadingVueSelect.value = false;
filtreColSelected.value[head] = [];
}
function selectValCol() {
filtreColModif.value++;
}
function endLoadingVueSelect() {
loadingVueSelect.value = false;
}
function calculer_nameLocalStorage() {
if (props.apiuri) {
return props.apiuri.match(/.*page=(.*)/)[1];
}
return "";
}
function afficher_crayons(name, l) {
let id =
l.crayons !== undefined && l.crayons[name] !== undefined
? l.crayons[name]
: l.html.id;
if (Object.keys(crayons.value).indexOf(name) !== -1) {
return `crayon ${crayons.value[name]}-${name}-${id}`;
}
}
function tri(col, sens = false) {
const i = triProps.value.indexOf(col);
if (i !== -1) {
if (!sens) {
sens = "asc";
if (triOrders.value[i] === "asc") {
sens = "desc";
}
}
triOrders.value[i] = sens;
} else {
if (!sens) {
sens = "asc";
}
triProps.value.push(col);
triOrders.value.push(sens);
}
table.value = orderBy(
table.value,
triProps.value,
triOrders.value,
champ_search.value
);
ordreCol.value[col] = sens;
saveHeader();
}
function ordreActif(col, sens) {
const i = triProps.value.indexOf(col);
if (i !== -1) {
if (triOrders.value[i] === sens) {
return "active";
}
}
}
function resetTri() {
loadingVueSelect.value = false;
table.value = orderBy(table.value, ["id"], "", champ_search.value);
triOrders.value = [];
triProps.value = [];
ordreCol.value = [];
Object.keys(filtreColType.value).forEach((col) => {
filtreColSelected.value[col] = [];
});
}
function selectLigne(id, col) {
if (col === "id" && parseInt(id)) {
let i = selectTr.value.indexOf(id);
if (i !== -1) {
selectTr.value.splice(i, 1);
} else {
selectTr.value.push(id);
}
}
}
function genererPDF(quoi = "tableau") {
let $tableau = [];
Object.values(this[quoi]).forEach((d) => {
$tableau.push(d.html);
});
const data = {
fichierpdf: props.fichierpdf,
namepdf: props.namepdf,
header: header.value,
arg: props.argpdf,
Tdata: $tableau,
};
let req = $.ajax({
url: props.pdfuri,
type: "POST",
dataType: "text",
data: data,
});
req.done(function (urlpdf) {
if (urlpdf) {
navigate(urlpdf, true);
}
});
}
function exportCSV(quoi = "tableau") {
let $csv = [];
let $header = [];
let $tableau = [];
Object.keys(header.value).forEach((k) => $header.push(k));
$tableau = this[quoi].reduce((acc, ligne) => {
let $uneLigne = [];
Object.values(ligne[props.champcsv]).forEach((l) => $uneLigne.push(l));
return [...acc, [...$uneLigne]];
}, []);
$csv = [[...$header], ...$tableau];
exporterCSV($csv, props.delimitercsv, props.namecsv);
}
function replaceBloc(ligne) {
let html = vuebloc.value;
Object.keys(ligne).forEach((key) => {
html = html.replace(`@@${key}@@`, ligne[key]);
});
return html;
}
function changerVue(vue) {
quelleVue.value = vue;
}
function validerCheckboxCol(head) {
let tableau = tableau.value;
if (Tcheckbox.value[head] !== undefined && Tcheckbox.value[head].length) {
Tcheckbox.value[head] = [];
} else {
Tcheckbox.value[head] = [];
tableau.forEach((d) => {
Tcheckbox.value[head].push(d.html.id);
});
}
}
function checkboxValider(head, url) {
let typeLien = "page";
if (url.includes("action=")) {
typeLien = "action";
}
if (typeLien === "action") {
$.ajax({
url: url,
data: { data: Tcheckbox.value[head] },
type: "POST",
}).done(function () {
chargerJson();
});
} else {
const Tcheck = encodeURIComponent(
JSON.stringify(Tcheckbox.value[head])
);
url += "&data=" + Tcheck;
url += "&var_zajax=content";
const data = {};
data.onClose = () => {
chargerJson();
};
$.modalbox(url, data);
}
}
function pagination(tableau) {
let from = page.value * parPage.value - parPage.value;
let to = page.value * parPage.value;
return tableau.slice(from, to);
}
function setPages() {
let nombreDePages = Math.ceil(table.value.length / parPage.value);
pages.value = [];
for (let index = 1; index <= nombreDePages; index++) {
pages.value.push(index);
}
}
function exporterCSV(json, delimitercsv, name) {
if (name.includes(".csv")) {
name = name.split(".")[0];
}
let csv = "";
if (delimitercsv) {
csv = $papa.unparse(json, { delimiter: delimitercsv });
} else {
csv = $papa.unparse(json);
}
$papa.download(csv, name);
}
function rechargerJson(id, ajax_Crayons = true) {
console.log("rechargerJson");
ajaxCrayons = ajax_Crayons;
chargerJson(id);
}
defineExpose({ rechargerJson });
</script>
<style scoped>
a {
color: #42b983;
}
</style>