Files
Checkin-Tool/pages/Page1/jsobjects/CheckinData/CheckinData.js
T

897 lines
16 KiB
JavaScript

export default {
ALL_VALUE: "__ALL__",
CORE_COLUMNS: [
"Vorgang",
"Name",
"Vorname",
"E-Mail",
"Email",
"Geb.-Datum",
"Zus_Wunsch",
"Skipassnummer"
],
getWorkbook() {
const file = Dateiupload.files?.[0];
if (!file) {
return [];
}
const rawData = file.data;
if (
rawData === null ||
rawData === undefined ||
rawData === ""
) {
return [];
}
if (typeof rawData === "string") {
try {
return JSON.parse(rawData);
} catch (error) {
return [];
}
}
return rawData;
},
getSheetRows() {
const workbook = this.getWorkbook();
if (
!Array.isArray(workbook) ||
workbook.length === 0
) {
return [];
}
/*
* Appsmith liefert Excel-Dateien meist so:
*
* [
* {
* name: "!Leistungsliste",
* data: [
* ["Vorgang", "Name", "Vorname"],
* ["98555", "Statz", "Hannah"]
* ]
* }
* ]
*/
if (
workbook[0] !== null &&
typeof workbook[0] === "object" &&
!Array.isArray(workbook[0]) &&
Array.isArray(workbook[0].data)
) {
const preferredSheet =
workbook.find(sheet =>
String(sheet?.name ?? "")
.toLowerCase()
.includes("leistungsliste")
);
const selectedSheet =
preferredSheet ?? workbook[0];
return Array.isArray(selectedSheet?.data)
? selectedSheet.data
: [];
}
/*
* Alternativ können die Zeilen direkt geliefert werden.
*/
return workbook;
},
createUniqueHeaders(headerRow) {
const usedHeaders = {};
return headerRow.map((value, index) => {
let header = String(value ?? "")
.replace(/\s+/g, " ")
.trim();
if (!header) {
header = `Spalte_${index + 1}`;
}
usedHeaders[header] =
(usedHeaders[header] ?? 0) + 1;
if (usedHeaders[header] > 1) {
return `${header}_${usedHeaders[header]}`;
}
return header;
});
},
rowsAsObjects() {
const rows = this.getSheetRows();
if (
!Array.isArray(rows) ||
rows.length === 0
) {
return [];
}
/*
* Daten liegen bereits als Objekte vor.
*/
if (
rows[0] !== null &&
typeof rows[0] === "object" &&
!Array.isArray(rows[0])
) {
return rows.filter(row =>
Object.values(row).some(value =>
String(value ?? "").trim() !== ""
)
);
}
/*
* Daten liegen als Zeilen-Arrays vor.
* Erste Zeile wird als Überschrift verwendet.
*/
if (!Array.isArray(rows[0])) {
return [];
}
const headers =
this.createUniqueHeaders(rows[0]);
return rows
.slice(1)
.filter(row =>
Array.isArray(row) &&
row.some(value =>
String(value ?? "").trim() !== ""
)
)
.map(row => {
const result = {};
headers.forEach((header, index) => {
result[header] = row[index] ?? "";
});
return result;
});
},
cleanText(value) {
return String(value ?? "").trim();
},
isBooked(value) {
const normalized = String(value ?? "")
.trim()
.toLowerCase();
const emptyValues = [
"",
"0",
"false",
"nein",
"no",
"n",
"null",
"undefined",
"-"
];
return !emptyValues.includes(normalized);
},
formatExcelDate(value) {
if (
value === null ||
value === undefined ||
value === ""
) {
return "";
}
const numericValue = Number(value);
/*
* Excel-Datumswerte sind fortlaufende Nummern.
* Beispiel: 41495 wird zu einem echten Datum.
*/
if (
Number.isFinite(numericValue) &&
numericValue > 20000 &&
numericValue < 100000
) {
const milliseconds =
Math.round(
(numericValue - 25569) *
86400 *
1000
);
const date = new Date(milliseconds);
if (!Number.isNaN(date.getTime())) {
const day = String(
date.getUTCDate()
).padStart(2, "0");
const month = String(
date.getUTCMonth() + 1
).padStart(2, "0");
const year =
date.getUTCFullYear();
return `${day}.${month}.${year}`;
}
}
return this.cleanText(value);
},
getServiceColumns() {
const rows = this.rowsAsObjects();
if (rows.length === 0) {
return [];
}
return Object.keys(rows[0])
.filter(column =>
!this.CORE_COLUMNS.includes(column)
)
.filter(column =>
!column.startsWith("Spalte_")
);
},
getSavedState() {
const state =
appsmith.store?.checkinState;
if (
state !== null &&
typeof state === "object" &&
!Array.isArray(state)
) {
return state;
}
return {};
},
createPersonId(rawRow, index) {
const vorgang =
this.cleanText(rawRow["Vorgang"]);
const name =
this.cleanText(rawRow["Name"]);
const vorname =
this.cleanText(rawRow["Vorname"]);
const email =
this.cleanText(
rawRow["E-Mail"] ??
rawRow["Email"]
);
const gebDatum =
this.cleanText(rawRow["Geb.-Datum"]);
return [
vorgang,
name,
vorname,
email,
gebDatum,
index
]
.join("|")
.toLowerCase()
.replace(/\s+/g, "_");
},
internalRows() {
const rawRows =
this.rowsAsObjects();
const serviceColumns =
this.getServiceColumns();
const savedState =
this.getSavedState();
return rawRows.map((rawRow, index) => {
const id =
this.createPersonId(rawRow, index);
const state =
savedState[id] ?? {};
const bookedServices =
serviceColumns.filter(column =>
this.isBooked(rawRow[column])
);
return {
id,
vorgang:
this.cleanText(
rawRow["Vorgang"]
),
name:
this.cleanText(
rawRow["Name"]
),
vorname:
this.cleanText(
rawRow["Vorname"]
),
email:
this.cleanText(
rawRow["E-Mail"] ??
rawRow["Email"]
),
gebDatum:
this.formatExcelDate(
rawRow["Geb.-Datum"]
),
zimmer:
state.zimmer ??
this.cleanText(
rawRow["Zus_Wunsch"]
),
schluessel:
Number(
state.schluessel ?? 0
),
schluesselRetour:
Boolean(
state.schluesselRetour ??
false
),
leistungenListe:
bookedServices,
leistungen:
bookedServices.join(" · "),
skipassnummer:
state.skipassnummer ??
this.cleanText(
rawRow["Skipassnummer"]
),
notiz:
state.notiz ?? "",
stirnlampe:
Number(
state.stirnlampe ?? 0
),
stirnlampeRetour:
Boolean(
state.stirnlampeRetour ??
false
),
nachbuchungen:
Array.isArray(
state.nachbuchungen
)
? state.nachbuchungen
: [],
status:
state.status ?? "offen",
reiseStatus:
state.reiseStatus ?? "aktiv",
checkedIn:
Boolean(
state.checkedIn ??
false
),
checkedInAt:
state.checkedInAt ?? "",
checkedInBy:
state.checkedInBy ?? ""
};
});
},
vorgangOptions() {
const vorgaenge = [
...new Set(
this.internalRows()
.map(row => row.vorgang)
.filter(Boolean)
)
].sort((a, b) =>
a.localeCompare(
b,
"de",
{
numeric: true
}
)
);
return [
{
label: "Alle Gruppen",
value: this.ALL_VALUE
},
...vorgaenge.map(vorgang => ({
label: vorgang,
value: vorgang
}))
];
},
leistungOptions() {
const services = [
...new Set(
this.internalRows()
.flatMap(row =>
row.leistungenListe ?? []
)
.filter(Boolean)
)
].sort((a, b) =>
a.localeCompare(b, "de")
);
return [
{
label: "Alle Leistungen",
value: this.ALL_VALUE
},
...services.map(service => ({
label: service,
value: service
}))
];
},
filteredInternalRows() {
let rows = [
...this.internalRows()
];
/*
* Suche
*/
const searchText =
this.cleanText(
inpSuche.text
).toLowerCase();
if (searchText) {
rows = rows.filter(row => {
const searchableText = [
row.vorgang,
row.name,
row.vorname,
row.email,
row.gebDatum,
row.zimmer,
row.skipassnummer,
row.leistungen
]
.join(" ")
.toLowerCase();
return searchableText.includes(
searchText
);
});
}
/*
* Vorgangs-/Gruppenfilter
*/
const selectedVorgang =
selVorgang.selectedOptionValue ||
this.ALL_VALUE;
if (
selectedVorgang !==
this.ALL_VALUE
) {
rows = rows.filter(row =>
row.vorgang ===
String(selectedVorgang)
);
}
/*
* Check-in-Filter
*/
const selectedCheckin =
selCheckin.selectedOptionValue ||
this.ALL_VALUE;
if (
selectedCheckin === "checked"
) {
rows = rows.filter(row =>
row.checkedIn === true
);
}
if (
selectedCheckin === "pending"
) {
rows = rows.filter(row =>
row.checkedIn === false
);
}
/*
* Reisestatusfilter
*/
const selectedReiseStatus =
selReiseStatus.selectedOptionValue ||
this.ALL_VALUE;
if (
selectedReiseStatus !==
this.ALL_VALUE
) {
rows = rows.filter(row =>
row.reiseStatus ===
selectedReiseStatus
);
}
/*
* Leistungsfilter
*/
const selectedLeistung =
selLeistung.selectedOptionValue ||
this.ALL_VALUE;
if (
selectedLeistung !==
this.ALL_VALUE
) {
rows = rows.filter(row =>
row.leistungenListe.includes(
selectedLeistung
)
);
}
return rows;
},
filteredRows() {
return this.filteredInternalRows()
.map(row => ({
id:
row.id,
Vorgang:
row.vorgang,
Name:
row.name,
Vorname:
row.vorname,
"E-Mail":
row.email,
"Geb.-Datum":
row.gebDatum,
Zimmer:
row.zimmer,
Schlüssel:
row.schluessel,
"Schlüssel retour":
row.schluesselRetour,
"Skipass / Leistungen":
row.leistungen,
Skipassnummer:
row.skipassnummer,
Notiz:
row.notiz,
Stirnlampe:
row.stirnlampe,
"Stirnlampe retour":
row.stirnlampeRetour,
Nachbuchungen:
row.nachbuchungen.length,
Status:
row.status,
Reisestatus:
row.reiseStatus,
Eingecheckt:
row.checkedIn,
"Check-in Zeitpunkt":
row.checkedInAt,
"Check-in Benutzer":
row.checkedInBy
}));
},
stats() {
const rows =
this.internalRows();
return {
gesamt:
rows.length,
eingecheckt:
rows.filter(row =>
row.checkedIn === true
).length,
ausstehend:
rows.filter(row =>
row.checkedIn === false
).length,
gruppen:
new Set(
rows
.map(row => row.vorgang)
.filter(Boolean)
).size
};
},
async savePersonState(
personId,
changes
) {
if (!personId) {
showAlert(
"Teilnehmer-ID fehlt.",
"error"
);
return false;
}
const currentState = {
...this.getSavedState()
};
currentState[personId] = {
...(currentState[personId] ?? {}),
...changes,
updatedAt:
new Date().toISOString(),
updatedBy:
appsmith.user?.email ||
"lokaler Benutzer"
};
await storeValue(
"checkinState",
currentState,
true
);
return true;
},
async toggleCheckin(tableRow) {
if (!tableRow?.id) {
showAlert(
"Teilnehmer konnte nicht ermittelt werden.",
"error"
);
return false;
}
const person =
this.internalRows().find(row =>
row.id === tableRow.id
);
if (!person) {
showAlert(
"Teilnehmer wurde nicht gefunden.",
"error"
);
return false;
}
const newCheckedIn =
!person.checkedIn;
await this.savePersonState(
person.id,
{
checkedIn:
newCheckedIn,
checkedInAt:
newCheckedIn
? new Date().toISOString()
: "",
checkedInBy:
newCheckedIn
? (
appsmith.user?.email ||
"lokaler Benutzer"
)
: ""
}
);
showAlert(
newCheckedIn
? `${person.vorname} ${person.name} wurde eingecheckt.`
: `Check-in von ${person.vorname} ${person.name} wurde aufgehoben.`,
newCheckedIn
? "success"
: "warning"
);
return true;
},
async saveUpdatedRow(updatedRow) {
if (!updatedRow?.id) {
showAlert(
"Die bearbeitete Person konnte nicht ermittelt werden.",
"error"
);
return false;
}
const person =
this.internalRows().find(row =>
row.id === updatedRow.id
);
if (!person) {
showAlert(
"Teilnehmer wurde nicht gefunden.",
"error"
);
return false;
}
const schluesselValue =
Number(updatedRow["Schlüssel"]);
const stirnlampeValue =
Number(updatedRow["Stirnlampe"]);
await this.savePersonState(
person.id,
{
zimmer:
this.cleanText(
updatedRow["Zimmer"] ??
person.zimmer
),
schluessel:
Number.isFinite(schluesselValue)
? schluesselValue
: person.schluessel,
schluesselRetour:
Boolean(
updatedRow["Schlüssel retour"] ??
person.schluesselRetour
),
skipassnummer:
this.cleanText(
updatedRow["Skipassnummer"] ??
person.skipassnummer
),
notiz:
this.cleanText(
updatedRow["Notiz"] ??
person.notiz
),
stirnlampe:
Number.isFinite(stirnlampeValue)
? stirnlampeValue
: person.stirnlampe,
stirnlampeRetour:
Boolean(
updatedRow["Stirnlampe retour"] ??
person.stirnlampeRetour
),
status:
this.cleanText(
updatedRow["Status"] ??
person.status
),
reiseStatus:
this.cleanText(
updatedRow["Reisestatus"] ??
person.reiseStatus
)
}
);
showAlert(
"Änderungen gespeichert.",
"success"
);
return true;
},
async resetCheckinState() {
await removeValue(
"checkinState"
);
showAlert(
"Gespeicherte Check-in-Daten wurden zurückgesetzt.",
"success"
);
return true;
},
async checkinButtonClick() {
return await this.toggleCheckin(
Teilnehmer.triggeredRow
);
}
};