diff --git a/application/backend/batches/batchendpoints.ts b/application/backend/batches/batchendpoints.ts index e584e8cc..44bd001b 100644 --- a/application/backend/batches/batchendpoints.ts +++ b/application/backend/batches/batchendpoints.ts @@ -42,7 +42,7 @@ async function nightlyMails() { checkMaster(now), ]); - const jobtypes: JobType[] = ["Presse", "Fluegel", "Photo", "TextFehlt", "Kasse", "Programmheft", "Staff", "Bar"]; + const jobtypes: JobType[] = ["Presse", "Fluegel", "Photo", "TextFehlt", "Kasse", "Programmheft", "Staff", "Bar", "Master"]; const typedResults = map(results, (jobResult, index) => ({ type: jobtypes[index], jobResult })); return informAdmin(typedResults); } diff --git a/application/backend/batches/sendMailToAdmin.ts b/application/backend/batches/sendMailToAdmin.ts index 6b92923d..84a7f9b1 100644 --- a/application/backend/batches/sendMailToAdmin.ts +++ b/application/backend/batches/sendMailToAdmin.ts @@ -7,7 +7,7 @@ import map from "lodash/map.js"; const receiver = "leider"; -export type JobType = "Programmheft" | "Presse" | "Kasse" | "Bar" | "Photo" | "Fluegel" | "TextFehlt" | "Staff"; +export type JobType = "Programmheft" | "Presse" | "Kasse" | "Bar" | "Photo" | "Fluegel" | "TextFehlt" | "Staff" | "Master"; export async function informAdmin(allResults: { type: JobType; jobResult: JobResult }[]) { const user = userstore.forId(receiver); diff --git a/application/backend/lib/commons/replies.ts b/application/backend/lib/commons/replies.ts index 3cb5c94b..e31031ff 100644 --- a/application/backend/lib/commons/replies.ts +++ b/application/backend/lib/commons/replies.ts @@ -1,17 +1,5 @@ import { Response } from "express"; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function reply(res: Response, err?: Error | null, value?: any): void { - if (err) { - res.status(500).send(err.message ? err.message : err); - return; - } - const valToSend = value?.toJSON ? value.toJSON() : value; - res.type("application/json").send(valToSend || { status: "ok" }); -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function resToJson(res: Response, value?: any) { - const valToSend = value?.toJSON ? value.toJSON() : value; - res.type("application/json").send(valToSend ?? { status: "ok" }); +export function resToJson(res: Response, value?: T) { + res.json(value); } diff --git a/application/backend/lib/konzerte/imageService.ts b/application/backend/lib/konzerte/imageService.ts index ae9006d5..0a5e2376 100644 --- a/application/backend/lib/konzerte/imageService.ts +++ b/application/backend/lib/konzerte/imageService.ts @@ -4,6 +4,7 @@ import { ImageOverviewRow } from "jc-shared/konzert/konzert.js"; import store from "./konzertestore.js"; import conf from "jc-shared/commons/simpleConfigure.js"; import map from "lodash/map.js"; +import sortBy from "lodash/sortBy.js"; async function renameImage(oldname: string, newname: string, konzertIds: string[], user: User) { function updateKonzert(id: string) { @@ -31,6 +32,6 @@ export default { alleBildNamen: async function alleBildNamen() { const files = await fs.readdir(conf.uploadDir); - return files.sort(); + return sortBy(files); }, }; diff --git a/application/backend/lib/konzerte/konzertestore.ts b/application/backend/lib/konzerte/konzertestore.ts index 1a07f2c6..13ffcdd8 100644 --- a/application/backend/lib/konzerte/konzertestore.ts +++ b/application/backend/lib/konzerte/konzertestore.ts @@ -60,8 +60,7 @@ export default { }, saveKonzert(konzert: Konzert, user: User) { - const object = konzert.toJSON(); - persistence.save(object as { id: string }, user); + persistence.save(konzert as { id: string }, user); return konzert; }, diff --git a/application/backend/lib/mailsender/mailstore.ts b/application/backend/lib/mailsender/mailstore.ts index 5b7b9dd8..c5970469 100644 --- a/application/backend/lib/mailsender/mailstore.ts +++ b/application/backend/lib/mailsender/mailstore.ts @@ -21,7 +21,7 @@ export default { }, save: function save(mailRule: MailRule, user: User) { - persistence.save(mailRule.toJSON(), user); + persistence.save(mailRule, user); return mailRule; }, diff --git a/application/backend/lib/optionen/kalendereventstore.ts b/application/backend/lib/optionen/kalendereventstore.ts index a2bd90b3..a0f8cfa0 100644 --- a/application/backend/lib/optionen/kalendereventstore.ts +++ b/application/backend/lib/optionen/kalendereventstore.ts @@ -12,7 +12,7 @@ export default { }, save: function save(event: KalenderEvents) { - persistence.save(event.toJSON(), new User({ name: "System" })); + persistence.save(event, new User({ name: "System" })); return event; }, }; diff --git a/application/backend/lib/optionen/optionenstore.ts b/application/backend/lib/optionen/optionenstore.ts index dd21772b..048774c6 100644 --- a/application/backend/lib/optionen/optionenstore.ts +++ b/application/backend/lib/optionen/optionenstore.ts @@ -25,7 +25,7 @@ export default { }, save: function save(object: OptionValues | Orte | FerienIcals, user: User) { - persistence.save(object.toJSON ? object.toJSON() : object, user); + persistence.save(object, user); return object; }, }; diff --git a/application/backend/lib/optionen/terminstore.ts b/application/backend/lib/optionen/terminstore.ts index 7f9cbf7c..4b1fddab 100644 --- a/application/backend/lib/optionen/terminstore.ts +++ b/application/backend/lib/optionen/terminstore.ts @@ -23,7 +23,7 @@ export default { }, save: function save(termin: Termin, user: User) { - persistence.save(termin.toJSON(), user); + persistence.save(termin, user); return termin; }, diff --git a/application/backend/lib/persistence/sqlitePersistence.ts b/application/backend/lib/persistence/sqlitePersistence.ts index b02f9c38..9933f63c 100644 --- a/application/backend/lib/persistence/sqlitePersistence.ts +++ b/application/backend/lib/persistence/sqlitePersistence.ts @@ -5,6 +5,7 @@ import User from "jc-shared/user/user.js"; import { areDifferentForHistoryEntries } from "jc-shared/commons/comparingAndTransforming.js"; import map from "lodash/map.js"; import forEach from "lodash/forEach.js"; +import isString from "lodash/isString.js"; export const db = new Database(conf.sqlitedb); const scriptLogger = loggers.get("scripts"); @@ -14,8 +15,11 @@ function asSqliteString(obj: object) { return `${escape(JSON.stringify(obj))}`; } -export function escape(str = "") { - return `'${str.replaceAll("'", "''")}'`; +export function escape(str: string | Date = "") { + if (isString(str)) { + return `'${str.replaceAll("'", "''")}'`; + } + return `'${str.toJSON().replaceAll("'", "''")}'`; } export function execWithTry(command: string) { @@ -93,11 +97,10 @@ class Persistence { return ["id", "data"].concat(this.extraCols); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private createValsForSave(object: { [ind: string]: any } & { id: string }) { + private createValsForSave(object: { [ind: string]: string | Date } & { id: string }) { return [escape(object.id), asSqliteString(object)].concat( map(this.extraCols, (col) => { - return object[col]?.toJSON ? escape(object[col].toJSON()) : escape(object[col]); + return escape(object[col]); }), ); } diff --git a/application/backend/lib/rider/ridersrest.ts b/application/backend/lib/rider/ridersrest.ts index b5e0e646..f1758569 100644 --- a/application/backend/lib/rider/ridersrest.ts +++ b/application/backend/lib/rider/ridersrest.ts @@ -25,7 +25,8 @@ app.post("/", (req: Request, res: Response) => { } const anonymous = new User({ id: "anonymous", name: "Rider Anonymous" }); store.saveRider(rider, anonymous); - return resToJson(res, rider); + resToJson(res, rider); + return; } } res.sendStatus(500); diff --git a/application/backend/lib/rider/riderstore.ts b/application/backend/lib/rider/riderstore.ts index 03932cf0..edf5d74c 100644 --- a/application/backend/lib/rider/riderstore.ts +++ b/application/backend/lib/rider/riderstore.ts @@ -12,7 +12,7 @@ export default { }, saveRider: function saveRider(object: Rider, user: User) { - persistence.save(object.toJSON() as Rider, user); + persistence.save(object, user); return object; }, }; diff --git a/application/backend/lib/vermietungen/vermietungenstore.ts b/application/backend/lib/vermietungen/vermietungenstore.ts index 2f60347f..ac179b79 100644 --- a/application/backend/lib/vermietungen/vermietungenstore.ts +++ b/application/backend/lib/vermietungen/vermietungenstore.ts @@ -60,8 +60,7 @@ export default { }, saveVermietung: function saveVermietung(vermietung: Vermietung, user: User) { - const object = vermietung.toJSON(); - persistence.save(object as { id: string }, user); + persistence.save(vermietung as { id: string }, user); return vermietung; }, diff --git a/application/backend/rest/calendar.ts b/application/backend/rest/calendar.ts index f030d171..2047166f 100644 --- a/application/backend/rest/calendar.ts +++ b/application/backend/rest/calendar.ts @@ -3,7 +3,7 @@ import flatMap from "lodash/flatMap.js"; import DatumUhrzeit from "jc-shared/commons/DatumUhrzeit.js"; import { Ical } from "jc-shared/optionen/ferienIcals.js"; -import { TerminFilterOptions } from "jc-shared/optionen/termin.js"; +import { TerminEvent, TerminFilterOptions } from "jc-shared/optionen/termin.js"; import User from "jc-shared/user/user.js"; import store from "../lib/konzerte/konzertestore.js"; @@ -18,6 +18,7 @@ import kalenderEventsService from "../lib/optionen/kalenderEventsService.js"; import Veranstaltung from "jc-shared/veranstaltung/veranstaltung.js"; import map from "lodash/map.js"; import filter from "lodash/filter.js"; +import identity from "lodash/identity.js"; const app = express(); @@ -67,7 +68,7 @@ app.get("/fullcalendarevents.json", async (req, res) => { const termineForIcals = await Promise.all(map(icals, termineForIcal)); const events = termine - .concat(flatMap(termineForIcals, (x) => x)) + .concat(flatMap(termineForIcals, identity)) .concat(konzerte) .concat(vermietungen); resToJson(res, events); diff --git a/application/backend/rest/konzerte.ts b/application/backend/rest/konzerte.ts index 12436e4b..f1dd3fcc 100644 --- a/application/backend/rest/konzerte.ts +++ b/application/backend/rest/konzerte.ts @@ -12,13 +12,12 @@ import { kassenzettelToBuchhaltung } from "../lib/pdf/pdfGeneration.js"; import { checkAbendkasse, checkOrgateam } from "./checkAccessHandlers.js"; import parseFormData from "../lib/commons/parseFormData.js"; import find from "lodash/find.js"; -import invokeMap from "lodash/invokeMap.js"; const app = express(); function standardHandler(res: Response, user: User, konzerte: Konzert[]) { const result = konzerteService.filterUnbestaetigteFuerJedermann(konzerte, user); - resToJson(res, invokeMap(result, "toJSON")); + resToJson(res, result); } function saveAndReply(req: Request, res: Response, konzert: Konzert) { diff --git a/application/backend/rest/mail.ts b/application/backend/rest/mail.ts index a0d3e471..22c86223 100644 --- a/application/backend/rest/mail.ts +++ b/application/backend/rest/mail.ts @@ -13,7 +13,6 @@ import User from "jc-shared/user/user.js"; import fs from "fs/promises"; import parseFormData from "../lib/commons/parseFormData.js"; import MailMessage from "jc-shared/mail/mailMessage.js"; -import invokeMap from "lodash/invokeMap.js"; import map from "lodash/map.js"; import forEach from "lodash/forEach.js"; import filter from "lodash/filter.js"; @@ -21,15 +20,13 @@ import filter from "lodash/filter.js"; const app = express(); app.get("/mailrule", [checkSuperuser], (req: Request, res: Response) => { - const rules = mailstore.all(); - const result = invokeMap(rules, "toJSON"); - resToJson(res, result); + resToJson(res, mailstore.all()); }); app.post("/mailrules", [checkSuperuser], (req: Request, res: Response) => { const oldRules = mailstore.all(); const newRules = misc.toObjectList(MailRule, req.body); - const { changed, deletedIds } = calculateChangedAndDeleted(invokeMap(newRules, "toJSON"), invokeMap(oldRules, "toJSON")); + const { changed, deletedIds } = calculateChangedAndDeleted(newRules, oldRules); mailstore.saveAll(changed, req.user as User); mailstore.removeAll(deletedIds, req.user as User); @@ -72,4 +69,9 @@ app.post("/mailinglisten", [checkSuperuser], (req: Request, res: Response) => { resToJson(res, users); }); +app.get("/mailinglisten", [checkSuperuser], (req: Request, res: Response) => { + const users = userstore.allUsers(); + resToJson(res, map(users, "withoutPass")); +}); + export default app; diff --git a/application/backend/rest/optionen.ts b/application/backend/rest/optionen.ts index c73a473e..eae59f3e 100644 --- a/application/backend/rest/optionen.ts +++ b/application/backend/rest/optionen.ts @@ -12,7 +12,6 @@ import { calculateChangedAndDeleted } from "jc-shared/commons/compareObjects.js" import misc from "jc-shared/commons/misc.js"; import { checkOrgateam } from "./checkAccessHandlers.js"; import User from "jc-shared/user/user.js"; -import invokeMap from "lodash/invokeMap.js"; const app = express(); @@ -51,7 +50,7 @@ app.get("/termine", (req, res) => { app.post("/termine", [checkOrgateam], (req: Request, res: Response) => { const oldTermine = terminstore.alle(); const newTermine = misc.toObjectList(Termin, req.body); - const { changed, deletedIds } = calculateChangedAndDeleted(invokeMap(newTermine, "toJSON"), invokeMap(oldTermine, "toJSON")); + const { changed, deletedIds } = calculateChangedAndDeleted(newTermine, oldTermine); terminstore.saveAll(changed, req.user as User); terminstore.removeAll(deletedIds, req.user as User); resToJson(res, terminstore.alle()); diff --git a/application/backend/rest/users.ts b/application/backend/rest/users.ts index 92a882dc..6aeb5ade 100644 --- a/application/backend/rest/users.ts +++ b/application/backend/rest/users.ts @@ -4,28 +4,28 @@ import User from "jc-shared/user/user.js"; import service from "../lib/users/usersService.js"; import store from "../lib/users/userstore.js"; -import { reply, resToJson } from "../lib/commons/replies.js"; +import { resToJson } from "../lib/commons/replies.js"; import { checkCanEditUser, checkSuperuser } from "./checkAccessHandlers.js"; -import invokeMap from "lodash/invokeMap.js"; +import map from "lodash/map.js"; const app = express(); app.get("/users/current", (req, res) => { if (req.user) { - return reply(res, undefined, new User(req.user).toJSONWithoutPass()); + return resToJson(res, new User(req.user).withoutPass); } res.sendStatus(401); }); app.get("/users", (req, res) => { const users = store.allUsers(); - resToJson(res, { users: invokeMap(users, "toJSONWithoutPass") }); + resToJson(res, map(users, "withoutPass")); }); app.post("/user/changePassword", [checkCanEditUser], (req: Request, res: Response) => { const user = new User(req.body); service.changePassword(user, req.user as User); - resToJson(res, user); + resToJson(res, user.withoutPass); }); app.post("/user", [checkCanEditUser], (req: Request, res: Response) => { @@ -37,19 +37,19 @@ app.post("/user", [checkCanEditUser], (req: Request, res: Response) => { user.hashedPassword = existingUser.hashedPassword; user.salt = existingUser.salt; store.save(user, req.user as User); - resToJson(res, user); + resToJson(res, user.withoutPass); }); app.put("/user", [checkSuperuser], (req: Request, res: Response) => { const user = new User(req.body); service.saveNewUserWithPassword(user, req.user as User); - resToJson(res, user); + resToJson(res, user.withoutPass); }); app.delete("/user", [checkSuperuser], (req: Request, res: Response) => { const userToDelete = new User(req.body); store.deleteUser(userToDelete.id, req.user as User); - resToJson(res, userToDelete); + resToJson(res, userToDelete.withoutPass); }); export default app; diff --git a/application/backend/rest/vermietungen.ts b/application/backend/rest/vermietungen.ts index 8dbdf435..5d5b7401 100644 --- a/application/backend/rest/vermietungen.ts +++ b/application/backend/rest/vermietungen.ts @@ -9,13 +9,12 @@ import store from "../lib/vermietungen/vermietungenstore.js"; import { checkOrgateam } from "./checkAccessHandlers.js"; import { saveVermietungToShare, vermietungVertragToBuchhaltung } from "../lib/pdf/pdfGeneration.js"; import { filterUnbestaetigteFuerJedermann } from "../lib/vermietungen/vermietungenService.js"; -import invokeMap from "lodash/invokeMap.js"; const app = express(); async function standardHandler(req: Request, res: Response, vermietungen: Vermietung[]) { const user: User = req.user as User; - resToJson(res, invokeMap(filterUnbestaetigteFuerJedermann(vermietungen, user), "toJSON")); + resToJson(res, filterUnbestaetigteFuerJedermann(vermietungen, user)); } function saveAndReply(req: Request, res: Response, vermietung: Vermietung) { diff --git a/application/shared/konzert/eintrittspreise.ts b/application/shared/konzert/eintrittspreise.ts index 12970d0d..a384e5fa 100644 --- a/application/shared/konzert/eintrittspreise.ts +++ b/application/shared/konzert/eintrittspreise.ts @@ -23,11 +23,6 @@ export default class Eintrittspreise { }; } - /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ - toJSON(): any { - return Object.assign({}, this); - } - constructor(object?: RecursivePartial) { if (object && keys(object).length !== 0) { if (!object.preisprofil) { diff --git a/application/shared/konzert/kasse.ts b/application/shared/konzert/kasse.ts index 99219639..de527efd 100644 --- a/application/shared/konzert/kasse.ts +++ b/application/shared/konzert/kasse.ts @@ -64,10 +64,6 @@ export default class Kasse { "10000": undefined, }; endbestandGezaehltEUR = 0; - /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ - toJSON(): any { - return Object.assign({}, this); - } constructor(object?: RecursivePartial & { kassenfreigabeAm?: Date | string }>) { if (object && keys(object).length !== 0) { diff --git a/application/shared/konzert/konzert.ts b/application/shared/konzert/konzert.ts index 8c6f737c..bd23fe85 100644 --- a/application/shared/konzert/konzert.ts +++ b/application/shared/konzert/konzert.ts @@ -53,25 +53,6 @@ export default class Konzert extends Veranstaltung { unterkunft: Unterkunft; - toJSON(): object { - const result = {}; - Object.assign(result, this, { - agentur: this.agentur.toJSON(), - artist: this.artist.toJSON(), - eintrittspreise: this.eintrittspreise.toJSON(), - hotel: this.hotel.toJSON(), - kasse: this.kasse.toJSON(), - kopf: this.kopf.toJSON(), - kosten: this.kosten.toJSON(), - presse: this.presse.toJSON(), - staff: this.staff.toJSON(), - technik: this.technik.toJSON(), - unterkunft: this.unterkunft.toJSON(), - vertrag: this.vertrag.toJSON(), - }); - return result; - } - constructor(object?: RecursivePartial & { startDate: string | Date; endDate: string | Date }>) { super(object); if (object) { diff --git a/application/shared/konzert/unterkunft.ts b/application/shared/konzert/unterkunft.ts index bf249528..73879e77 100644 --- a/application/shared/konzert/unterkunft.ts +++ b/application/shared/konzert/unterkunft.ts @@ -18,11 +18,6 @@ export default class Unterkunft { anreiseDate: Date; abreiseDate: Date; - /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ - toJSON(): any { - return Object.assign({}, this); - } - constructor( object: | RecursivePartial< diff --git a/application/shared/konzert/vertrag.ts b/application/shared/konzert/vertrag.ts index cf6c25e3..34143930 100644 --- a/application/shared/konzert/vertrag.ts +++ b/application/shared/konzert/vertrag.ts @@ -20,11 +20,6 @@ export default class Vertrag { return ["Deutsch", "Englisch", "Regional"]; } - /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ - toJSON(): any { - return Object.assign({}, this); - } - constructor(object?: RecursivePartial) { if (object && keys(object).length !== 0) { Object.assign(this, object, { diff --git a/application/shared/mail/mailRule.ts b/application/shared/mail/mailRule.ts index d619f403..c7bfe37b 100644 --- a/application/shared/mail/mailRule.ts +++ b/application/shared/mail/mailRule.ts @@ -140,11 +140,6 @@ export default class MailRule { return new MailRule(object); } - /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ - toJSON(): any { - return Object.assign({}, this); - } - constructor(object?: Partial) { if (object) { Object.assign(this, object); diff --git a/application/shared/optionen/ferienIcals.ts b/application/shared/optionen/ferienIcals.ts index 683bd657..8e55e501 100644 --- a/application/shared/optionen/ferienIcals.ts +++ b/application/shared/optionen/ferienIcals.ts @@ -1,7 +1,6 @@ import Termin, { TerminType } from "./termin.js"; import Misc from "../commons/misc.js"; import map from "lodash/map.js"; -import invokeMap from "lodash/invokeMap.js"; export class Ical { name = ""; @@ -14,11 +13,6 @@ export class Ical { } } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - toJSON(): any { - return Object.assign({}, this); - } - get color(): string { return Termin.colorForType(this.typ); } @@ -36,11 +30,6 @@ export class KalenderEvents { this.updatedAt = Misc.stringOrDateToDate(object.updatedAt); } } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - toJSON(): any { - return Object.assign({}, this); - } } export default class FerienIcals { @@ -52,11 +41,4 @@ export default class FerienIcals { this.icals = map(object.icals, (each) => new Ical(each)); } } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - toJSON(): any { - return Object.assign({}, this, { - icals: invokeMap(this.icals, "toJSON"), - }); - } } diff --git a/application/shared/optionen/optionValues.ts b/application/shared/optionen/optionValues.ts index 2c0db45f..7dda4176 100644 --- a/application/shared/optionen/optionValues.ts +++ b/application/shared/optionen/optionValues.ts @@ -103,35 +103,34 @@ export default class OptionValues { return new OptionValues(object); } - toJSON(): object { - return Object.assign({}, this); - } - constructor(object?: Partial) { if (object) { Object.assign(this, object, { - kooperationen: sortByNameCaseInsensitive(object.kooperationen || []), - genres: sortByNameCaseInsensitive(object.genres || []), - backlineJazzclub: sortByNameCaseInsensitive(object.backlineJazzclub || []), - backlineRockshop: sortByNameCaseInsensitive(object.backlineRockshop || []), - artists: sortByNameCaseInsensitive(object.artists || []), - preisprofile: (object.preisprofile || preisprofileInitial()).sort((a: Preisprofil, b: Preisprofil) => { + kooperationen: sortByNameCaseInsensitive(object.kooperationen), + genres: sortByNameCaseInsensitive(object.genres), + backlineJazzclub: sortByNameCaseInsensitive(object.backlineJazzclub), + backlineRockshop: sortByNameCaseInsensitive(object.backlineRockshop), + artists: sortByNameCaseInsensitive(object.artists), + preisprofile: (object.preisprofile ?? preisprofileInitial()).sort((a, b) => { if (a.regulaer === b.regulaer) { return a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase()); } return a.regulaer > b.regulaer ? 1 : -1; }), - typenPlus: (object.typenPlus ?? []).sort((a: TypMitMehr, b: TypMitMehr) => - a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase()), - ), + typenPlus: (object.typenPlus ?? []).sort((a, b) => a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase())), agenturen: sortKontakte(object.agenturen), hotels: sortKontakte(object.hotels), }); } } - addOrUpdateKontakt(kontaktKey: "agenturen" | "hotels", kontakt: Kontakt, selection: string): void { - if (!(selection || "[temporär]").match(/\[temporär]/)) { + addOrUpdateKontakt(kontaktKey: "agenturen" | "hotels", kontakt: Kontakt, selection?: string): void { + if ("[temporär]" !== selection) { + if (!kontakt.name) { + // we do nothing if name not given + return; + } + const ourCollection = kontaktKey === "agenturen" ? this.agenturen : this.hotels; remove(ourCollection, (k) => k.name === kontakt.name); ourCollection.push(kontakt); diff --git a/application/shared/optionen/orte.ts b/application/shared/optionen/orte.ts index 2c90c9ae..84c9f929 100644 --- a/application/shared/optionen/orte.ts +++ b/application/shared/optionen/orte.ts @@ -6,7 +6,6 @@ import toLower from "lodash/fp/toLower.js"; import prop from "lodash/fp/prop.js"; import { RecursivePartial } from "../commons/advancedTypes.js"; import map from "lodash/map.js"; -import invokeMap from "lodash/invokeMap.js"; const sortByNameCaseInsensitive = sortBy(flowRight(toLower, prop("name"))); @@ -22,22 +21,12 @@ export class Ort { Object.assign(this, object); } } - - toJSON(): object { - return Object.assign({}, this); - } } export default class Orte { id = "orte"; orte: Ort[] = []; - toJSON(): object { - return Object.assign({}, this, { - orte: invokeMap(this.orte, "toJSON"), - }); - } - constructor(object?: RecursivePartial) { if (object && object.orte) { this.orte = sortByNameCaseInsensitive(map(object.orte, (o: any) => new Ort(o))); diff --git a/application/shared/optionen/termin.ts b/application/shared/optionen/termin.ts index 5b3dea81..f8e72142 100644 --- a/application/shared/optionen/termin.ts +++ b/application/shared/optionen/termin.ts @@ -41,11 +41,6 @@ export default class Termin { this.id = object?.id ? object.id : encodeURIComponent(DatumUhrzeit.forJSDate(this.startDate).fuerCalendarWidget + this.beschreibung); } - /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ - toJSON(): any { - return Object.assign({}, this); - } - static colorForType(typ: TerminType): string { return { Sonstiges: "#d6bdff", diff --git a/application/shared/programmheft/kalender.ts b/application/shared/programmheft/kalender.ts index 7e69c5a5..02b7426f 100644 --- a/application/shared/programmheft/kalender.ts +++ b/application/shared/programmheft/kalender.ts @@ -4,6 +4,7 @@ import { Event } from "./Event.js"; import { RecursivePartial } from "../commons/advancedTypes.js"; import map from "lodash/map.js"; import invokeMap from "lodash/invokeMap.js"; +import sortBy from "lodash/sortBy.js"; export default class Kalender { id: string; @@ -31,9 +32,8 @@ export default class Kalender { const thisDatum = DatumUhrzeit.forYYYYslashMM(this.id); const otherDatum = DatumUhrzeit.forYYYYslashMM(otherKalId); const differenz = otherDatum.differenzInMonaten(thisDatum); - const result = invokeMap(this.events, "cloneAndMoveBy", { monate: differenz }); - result.sort((a, b) => a.start.localeCompare(b.start)); - return result; + const result: Event[] = invokeMap(this.events, "cloneAndMoveBy", { monate: differenz }); + return sortBy(result, ["start"]); } sortEvents() { diff --git a/application/shared/rider/rider.ts b/application/shared/rider/rider.ts index 511a024f..77f46bae 100644 --- a/application/shared/rider/rider.ts +++ b/application/shared/rider/rider.ts @@ -19,10 +19,6 @@ export class Rider { boxes: object?.boxes || [], }); } - - toJSON(): object { - return Object.assign({}, this); - } } export class PrintableBox implements BoxParams { diff --git a/application/shared/test/konzert/eintrittspreise_object.test.ts b/application/shared/test/konzert/eintrittspreise_object.test.ts index b8be42b2..bd18dd41 100644 --- a/application/shared/test/konzert/eintrittspreise_object.test.ts +++ b/application/shared/test/konzert/eintrittspreise_object.test.ts @@ -10,11 +10,11 @@ describe("Eintrittspreise", () => { describe("Initialisiert", () => { it("mit freiem Eintritt für leeres Objekt", () => { const preise = new Eintrittspreise({}); - expect(preise.toJSON()).to.eql(freierEintritt); + expect(preise).to.eql(freierEintritt); }); it("mit freiem Eintritt für 'undefined'", () => { const preise = new Eintrittspreise(); - expect(preise.toJSON()).to.eql(freierEintritt); + expect(preise).to.eql(freierEintritt); }); }); @@ -22,23 +22,23 @@ describe("Eintrittspreise", () => { it("fuer freien Eintritt", () => { const alterDatensatz = { frei: true, erwarteteBesucher: NaN }; const preise = new Eintrittspreise(alterDatensatz); - expect(preise.toJSON()).to.eql(freierEintritt); - expect(preise.toJSON().zuschuss).to.eql(0); - expect(preise.toJSON().erwarteteBesucher).to.eql(0); + expect(preise).to.eql(freierEintritt); + expect(preise.zuschuss).to.eql(0); + expect(preise.erwarteteBesucher).to.eql(0); }); it("fuer freien Eintritt mit gesetzten Rabatten", () => { const alterDatensatz = { frei: true, regulaer: 0, rabattErmaessigt: 2, rabattMitglied: 5, erwarteteBesucher: 0 }; const preise = new Eintrittspreise(alterDatensatz); - expect(preise.toJSON()).to.eql(freierEintritt); + expect(preise).to.eql(freierEintritt); }); it("nimmt alte Preise in neues custom Profil", () => { const alterDatensatz = { regulaer: 10, rabattErmaessigt: 2, rabattMitglied: 5, erwarteteBesucher: 50, zuschuss: 10 }; const preise = new Eintrittspreise(alterDatensatz); - expect(preise.toJSON().preisprofil).to.eql({ name: "Individuell (Alt)", rabattErmaessigt: 2, rabattMitglied: 5, regulaer: 10 }); - expect(preise.toJSON().zuschuss).to.eql(10); - expect(preise.toJSON().erwarteteBesucher).to.eql(50); + expect(preise.preisprofil).to.eql({ name: "Individuell (Alt)", rabattErmaessigt: 2, rabattMitglied: 5, regulaer: 10 }); + expect(preise.zuschuss).to.eql(10); + expect(preise.erwarteteBesucher).to.eql(50); }); }); @@ -46,13 +46,13 @@ describe("Eintrittspreise", () => { it("fuer freien Eintritt", () => { const alterDatensatz = { frei: true }; const preise = new Eintrittspreise(alterDatensatz); - expect(preise.toJSON()).to.eql(freierEintritt); + expect(preise).to.eql(freierEintritt); }); it("nimmt alte Preise in neues custom Profil", () => { const p = { preisprofil: { name: "18,00", regulaer: 18, rabattErmaessigt: 2, rabattMitglied: 5 } }; const preise = new Eintrittspreise(p); - expect(preise.toJSON().preisprofil).to.eql({ name: "18,00", regulaer: 18, rabattErmaessigt: 2, rabattMitglied: 5 }); + expect(preise.preisprofil).to.eql({ name: "18,00", regulaer: 18, rabattErmaessigt: 2, rabattMitglied: 5 }); }); }); }); diff --git a/application/shared/test/optionen/optionValue_kontakte.test.ts b/application/shared/test/optionen/optionValue_kontakte.test.ts new file mode 100644 index 00000000..9fe9deed --- /dev/null +++ b/application/shared/test/optionen/optionValue_kontakte.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it } from "vitest"; +import OptionValues from "../../optionen/optionValues.js"; +import Kontakt from "../../veranstaltung/kontakt.js"; + +describe("Agenturen in Optionen", () => { + it("is properly initialized", () => { + const emptyOptionen = new OptionValues(); + expect(emptyOptionen).to.eql({ + agenturen: [], + artists: [], + backlineJazzclub: [], + backlineRockshop: [], + genres: [], + hotelpreise: [], + hotels: [], + id: "instance", + kooperationen: [], + preisKlavierstimmer: 125, + preisprofile: [], + typenPlus: [], + }); + }); + + describe("addOrUpdateKontakt", () => { + it("adds Agentur if name is set", () => { + const emptyOptionen = new OptionValues(); + const kontakt = new Kontakt({ name: "Name" }); + emptyOptionen.addOrUpdateKontakt("agenturen", kontakt); + expect(emptyOptionen.agenturen).to.eql([{ adresse: "", ansprechpartner: "", email: "", name: "Name", telefon: "" }]); + }); + + it("changes existing Agentur if name is set", () => { + const optionenWithAgentur = new OptionValues({ agenturen: [new Kontakt({ name: "Name" })] }); + expect(optionenWithAgentur.agenturen).toHaveLength(1); + const kontakt = new Kontakt({ name: "Name", telefon: "123" }); + optionenWithAgentur.addOrUpdateKontakt("agenturen", kontakt); + expect(optionenWithAgentur.agenturen).to.eql([{ adresse: "", ansprechpartner: "", email: "", name: "Name", telefon: "123" }]); + }); + + it("adds second Agentur if name is not yet in", () => { + const optionenWithAgentur = new OptionValues({ agenturen: [new Kontakt({ name: "Name" })] }); + expect(optionenWithAgentur.agenturen).toHaveLength(1); + const kontakt = new Kontakt({ name: "Name2" }); + optionenWithAgentur.addOrUpdateKontakt("agenturen", kontakt); + expect(optionenWithAgentur.agenturen).to.eql([ + { adresse: "", ansprechpartner: "", email: "", name: "Name", telefon: "" }, + { adresse: "", ansprechpartner: "", email: "", name: "Name2", telefon: "" }, + ]); + }); + + it("does not add an Agentur without name", () => { + const emptyOptionen = new OptionValues(); + const kontakt = new Kontakt({ name: "" }); + emptyOptionen.addOrUpdateKontakt("agenturen", kontakt); + expect(emptyOptionen.agenturen).to.be.empty; + }); + + it('does not add an Agentur if "[temporär]"', () => { + const emptyOptionen = new OptionValues(); + const kontakt = new Kontakt({ name: "Name" }); + emptyOptionen.addOrUpdateKontakt("agenturen", kontakt, "[temporär]"); + expect(emptyOptionen.agenturen).to.be.empty; + }); + }); +}); diff --git a/application/shared/user/accessrights.ts b/application/shared/user/accessrights.ts index 4ba995bc..48563590 100644 --- a/application/shared/user/accessrights.ts +++ b/application/shared/user/accessrights.ts @@ -1,42 +1,42 @@ import User, { ABENDKASSE, BOOKING, ORGA, SUPERUSERS } from "./user.js"; export default class Accessrights { - private user?: User; + private user: User; constructor(user?: User) { - this.user = user; + this.user = user ?? new User({}); } - get member(): User | undefined { + get member(): User { return this.user; } get memberId(): string { - return this.member?.id || ""; + return this.member.id || ""; } - get gruppen(): string[] { - return [this.member?.gruppen ?? ""]; + get gruppen(): typeof SUPERUSERS | typeof ORGA | typeof BOOKING | typeof ABENDKASSE | "" { + return this.member.gruppen ?? ""; } get rechte(): string[] { - return this.member?.rechte || []; + return this.member.rechte; } get isSuperuser(): boolean { - return this.gruppen.includes(SUPERUSERS); + return this.gruppen === SUPERUSERS; } get isBookingTeam(): boolean { - return this.isSuperuser || this.gruppen.includes(BOOKING); + return this.isSuperuser || this.gruppen === BOOKING; } get isOrgaTeam(): boolean { - return this.isBookingTeam || this.gruppen.includes(ORGA); + return this.isBookingTeam || this.gruppen === ORGA; } get isAbendkasse(): boolean { - return this.isOrgaTeam || this.gruppen.includes(ABENDKASSE); + return this.isOrgaTeam || this.gruppen === ABENDKASSE; } get darfKasseFreigeben(): boolean { diff --git a/application/shared/user/user.ts b/application/shared/user/user.ts index e18f1516..8f505125 100644 --- a/application/shared/user/user.ts +++ b/application/shared/user/user.ts @@ -1,5 +1,6 @@ import Accessrights from "./accessrights.js"; import isNil from "lodash/isNil.js"; +import cloneDeep from "lodash/cloneDeep.js"; export type KannSection = "Kasse" | "Ton" | "Licht" | "Master" | "Ersthelfer"; @@ -25,7 +26,6 @@ export default class User { mailinglisten: string[] = []; wantsEmailReminders?: boolean; password?: string; // take care to not persist! - accessrightsTransient?: Accessrights; // transient kannKasse?: boolean; kannTon?: boolean; @@ -34,7 +34,6 @@ export default class User { kannErsthelfer?: boolean; constructor(object: Partial) { - delete this.accessrightsTransient; this.id = object.id!; Object.assign(this, object, { kassenfreigabe: object.kassenfreigabe || object.rechte?.includes("kassenfreigabe"), @@ -42,16 +41,8 @@ export default class User { }); } - /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ - toJSON(): any { - const result = Object.assign({}, this); - delete result.accessrightsTransient; - return result; - } - - /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ - toJSONWithoutPass(): any { - const result = this.toJSON(); + get withoutPass() { + const result = cloneDeep(this); delete result.hashedPassword; delete result.salt; return result; @@ -105,10 +96,7 @@ export default class User { } get accessrights(): Accessrights { - if (!this.accessrightsTransient) { - this.accessrightsTransient = new Accessrights(this); - } - return this.accessrightsTransient; + return new Accessrights(this); } get asUserAsOption() { diff --git a/application/shared/veranstaltung/artist.ts b/application/shared/veranstaltung/artist.ts index 10c4ac65..260132af 100644 --- a/application/shared/veranstaltung/artist.ts +++ b/application/shared/veranstaltung/artist.ts @@ -15,10 +15,6 @@ export default class Artist { getInForMasterDate?: Date; bandTransport?: BandTransport; - toJSON(): object { - return Object.assign({}, this); - } - constructor(object?: Omit & { getInForMasterDate: string | Date }) { if (object && keys(object).length !== 0) { const getIn = Misc.stringOrDateToDate(object.getInForMasterDate); diff --git a/application/shared/veranstaltung/kontakt.ts b/application/shared/veranstaltung/kontakt.ts index 3de338e6..8a315362 100644 --- a/application/shared/veranstaltung/kontakt.ts +++ b/application/shared/veranstaltung/kontakt.ts @@ -8,11 +8,6 @@ export default class Kontakt { name = ""; telefon = ""; - /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ - toJSON(): any { - return Object.assign({}, this); - } - constructor(object?: RecursivePartial) { if (object && keys(object).length) { Object.assign(this, object); diff --git a/application/shared/veranstaltung/kopf.ts b/application/shared/veranstaltung/kopf.ts index 2d55915f..13a2fcb1 100644 --- a/application/shared/veranstaltung/kopf.ts +++ b/application/shared/veranstaltung/kopf.ts @@ -20,11 +20,6 @@ export default class Kopf { kannAufHomePage = false; kannInSocialMedia = false; - /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ - toJSON(): any { - return Object.assign({}, this); - } - constructor(object?: RecursivePartial) { if (object && keys(object).length) { Object.assign(this, object); diff --git a/application/shared/veranstaltung/kosten.ts b/application/shared/veranstaltung/kosten.ts index f1619178..1265cd3c 100644 --- a/application/shared/veranstaltung/kosten.ts +++ b/application/shared/veranstaltung/kosten.ts @@ -50,11 +50,6 @@ export default class Kosten { tontechniker = 0; lichttechniker = 0; - /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ - toJSON(): any { - return Object.assign({}, this); - } - constructor(object?: RecursivePartial) { if (object && keys(object).length) { Object.assign(this, object); diff --git a/application/shared/veranstaltung/presse.ts b/application/shared/veranstaltung/presse.ts index bb6c93eb..9848888a 100644 --- a/application/shared/veranstaltung/presse.ts +++ b/application/shared/veranstaltung/presse.ts @@ -9,11 +9,6 @@ export default class Presse { checked = false; jazzclubURL = ""; - /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ - toJSON(): any { - return Object.assign({}, this); - } - constructor(object?: RecursivePartial) { if (object && keys(object).length) { Object.assign(this, object, { diff --git a/application/shared/veranstaltung/staff.ts b/application/shared/veranstaltung/staff.ts index e6e9ce70..8e027eed 100644 --- a/application/shared/veranstaltung/staff.ts +++ b/application/shared/veranstaltung/staff.ts @@ -22,10 +22,6 @@ export default class Staff { merchandiseNotNeeded = true; ersthelferNotNeeded = false; - toJSON(): object { - return Object.assign({}, this); - } - constructor(object?: RecursivePartial) { if (object && keys(object).length) { Object.assign(this, object, { diff --git a/application/shared/veranstaltung/technik.ts b/application/shared/veranstaltung/technik.ts index 7c23cb2e..e6778a00 100644 --- a/application/shared/veranstaltung/technik.ts +++ b/application/shared/veranstaltung/technik.ts @@ -10,11 +10,6 @@ export default class Technik { checked = false; fluegel = false; - /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ - toJSON(): any { - return Object.assign({}, this); - } - constructor(object?: RecursivePartial) { if (object && keys(object).length) { Object.assign(this, object, { diff --git a/application/shared/veranstaltung/veranstaltung.ts b/application/shared/veranstaltung/veranstaltung.ts index bc2be30f..c6c13bfc 100644 --- a/application/shared/veranstaltung/veranstaltung.ts +++ b/application/shared/veranstaltung/veranstaltung.ts @@ -13,7 +13,6 @@ import { colorVermietung } from "../optionen/optionValues.js"; import { RecursivePartial } from "../commons/advancedTypes.js"; import map from "lodash/map.js"; import tinycolor from "tinycolor2"; -import User from "../user/user.js"; export type MinimalVeranstaltung = Partial & { id: string; startDate: Date; kopf: Kopf; url: string; ghost: boolean }; export default abstract class Veranstaltung { @@ -29,7 +28,7 @@ export default abstract class Veranstaltung { staff = new Staff(); technik = new Technik(); brauchtPresse = true; - booker?: User[] = []; + booker?: string[] = []; constructor( object?: RecursivePartial & { startDate: string | Date; endDate: string | Date }>, @@ -56,8 +55,6 @@ export default abstract class Veranstaltung { } } - abstract toJSON(): object; - abstract get isVermietung(): boolean; get fullyQualifiedUrl(): string { diff --git a/application/shared/vermietung/angebot.ts b/application/shared/vermietung/angebot.ts index d47433c3..171c5407 100644 --- a/application/shared/vermietung/angebot.ts +++ b/application/shared/vermietung/angebot.ts @@ -31,11 +31,6 @@ export default class Angebot { freigabeAm?: Date; rechnungsnummer?: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - toJSON(): any { - return Object.assign({}, this); - } - constructor(object?: Omit & { freigabeAm?: Date | string }) { if (object && keys(object).length) { Object.assign(this, object, { diff --git a/application/shared/vermietung/vermietung.ts b/application/shared/vermietung/vermietung.ts index 885c7286..d0ed74b7 100644 --- a/application/shared/vermietung/vermietung.ts +++ b/application/shared/vermietung/vermietung.ts @@ -13,21 +13,6 @@ export default class Vermietung extends Veranstaltung { angebot = new Angebot(); vertragspartner = new Kontakt(); - toJSON(): object { - const result = {}; - Object.assign(result, this, { - artist: this.artist.toJSON(), - kopf: this.kopf.toJSON(), - kosten: this.kosten.toJSON(), - presse: this.presse.toJSON(), - staff: this.staff.toJSON(), - technik: this.technik.toJSON(), - angebot: this.angebot.toJSON(), - vertragspartner: this.vertragspartner.toJSON(), - }); - return result; - } - constructor(object?: RecursivePartial & { startDate: string | Date; endDate: string | Date }>) { super(object ?? { brauchtPresse: false }); if (object) { diff --git a/application/vue/src/app/JazzclubApp.tsx b/application/vue/src/app/JazzclubApp.tsx index a3651a60..a97a9426 100644 --- a/application/vue/src/app/JazzclubApp.tsx +++ b/application/vue/src/app/JazzclubApp.tsx @@ -93,6 +93,8 @@ function JazzclubApp() { }} locale={locale_de} theme={{ + cssVar: true, + hashed: false, token: { colorPrimary: "#337ab7", colorTextDisabled, diff --git a/application/vue/src/commons/auth.tsx b/application/vue/src/commons/auth.tsx index b197662e..7def320a 100755 --- a/application/vue/src/commons/auth.tsx +++ b/application/vue/src/commons/auth.tsx @@ -3,7 +3,7 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { LoginState } from "./authConsts"; import { useLocation, useNavigate } from "react-router"; -import { loginPost, logoutManually, refreshTokenPost } from "@/rest/loader.ts"; +import { loginPost, logoutManually, refreshTokenPost } from "@/rest/authenticationRequests"; export interface IUseProvideAuth { /** diff --git a/application/vue/src/components/content/menu/Preferences.tsx b/application/vue/src/components/content/menu/Preferences.tsx index 1be5b813..68e2d41a 100644 --- a/application/vue/src/components/content/menu/Preferences.tsx +++ b/application/vue/src/components/content/menu/Preferences.tsx @@ -5,6 +5,18 @@ import { CheckboxGroupProps } from "antd/es/checkbox"; import useJazzPrefs, { JazzPrefs } from "@/app/useJazzPrefs.ts"; import { JazzModal } from "@/widgets/JazzModal.tsx"; +const optionsHellDunkel: CheckboxGroupProps["options"] = [ + { label: "Hell", value: "bright" }, + { label: "Dunkel", value: "dark" }, + { label: "Automatisch", value: "auto" }, +]; + +const optionsKompaktNormal: CheckboxGroupProps["options"] = [ + { label: "Normal", value: "normal" }, + { label: "Kompakt", value: "compact" }, + { label: "Automatisch", value: "auto" }, +]; + export default function Preferences({ isOpen, setIsOpen }: { readonly isOpen: boolean; readonly setIsOpen: (x: boolean) => void }) { const [form] = Form.useForm(); const { setPreferences, getPreferences } = useJazzPrefs(); @@ -14,18 +26,6 @@ export default function Preferences({ isOpen, setIsOpen }: { readonly isOpen: bo form.setFieldsValue(prefs); }, [form, getPreferences]); - const optionsHellDunkel: CheckboxGroupProps["options"] = [ - { label: "Hell", value: "bright" }, - { label: "Dunkel", value: "dark" }, - { label: "Automatisch", value: "auto" }, - ]; - - const optionsKompaktNormal: CheckboxGroupProps["options"] = [ - { label: "Normal", value: "normal" }, - { label: "Kompakt", value: "compact" }, - { label: "Automatisch", value: "auto" }, - ]; - function saveForm() { setPreferences(form.getFieldsValue(true)); } diff --git a/application/vue/src/components/content/useJazzContext.ts b/application/vue/src/components/content/useJazzContext.ts index 3aa94409..742b194d 100644 --- a/application/vue/src/components/content/useJazzContext.ts +++ b/application/vue/src/components/content/useJazzContext.ts @@ -1,4 +1,4 @@ -import { createContext, useContext, useMemo, useState } from "react"; +import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"; import User from "jc-shared/user/user.ts"; import { useQueries } from "@tanstack/react-query"; import { allUsers, currentUser, konzerteForToday, optionen as optionenLoader, orte as orteLoader, wikisubdirs } from "@/rest/loader.ts"; @@ -21,8 +21,8 @@ const emptyContext: SharedGlobals = { todayKonzerte: [], showSuccess: noop, showError: noop, - filter: {}, - setFilter: noop, + teamFilter: {}, + setTeamFilter: noop, isDirty: false, setIsDirty: noop, setMemoizedId: noop, @@ -39,8 +39,8 @@ type SharedGlobals = { todayKonzerte: Konzert[]; showSuccess: ({ text, title, duration }: { duration?: number; text?: React.ReactNode; title?: string }) => void; showError: ({ text, title, closeCallback }: { text?: string; title?: string; closeCallback?: () => void }) => void; - filter: TeamFilterObject; - setFilter: (filter: TeamFilterObject) => void; + teamFilter: TeamFilterObject; + setTeamFilter: (filter: TeamFilterObject) => void; isDirty: boolean; setIsDirty: (a: boolean) => void; memoizedId?: string; @@ -59,6 +59,15 @@ export function useCreateJazzContext(auth: IUseProvideAuth): SharedGlobals { const refetchInterval = 30 * 60 * 1000; // 30 minutes const [filter, setFilter] = useState({}); + useEffect(() => { + setFilter(JSON.parse(localStorage.getItem("teamFilter") ?? "{}")); + }, []); + + const setTeamFilter = useCallback((filter: TeamFilterObject) => { + localStorage.setItem("teamFilter", JSON.stringify(filter)); + setFilter(filter); + }, []); + const [isDirty, setIsDirty] = useState(false); const [memoizedId, setMemoizedId] = useState(); @@ -66,8 +75,8 @@ export function useCreateJazzContext(auth: IUseProvideAuth): SharedGlobals { SharedGlobals, | "showSuccess" | "showError" - | "filter" - | "setFilter" + | "teamFilter" + | "setTeamFilter" | "isDirty" | "setIsDirty" | "memoizedId" @@ -143,8 +152,8 @@ export function useCreateJazzContext(auth: IUseProvideAuth): SharedGlobals { ...exposedContext, showSuccess, showError, - filter, - setFilter, + teamFilter: filter, + setTeamFilter, isDirty, setIsDirty, memoizedId, diff --git a/application/vue/src/components/konzert/KonzertComp.tsx b/application/vue/src/components/konzert/KonzertComp.tsx index 826634e8..0ee16153 100644 --- a/application/vue/src/components/konzert/KonzertComp.tsx +++ b/application/vue/src/components/konzert/KonzertComp.tsx @@ -56,10 +56,10 @@ export default function KonzertComp() { } const untypedKonzert = vals as { agenturauswahl?: string; hotelauswahl?: string; hotelpreiseAlsDefault?: boolean }; - optionen.addOrUpdateKontakt("agenturen", konz.agentur, untypedKonzert.agenturauswahl ?? ""); + optionen.addOrUpdateKontakt("agenturen", konz.agentur, untypedKonzert.agenturauswahl); delete untypedKonzert.agenturauswahl; if (konz.artist.brauchtHotel) { - optionen.addOrUpdateKontakt("hotels", konz.hotel, untypedKonzert.hotelauswahl ?? ""); + optionen.addOrUpdateKontakt("hotels", konz.hotel, untypedKonzert.hotelauswahl); delete untypedKonzert.hotelauswahl; if (untypedKonzert.hotelpreiseAlsDefault) { optionen.updateHotelpreise(konz.hotel, konz.unterkunft.zimmerPreise); diff --git a/application/vue/src/components/konzert/allgemeines/EventCard.tsx b/application/vue/src/components/konzert/allgemeines/EventCard.tsx index 44dcf1e7..365ae018 100644 --- a/application/vue/src/components/konzert/allgemeines/EventCard.tsx +++ b/application/vue/src/components/konzert/allgemeines/EventCard.tsx @@ -39,8 +39,7 @@ export default function EventCard() { } }, [currentUser, form, id, url]); - // eslint-disable-next-line lodash/prop-shorthand - const bookersOnly = useMemo(() => filter(allUsers, (u) => u.accessrights.isBookingTeam), [allUsers]); + const bookersOnly = useMemo(() => filter(allUsers, "accessrights.isBookingTeam"), [allUsers]); const bookersAsOptions = useMemo(() => map(bookersOnly, "asUserAsOption"), [bookersOnly]); const isBookingTeam = useMemo(() => currentUser.accessrights.isBookingTeam, [currentUser.accessrights.isBookingTeam]); diff --git a/application/vue/src/components/konzert/preview/GaesteInPreview.tsx b/application/vue/src/components/konzert/preview/GaesteInPreview.tsx index 3ae41367..1086c345 100644 --- a/application/vue/src/components/konzert/preview/GaesteInPreview.tsx +++ b/application/vue/src/components/konzert/preview/GaesteInPreview.tsx @@ -9,6 +9,7 @@ import { colorsAndIconsForSections } from "@/widgets/buttonsAndIcons/colorsIcons import { useJazzContext } from "@/components/content/useJazzContext.ts"; import ButtonWithIconAndLink from "@/widgets/buttonsAndIcons/ButtonWithIconAndLink.tsx"; import { JazzRow } from "@/widgets/JazzRow"; +import sortBy from "lodash/sortBy"; function AddOrRemoveGastButton({ konzert, @@ -42,7 +43,7 @@ function AddOrRemoveGastButton({ } function GastResList({ source, art, konzert }: { readonly konzert: Konzert; readonly source: NameWithNumber[]; readonly art: GastArt }) { - const dataSource = useMemo(() => source.sort((a, b) => a.name.localeCompare(b.name)), [source]); + const dataSource = useMemo(() => sortBy(source, "name"), [source]); return ( ({ ...list })).sort((a, b) => a.name.localeCompare(b.name)); - } - toJSON(): object { - return cloneDeep(this); - } -} - -function MailingListsInternal({ users }: { readonly users: User[] }) { - const usersAsOptions = useMemo(() => map(users, "asUserAsOption"), [users]); +function MailingListsInternal() { + const { allUsers } = useJazzContext(); + const usersAsOptions = useMemo(() => map(allUsers, "asUserAsOption"), [allUsers]); const columnDescriptions: Columns[] = [ { dataIndex: "name", title: "Name", type: "text", width: "150px", required: true, uniqueValues: true }, @@ -37,7 +26,7 @@ function MailingListsInternal({ users }: { readonly users: User[] }) { columnDescriptions={columnDescriptions} - name="allLists" + name="lists" newRowFactory={(val) => Object.assign({ users: [] }, val)} usersWithKann={usersAsOptions} /> @@ -47,12 +36,7 @@ function MailingListsInternal({ users }: { readonly users: User[] }) { } export default function MailingLists() { - const { data, refetch } = useQuery({ queryKey: ["users"], queryFn: () => allUsers() }); - - const [mailingLists, setMailingLists] = useState(new MailingListsWrapper([])); - useEffect(() => { - setMailingLists(new MailingListsWrapper(new Users(data ?? []).mailinglisten)); - }, [data]); + const { data: mailingLists, refetch } = useQuery({ queryKey: ["mailinglists"], queryFn: allMailinglists }); const mutateLists = useJazzMutation({ saveFunction: saveMailinglists, @@ -60,13 +44,13 @@ export default function MailingLists() { successMessage: "Die Listen wurden gespeichert", }); - function saveForm(vals: MailingListsWrapper) { - mutateLists.mutate(vals.allLists); + function saveForm(vals: { lists: Mailingliste[] }) { + mutateLists.mutate(vals); } return ( - + ); } diff --git a/application/vue/src/components/options/TerminePage.tsx b/application/vue/src/components/options/TerminePage.tsx index 33901880..ecb397b3 100644 --- a/application/vue/src/components/options/TerminePage.tsx +++ b/application/vue/src/components/options/TerminePage.tsx @@ -6,7 +6,6 @@ import { Col } from "antd"; import Termin, { TerminType } from "jc-shared/optionen/termin"; import EditableTable from "@/widgets/EditableTable/EditableTable.tsx"; import { Columns } from "@/widgets/EditableTable/types.ts"; -import cloneDeep from "lodash/cloneDeep"; import JazzFormAndHeader from "@/components/content/JazzFormAndHeader.tsx"; import { useJazzMutation } from "@/commons/useJazzMutation.ts"; import map from "lodash/map"; @@ -23,9 +22,6 @@ class TermineWrapper { typ: termin.typ, })); } - toJSON(): object { - return cloneDeep(this); - } } function TerminePageInternal() { diff --git a/application/vue/src/components/options/imageoverview/ImageOverview.tsx b/application/vue/src/components/options/imageoverview/ImageOverview.tsx index 2b9462b3..259aff60 100644 --- a/application/vue/src/components/options/imageoverview/ImageOverview.tsx +++ b/application/vue/src/components/options/imageoverview/ImageOverview.tsx @@ -12,13 +12,11 @@ import { useJazzMutation } from "@/commons/useJazzMutation.ts"; import filter from "lodash/filter"; import { JazzRow } from "@/widgets/JazzRow.tsx"; +type ImageOverviewForm = { with: ImageOverviewRow[]; notFound: ImageOverviewRow[]; unused: ImageOverviewRow[] }; + export default function ImageOverview() { useDirtyBlocker(false); - const [form] = Form.useForm<{ - with: ImageOverviewRow[]; - notFound: ImageOverviewRow[]; - unused: ImageOverviewRow[]; - }>(); + const [form] = Form.useForm(); const mutateImages = useJazzMutation({ saveFunction: saveImagenames, @@ -38,10 +36,8 @@ export default function ImageOverview() { }, [form, sections]); function saveForm() { - const formValues = form.getFieldsValue(true); - const changedRows = filter(formValues.with, (v: ImageOverviewRow) => v.newname !== v.image) - .concat(filter(formValues.notFound, (v: ImageOverviewRow) => v.newname !== v.image)) - .concat(filter(formValues.unused, (v: ImageOverviewRow) => v.newname !== v.image)); + const sects: ImageOverviewForm = form.getFieldsValue(true); + const changedRows = filter(sects.with.concat(sects.notFound).concat(sects.unused), (v) => v.newname !== v.image); form.validateFields().then(async () => { mutateImages.mutate(changedRows); }); diff --git a/application/vue/src/components/options/imageoverview/useCreateImagenamesSections.ts b/application/vue/src/components/options/imageoverview/useCreateImagenamesSections.ts index 5b16cde1..6562dc87 100644 --- a/application/vue/src/components/options/imageoverview/useCreateImagenamesSections.ts +++ b/application/vue/src/components/options/imageoverview/useCreateImagenamesSections.ts @@ -1,5 +1,5 @@ import { useCallback, useMemo } from "react"; -import Konzert, { ImageOverviewVeranstaltung } from "jc-shared/konzert/konzert.ts"; +import Konzert, { ImageOverviewRow, ImageOverviewVeranstaltung } from "jc-shared/konzert/konzert.ts"; import uniq from "lodash/uniq"; import flatMap from "lodash/flatMap"; import intersection from "lodash/intersection"; @@ -8,6 +8,7 @@ import { useQueries } from "@tanstack/react-query"; import { imagenames as imagenamesQuery, konzerteForTeam } from "@/rest/loader.ts"; import map from "lodash/map"; import filter from "lodash/filter"; +import sortBy from "lodash/sortBy"; function suitableForImageOverview(veranstaltung: Konzert): ImageOverviewVeranstaltung { return { @@ -39,14 +40,14 @@ export function useCreateImagenamesSections() { ); const toImageOverviewRow = useCallback( - (im: string) => ({ image: im, newname: im, veranstaltungen: elementsWithImage(im) }), + (im: string) => ({ image: im, newname: im, veranstaltungen: elementsWithImage(im) }) as ImageOverviewRow, [elementsWithImage], ); return useMemo(() => { const convertString = (a: string): string => a.replace(/\s/g, "_"); - const imagenamesOfVeranstaltungen = uniq(flatMap(veranstaltungen, "images")).sort() as string[]; + const imagenamesOfVeranstaltungen = sortBy(uniq(flatMap(veranstaltungen, "images") as string[])); return { with: map(intersection(imagenames, imagenamesOfVeranstaltungen), toImageOverviewRow), diff --git a/application/vue/src/components/programmheft/Programmheft.tsx b/application/vue/src/components/programmheft/Programmheft.tsx index 19106db9..c1457474 100644 --- a/application/vue/src/components/programmheft/Programmheft.tsx +++ b/application/vue/src/components/programmheft/Programmheft.tsx @@ -23,6 +23,7 @@ import useFormInstance from "antd/es/form/hooks/useFormInstance"; import { useJazzMutation } from "@/commons/useJazzMutation.ts"; import map from "lodash/map"; import invokeMap from "lodash/invokeMap"; +import sortBy from "lodash/sortBy"; function ProgrammheftInternal({ start }: { readonly start: DatumUhrzeit }) { const form = useFormInstance(); @@ -38,9 +39,8 @@ function ProgrammheftInternal({ start }: { readonly start: DatumUhrzeit }) { const moveEvents = useCallback( (offset: number) => { function moveEventsBy(events: Event[], options: AdditionOptions) { - const result = invokeMap(events, "cloneAndMoveBy", options); - result.sort((a, b) => a.start.localeCompare(b.start)); - return result; + const result: Event[] = invokeMap(events, "cloneAndMoveBy", options); + return sortBy(result, "start"); } const newEvents = moveEventsBy(events, { tage: offset }); diff --git a/application/vue/src/components/team/ExcelMultiExportButton.tsx b/application/vue/src/components/team/ExcelMultiExportButton.tsx index 4a9aa038..86ab3480 100644 --- a/application/vue/src/components/team/ExcelMultiExportButton.tsx +++ b/application/vue/src/components/team/ExcelMultiExportButton.tsx @@ -26,7 +26,7 @@ function SelectRangeForExcelModal({ readonly alle: { startDate: Date }[]; }) { const [form] = useForm(); - const { optionen, filter: teamFilter } = useJazzContext(); + const { optionen, teamFilter } = useJazzContext(); const [first, setFirst] = useState(dayjs()); const [last, setLast] = useState(dayjs()); diff --git a/application/vue/src/components/team/Info.tsx b/application/vue/src/components/team/Info.tsx index 8ad467d2..473f42ff 100644 --- a/application/vue/src/components/team/Info.tsx +++ b/application/vue/src/components/team/Info.tsx @@ -91,7 +91,7 @@ function Uebersicht({ veranstaltungen }: { readonly veranstaltungen: Veranstaltu } export default function Info() { - const { filter: contextFilter } = useJazzContext(); + const { teamFilter } = useJazzContext(); const { monatJahr } = useParams(); // als yymm const [search, setSearch] = useSearchParams(); const [activePage, setActivePage] = useState("pressetexte"); @@ -109,7 +109,7 @@ export default function Info() { queryFn: () => konzerteBetweenYYYYMM(start.yyyyMM, end.yyyyMM), }); - const veranstaltungen = useMemo(() => filter(data, applyTeamFilter(contextFilter)), [data, contextFilter]); + const veranstaltungen = useMemo(() => filter(data, applyTeamFilter(teamFilter)), [data, teamFilter]); useEffect( () => { diff --git a/application/vue/src/components/team/Team.tsx b/application/vue/src/components/team/Team.tsx index a97fdcf5..197ba78e 100644 --- a/application/vue/src/components/team/Team.tsx +++ b/application/vue/src/components/team/Team.tsx @@ -6,5 +6,5 @@ export default function Team() { useDirtyBlocker(false); document.title = "Team"; - return ; + return ; } diff --git a/application/vue/src/components/team/TeamBlock/AdminContent.tsx b/application/vue/src/components/team/TeamBlock/AdminContent.tsx index 127d0a90..9b631fe9 100644 --- a/application/vue/src/components/team/TeamBlock/AdminContent.tsx +++ b/application/vue/src/components/team/TeamBlock/AdminContent.tsx @@ -15,6 +15,7 @@ import { useJazzMutation } from "@/commons/useJazzMutation.ts"; import { useJazzContext } from "@/components/content/useJazzContext.ts"; import { useInView } from "react-intersection-observer"; import useFormInstance from "antd/es/form/hooks/useFormInstance"; +import cloneDeep from "lodash/cloneDeep"; type ButtonsProps = { readonly dirty: boolean; @@ -68,10 +69,10 @@ export default function AdminContent({ veranstaltung: veranVermiet }: { readonly }, [veranstaltung]); const setFormValue = useCallback(() => { - const deepCopy = veranstaltung.toJSON(); + const deepCopy = cloneDeep(veranstaltung); form.resetFields(); form.setFieldsValue(deepCopy); - setInitialValue(veranstaltung.toJSON()); + setInitialValue(cloneDeep(veranstaltung)); setDirty(false); }, [form, veranstaltung]); diff --git a/application/vue/src/components/team/TeamFilter/TeamFilter.tsx b/application/vue/src/components/team/TeamFilter/TeamFilter.tsx index 456d02d0..3f3a7e71 100644 --- a/application/vue/src/components/team/TeamFilter/TeamFilter.tsx +++ b/application/vue/src/components/team/TeamFilter/TeamFilter.tsx @@ -1,10 +1,9 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"; -import { Form, Tag } from "antd"; +import { Form, FormInstance, Tag } from "antd"; import ButtonWithIcon from "@/widgets/buttonsAndIcons/ButtonWithIcon.tsx"; import { TeamFilterObject } from "./applyTeamFilter.ts"; import { useJazzContext } from "@/components/content/useJazzContext.ts"; import isNil from "lodash/isNil"; -import { withoutNullOrUndefinedStrippedBy } from "jc-shared/commons/comparingAndTransforming.ts"; import isEmpty from "lodash/isEmpty"; import { NamePath } from "rc-field-form/es/interface"; import { TeamFilterEdit } from "@/components/team/TeamFilter/TeamFilterEdit.tsx"; @@ -13,74 +12,74 @@ import isBoolean from "lodash/isBoolean"; import find from "lodash/find"; import map from "lodash/map"; import filter from "lodash/filter"; +import User from "jc-shared/user/user.ts"; type LabelColorProperty = { readonly label: string; readonly color: boolean | string; readonly prop?: NamePath; + readonly value?: string; }; +function HeaderTag({ label, value, color, prop, form }: LabelColorProperty & { readonly form: FormInstance }) { + const { setTeamFilter } = useJazzContext(); + + const closePropTag = useCallback(() => { + const values = filter(form.getFieldValue(prop), (val: string) => val !== (value ?? label)); + form.setFieldValue(prop, values); + setTeamFilter(form.getFieldsValue(true)); + }, [form, label, prop, setTeamFilter, value]); + + const closeBooleanTagForProp = useCallback(() => { + if (prop) { + form.setFieldValue(prop, undefined); + setTeamFilter(form.getFieldsValue(true)); + } + }, [form, prop, setTeamFilter]); + + return isBoolean(color) ? ( + + {label} + + ) : ( + + {label} + + ); +} + export default function TeamFilter() { const [open, setOpen] = useState(false); const [form] = Form.useForm(); - const { filter: filterObj, setFilter, optionen } = useJazzContext(); + const { teamFilter, setTeamFilter, optionen, allUsers } = useJazzContext(); useEffect(() => { - form.setFieldsValue(filterObj); - }, [filterObj, form]); + form.setFieldsValue(teamFilter); + }, [form, teamFilter]); const eventTypTag = useCallback( (typ: string) => { - const result = find(optionen.typenPlus, ["name", typ]); - if (result) { - return { label: result.name, color: result.color }; - } - return undefined; + const result = find(optionen.typenPlus, ["name", typ]) ?? { name: "", color: "" }; + return { label: result.name, color: result.color, prop: ["kopf", "eventTyp"] }; }, [optionen.typenPlus], ); function headerTagsForFilters(labelsColors: LabelColorProperty[]) { - function HeaderTag({ label, color, prop }: LabelColorProperty) { - if (isBoolean(color)) { - return ( - { - if (prop) { - form.setFieldValue(prop, undefined); - setFilter(form.getFieldsValue(true)); - } - }} - > - {label} - - ); - } else { - return ( - { - const typen = filter(form.getFieldValue(["kopf", "eventTyp"]), (typ: string) => typ !== label); - form.setFieldValue(["kopf", "eventTyp"], typen); - setFilter(form.getFieldsValue(true)); - }} - > - {label} - - ); - } - } - return map(labelsColors, (tag) => ); + return map(labelsColors, (tag) => ( + + )); } - const teamFilter = withoutNullOrUndefinedStrippedBy(filterObj); + const createBookerTag = useCallback( + (booker: string) => { + const bookerUser = find(allUsers, { id: booker }) ?? new User({ name: booker }); + return { label: bookerUser.name, value: booker, color: "blue", prop: "booker" }; + }, + [allUsers], + ); const taggies = useMemo(() => { function pushIfSet(att: boolean | undefined, label: string, prop?: NamePath) { @@ -107,14 +106,24 @@ export default function TeamFilter() { pushIfSet(teamFilter.kopf?.fotografBestellen, "Fotograf einladen", ["kopf", "fotografBestellen"]); pushIfSet(teamFilter.technik?.checked, "Technik ist geklärt", ["technik", "checked"]); pushIfSet(teamFilter.technik?.fluegel, "Flügel stimmen", ["technik", "fluegel"]); - const eventTypTags = map(teamFilter.kopf?.eventTyp, (typ: string) => eventTypTag(typ)!); - return tags.concat(eventTypTags); - }, [eventTypTag, teamFilter]); + const eventTypTags = map(teamFilter.kopf?.eventTyp, eventTypTag); + const bookerTags = map(teamFilter.booker, createBookerTag); + return tags.concat(eventTypTags).concat(bookerTags); + }, [createBookerTag, eventTypTag, teamFilter]); const result = [ setOpen(true)} size="small" text="Filter..." type="default" /> - +
setTeamFilter(form.getFieldsValue(true))} + size="small" + style={{ display: "inline" }} + > + +
, ]; if (!isEmpty(taggies)) { @@ -124,7 +133,7 @@ export default function TeamFilter() { key="resetFilter" onClick={() => { reset(form); - setFilter(form.getFieldsValue(true)); + setTeamFilter(form.getFieldsValue(true)); }} size="small" text="Zurücksetzen" diff --git a/application/vue/src/components/team/TeamFilter/TeamFilterEdit.tsx b/application/vue/src/components/team/TeamFilter/TeamFilterEdit.tsx index e41a3dfe..8d9286cd 100644 --- a/application/vue/src/components/team/TeamFilter/TeamFilterEdit.tsx +++ b/application/vue/src/components/team/TeamFilter/TeamFilterEdit.tsx @@ -1,23 +1,23 @@ -import { Col, Collapse, CollapseProps, ConfigProvider, Form, FormInstance, Row, Space } from "antd"; +import { Col, Collapse, CollapseProps, ConfigProvider, Row, Space } from "antd"; import ButtonWithIcon from "@/widgets/buttonsAndIcons/ButtonWithIcon.tsx"; import ThreewayCheckbox from "@/widgets/ThreewayCheckbox.tsx"; -import React from "react"; +import React, { useMemo } from "react"; import { TeamFilterObject } from "@/components/team/TeamFilter/applyTeamFilter.ts"; import { useJazzContext } from "@/components/content/useJazzContext.ts"; import { EventTypeMultiSelect } from "@/widgets/EventTypeSelects/EventTypeMultiSelect.tsx"; import { reset } from "@/components/team/TeamFilter/resetTeamFilter.ts"; import { JazzModal } from "@/widgets/JazzModal.tsx"; +import MitarbeiterMultiSelect from "@/widgets/MitarbeiterMultiSelect.tsx"; +import filter from "lodash/filter"; +import map from "lodash/map"; +import useFormInstance from "antd/es/form/hooks/useFormInstance"; -export function TeamFilterEdit({ - form, - open, - setOpen, -}: { - readonly form: FormInstance; - readonly open: boolean; - readonly setOpen: (open: boolean) => void; -}) { - const { setFilter } = useJazzContext(); +export function TeamFilterEdit({ open, setOpen }: { readonly open: boolean; readonly setOpen: (open: boolean) => void }) { + const { allUsers, setTeamFilter } = useJazzContext(); + const form = useFormInstance(); + + const bookersOnly = useMemo(() => filter(allUsers, "accessrights.isBookingTeam"), [allUsers]); + const bookersAsOptions = useMemo(() => map(bookersOnly, "asUserAsOption"), [bookersOnly]); const items: CollapseProps["items"] = [ { @@ -31,6 +31,11 @@ export function TeamFilterEdit({ + + + + + @@ -116,7 +121,7 @@ export function TeamFilterEdit({ alwaysText onClick={() => { reset(form); - setFilter(form.getFieldsValue(true)); + setTeamFilter(form.getFieldsValue(true)); }} text="Zurücksetzen" type="default" @@ -125,7 +130,7 @@ export function TeamFilterEdit({ alwaysText onClick={() => { setOpen(false); - setFilter(form.getFieldsValue(true)); + setTeamFilter(form.getFieldsValue(true)); }} text="Schließen" /> @@ -133,11 +138,9 @@ export function TeamFilterEdit({ } open={open} > -
setFilter(form.getFieldsValue(true))} size="small"> - - - -
+ + + ); } diff --git a/application/vue/src/components/team/TeamFilter/applyTeamFilter.ts b/application/vue/src/components/team/TeamFilter/applyTeamFilter.ts index a1698ff1..b5a03595 100644 --- a/application/vue/src/components/team/TeamFilter/applyTeamFilter.ts +++ b/application/vue/src/components/team/TeamFilter/applyTeamFilter.ts @@ -3,6 +3,8 @@ import isEmpty from "lodash/isEmpty"; import isNil from "lodash/isNil"; import { withoutNullOrUndefinedStrippedBy } from "jc-shared/commons/comparingAndTransforming.ts"; import Konzert from "jc-shared/konzert/konzert.ts"; +import intersection from "lodash/intersection"; +import compact from "lodash/compact"; export type TeamFilterObject = { istKonzert?: boolean; @@ -20,6 +22,7 @@ export type TeamFilterObject = { kannInSocialMedia?: boolean; eventTyp?: string[]; }; + booker?: string[]; technik?: { checked?: boolean; fluegel?: boolean }; }; @@ -58,6 +61,14 @@ function filterKopf(ver: Veranstaltung, filterObj: TeamFilterObject) { return pred1 && pred2 && pred3 && pred4 && pred5 && pred6; } +function filterBooker(ver: Veranstaltung, filterObj: TeamFilterObject) { + const filter = filterObj?.booker; + if (isEmpty(filter)) { + return true; + } + return !isEmpty(compact(intersection(filter, ver.booker))); +} + function filterTechnik(ver: Veranstaltung, filterObj: TeamFilterObject) { const filter = filterObj.technik; if (isEmpty(filter)) { @@ -84,6 +95,13 @@ export default function applyTeamFilter(filterOri: TeamFilterObject) { return (ver: Veranstaltung) => { if (!ver || !filter) return true; const artFilter = isNil(filter.istKonzert) ? true : ver.isVermietung === !filter.istKonzert; - return artFilter && filterPresse(ver, filter) && filterKopf(ver, filter) && filterTechnik(ver, filter) && filterUnterkunft(ver, filter); + return ( + artFilter && + filterPresse(ver, filter) && + filterKopf(ver, filter) && + filterBooker(ver, filter) && + filterTechnik(ver, filter) && + filterUnterkunft(ver, filter) + ); }; } diff --git a/application/vue/src/components/team/TeamFilter/resetTeamFilter.ts b/application/vue/src/components/team/TeamFilter/resetTeamFilter.ts index 9a894ae5..0a50d676 100644 --- a/application/vue/src/components/team/TeamFilter/resetTeamFilter.ts +++ b/application/vue/src/components/team/TeamFilter/resetTeamFilter.ts @@ -15,5 +15,6 @@ export function reset(form: FormInstance) { eventTyp: undefined, }, technik: { checked: undefined, fluegel: undefined }, + booker: [], }); } diff --git a/application/vue/src/components/team/TeamUndVeranstaltungen.tsx b/application/vue/src/components/team/TeamUndVeranstaltungen.tsx index 1b622b09..89f147e7 100644 --- a/application/vue/src/components/team/TeamUndVeranstaltungen.tsx +++ b/application/vue/src/components/team/TeamUndVeranstaltungen.tsx @@ -10,9 +10,11 @@ import { TeamContext } from "@/components/team/TeamContext.ts"; import { useTeamVeranstaltungenCommons } from "@/components/team/useTeamVeranstaltungenCommons.ts"; import { useJazzContext } from "@/components/content/useJazzContext.ts"; import map from "lodash/map"; +import { useLocation } from "react-router"; -export function TeamUndVeranstaltungen({ periodsToShow }: { readonly periodsToShow: string[] }) { +export function TeamUndVeranstaltungen() { const { memoizedId } = useJazzContext(); + const { pathname } = useLocation(); useEffect(() => { setTimeout(() => { const element = document.getElementById(memoizedId ?? ""); @@ -25,9 +27,9 @@ export function TeamUndVeranstaltungen({ periodsToShow }: { readonly periodsToSh }, 1000); }, [memoizedId]); - const forVeranstaltungen = useMemo(() => periodsToShow.includes("alle"), [periodsToShow]); + const forVeranstaltungen = useMemo(() => pathname === "/veranstaltungen", [pathname]); const { period, periods, veranstaltungen, veranstaltungenNachMonat, monate, filterTags, usersAsOptions } = - useTeamVeranstaltungenCommons(periodsToShow); + useTeamVeranstaltungenCommons(); const subState = useMemo(() => ({ veranstaltungenNachMonat, usersAsOptions }), [usersAsOptions, veranstaltungenNachMonat]); diff --git a/application/vue/src/components/team/Veranstaltungen.tsx b/application/vue/src/components/team/Veranstaltungen.tsx index 05660e65..2a332dcf 100644 --- a/application/vue/src/components/team/Veranstaltungen.tsx +++ b/application/vue/src/components/team/Veranstaltungen.tsx @@ -6,5 +6,5 @@ export default function Veranstaltungen() { useDirtyBlocker(false); document.title = "Veranstaltungen"; - return ; + return ; } diff --git a/application/vue/src/components/team/useTeamVeranstaltungenCommons.ts b/application/vue/src/components/team/useTeamVeranstaltungenCommons.ts index ad56fd1e..4fa48f39 100644 --- a/application/vue/src/components/team/useTeamVeranstaltungenCommons.ts +++ b/application/vue/src/components/team/useTeamVeranstaltungenCommons.ts @@ -1,6 +1,5 @@ -import { useSearchParams } from "react-router"; import { useJazzContext } from "@/components/content/useJazzContext.ts"; -import { useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useQueries } from "@tanstack/react-query"; import { konzerteForTeam, vermietungenForTeam } from "@/rest/loader.ts"; import Veranstaltung from "jc-shared/veranstaltung/veranstaltung.ts"; @@ -9,37 +8,55 @@ import reverse from "lodash/reverse"; import applyTeamFilter from "@/components/team/TeamFilter/applyTeamFilter.ts"; import groupBy from "lodash/groupBy"; import TeamFilter from "@/components/team/TeamFilter/TeamFilter.tsx"; -import find from "lodash/find"; import map from "lodash/map"; import filter from "lodash/filter"; import capitalize from "lodash/capitalize"; import keys from "lodash/keys"; -export const useTeamVeranstaltungenCommons = (periodsToShow: string[]) => { - const [search, setSearch] = useSearchParams(); - const { allUsers, filter: teamFilter } = useJazzContext(); +export type Period = "zukuenftige" | "vergangene" | "alle"; + +export const useTeamVeranstaltungenCommons = () => { + const [search, setSearch] = useState("zukuenftige"); + const { allUsers, currentUser, teamFilter } = useJazzContext(); + + const periodsToShow = useMemo( + () => (currentUser.accessrights.isOrgaTeam ? ["zukuenftige", "vergangene", "alle"] : ["zukuenftige", "vergangene"]) as Period[], + [currentUser], + ); + + const setSelectedPeriod = useCallback((period: Period) => { + localStorage.setItem("veranstaltungenPeriod", period); + setSearch(period); + }, []); + + useEffect(() => { + const period = (localStorage.getItem("veranstaltungenPeriod") ?? "zukuenftige") as Period; + if (periodsToShow.includes(period)) { + setSearch(period); + } else { + setSearch("zukuenftige"); + } + }, [periodsToShow]); const [period, setPeriod] = useState("Zukünftige"); const periods = useMemo(() => { return map(periodsToShow, (period) => { - return { label: period === "zukuenftige" ? "Zukünftige" : capitalize(period), key: period, onClick: () => setSearch({ period }) }; + return { + label: period === "zukuenftige" ? "Zukünftige" : capitalize(period), + key: period, + onClick: () => setSelectedPeriod(period), + }; }); - }, [periodsToShow, setSearch]); + }, [periodsToShow, setSelectedPeriod]); - const selectedPeriod: "zukuenftige" | "vergangene" | "alle" = useMemo(() => { - return (search.get("period") || periods[0].key) as "zukuenftige" | "vergangene" | "alle"; + const selectedPeriod: Period = useMemo(() => { + return search || periods[0].key; }, [periods, search]); useEffect(() => { - const result = find(periods, ["key", search.get("period")]); - if (!result) { - setSearch({ period: periods[0].key }); - setPeriod("Zukünftige"); - } else { - setPeriod(result.label); - } - }, [periods, search, setSearch]); + setPeriod(selectedPeriod === "zukuenftige" ? "Zukünftige" : capitalize(selectedPeriod)); + }, [selectedPeriod]); const queryResult = useQueries({ queries: [ diff --git a/application/vue/src/components/users/TellUserToFillHelpFields.tsx b/application/vue/src/components/users/TellUserToFillHelpFields.tsx index cee64578..ea9a10d7 100644 --- a/application/vue/src/components/users/TellUserToFillHelpFields.tsx +++ b/application/vue/src/components/users/TellUserToFillHelpFields.tsx @@ -8,6 +8,7 @@ import { saveUser } from "@/rest/loader.ts"; import { JazzPageHeader } from "@/widgets/JazzPageHeader.tsx"; import isNil from "lodash/isNil"; import { JazzModal } from "@/widgets/JazzModal.tsx"; +import cloneDeep from "lodash/cloneDeep"; export function TellUserToFillHelpFields() { const { currentUser } = useJazzContext(); @@ -26,8 +27,7 @@ export function TellUserToFillHelpFields() { useEffect(() => { if (currentUser.id) { if (currentUser.hatKeineKannsGefuellt || isNil(currentUser.kannErsthelfer)) { - const deepCopy = currentUser.toJSONWithoutPass(); - form.setFieldsValue(deepCopy); + form.setFieldsValue(cloneDeep(currentUser)); setIsOpen(true); } } diff --git a/application/vue/src/components/users/UserModals.tsx b/application/vue/src/components/users/UserModals.tsx index b46036a7..bdddc582 100644 --- a/application/vue/src/components/users/UserModals.tsx +++ b/application/vue/src/components/users/UserModals.tsx @@ -13,6 +13,7 @@ import { useWatch } from "antd/es/form/Form"; import isNil from "lodash/isNil"; import { logDiffForDirty } from "jc-shared/commons/comparingAndTransforming.ts"; import { JazzModal } from "@/widgets/JazzModal.tsx"; +import cloneDeep from "lodash/cloneDeep"; export function ChangePasswordModal({ isOpen, @@ -36,7 +37,7 @@ export function ChangePasswordModal({ }); useEffect(() => { - form.setFieldsValue(user.toJSONWithoutPass()); + form.setFieldsValue(cloneDeep(user)); }, [form, user]); async function saveForm() { form.validateFields().then(async () => { @@ -137,9 +138,9 @@ export function EditUserModal({ const [dirty, setDirty] = useState(false); function initializeForm() { - const deepCopy = user.toJSONWithoutPass(); + const deepCopy = cloneDeep(user); form.setFieldsValue(deepCopy); - const initial = user.toJSONWithoutPass(); + const initial = cloneDeep(user); setInitialValue(initial); setDirty(areDifferent(initial, deepCopy)); form.validateFields(); diff --git a/application/vue/src/components/wiki/WikiPage.tsx b/application/vue/src/components/wiki/WikiPage.tsx index 0c5f93ea..10169c3e 100644 --- a/application/vue/src/components/wiki/WikiPage.tsx +++ b/application/vue/src/components/wiki/WikiPage.tsx @@ -45,7 +45,7 @@ export default function WikiPage() { }, [data]); const mutateContent = useJazzMutation({ - saveFunction: (content: string) => saveWikiPage(subdir!, realPage, content), + saveFunction: (data: { content: string }) => saveWikiPage(subdir!, realPage, data), queryKey: "wiki", successMessage: "Die Seite wurde gespeichert", }); @@ -73,7 +73,7 @@ export default function WikiPage() { } function saveForm() { form.validateFields().then(async () => { - mutateContent.mutate(form.getFieldValue("content")); + mutateContent.mutate(form.getFieldsValue(true)); setWikipage(form.getFieldValue("content")); }); } diff --git a/application/vue/src/rest/authenticationRequests.ts b/application/vue/src/rest/authenticationRequests.ts new file mode 100644 index 00000000..d8073b1c --- /dev/null +++ b/application/vue/src/rest/authenticationRequests.ts @@ -0,0 +1,23 @@ +import axios from "axios"; + +export async function loginPost(name: string, pass: string) { + const token = await axios.post("/login", { name, pass }); + return refreshTokenPost(token.data.token); +} + +export async function logoutManually() { + return axios.post("/logout"); +} + +export async function refreshTokenPost(tokenFromLogin?: string) { + let token = tokenFromLogin; + if (!tokenFromLogin) { + const result = await axios.post("/refreshToken"); + token = result.data.token; + } + if (!token) { + return ""; + } + axios.defaults.headers.Authorization = `Bearer ${token}`; + return token; +} diff --git a/application/vue/src/rest/loader.ts b/application/vue/src/rest/loader.ts index 95a918f0..b935b29b 100644 --- a/application/vue/src/rest/loader.ts +++ b/application/vue/src/rest/loader.ts @@ -1,13 +1,12 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import axios, { AxiosRequestConfig, Method } from "axios"; +import axios, { AxiosRequestConfig, AxiosResponse, Method } from "axios"; import User from "jc-shared/user/user.ts"; import Kalender from "jc-shared/programmheft/kalender.ts"; import OptionValues from "jc-shared/optionen/optionValues.ts"; import Orte from "jc-shared/optionen/orte.ts"; -import { Mailingliste } from "jc-shared/user/users.ts"; +import Users, { Mailingliste } from "jc-shared/user/users.ts"; import MailRule from "jc-shared/mail/mailRule.ts"; -import Termin, { TerminFilterOptions } from "jc-shared/optionen/termin.ts"; +import Termin, { TerminEvent, TerminFilterOptions } from "jc-shared/optionen/termin.ts"; import FerienIcals from "jc-shared/optionen/ferienIcals.ts"; import Konzert, { GastArt, ImageOverviewRow, NameWithNumber } from "jc-shared/konzert/konzert.ts"; import isMobile from "ismobilejs"; @@ -16,44 +15,39 @@ import { Rider } from "jc-shared/rider/rider.ts"; import * as jose from "jose"; import { StaffType } from "jc-shared/veranstaltung/staff.ts"; import Veranstaltung from "jc-shared/veranstaltung/veranstaltung.ts"; -import { HistoryObjectOverview } from "jc-shared/history/history.ts"; +import { HistoryDBType, HistoryObjectOverview } from "jc-shared/history/history.ts"; import { SentMessageInfo } from "nodemailer/lib/smtp-transport"; import map from "lodash/map"; import KonzertWithRiderBoxes from "jc-shared/konzert/konzertWithRiderBoxes.ts"; import { historyFromRawRows } from "@/rest/historyObject.ts"; +import { refreshTokenPost } from "@/rest/authenticationRequests.ts"; +import sortBy from "lodash/sortBy"; +import { Period } from "@/components/team/useTeamVeranstaltungenCommons.ts"; -type ContentType = "json" | "pdf" | "zip" | "other"; +type ContentType = "pdf" | "zip" | "other"; -type FetchParams = { +type FetchParams = { + urlPrefix?: string; url: string; - contentType: ContentType; + contentType?: ContentType; method: Method; - data?: any; + data?: T; + resType?: R; }; -export async function loginPost(name: string, pass: string) { - const token = await axios.post("/login", { name, pass }); - return refreshTokenPost(token.data.token); +async function get(params: Omit, "method">) { + return standardFetch({ ...params, method: "GET" }); } -export async function logoutManually() { - return axios.post("/logout"); +async function loeschen(params: Omit, "method">) { + return standardFetch({ ...params, method: "DELETE" }); } -export async function refreshTokenPost(tokenFromLogin?: string) { - let token = tokenFromLogin; - if (!tokenFromLogin) { - const result = await axios.post("/refreshToken"); - token = result.data.token; - } - if (!token) { - return ""; - } - axios.defaults.headers.Authorization = `Bearer ${token}`; - return token; +async function post(params: Omit, "method">) { + return standardFetch({ ...params, method: "POST" }); } -async function standardFetch(params: FetchParams) { +async function standardFetch({ urlPrefix = "/rest", url, method, data, contentType }: FetchParams) { if (!axios.defaults.headers.Authorization) { await refreshTokenPost(); } else { @@ -65,55 +59,36 @@ async function standardFetch(params: FetchParams) { console.log("token veraltet"); } } - const options: AxiosRequestConfig = { - url: params.url, - method: params.method, - data: params.data, - responseType: params.contentType !== "json" ? "blob" : "json", - }; - const res = await axios(options); + const options: AxiosRequestConfig = { url: urlPrefix + url, method: method, data: data, responseType: contentType ? "blob" : "json" }; + const res = await axios>(options); return res.data; } -async function getForType(contentType: ContentType, url: string) { - return standardFetch({ contentType, url, method: "GET" }); -} - export async function uploadFile(data: FormData) { - return standardFetch({ - method: "POST", - url: "/rest/upload", - data, - contentType: "json", - }); + const result = await post({ url: "/upload", data, resType: new Konzert() }); + return new Konzert(result); } export async function uploadWikiImage(data: FormData) { - const res = await standardFetch({ - method: "POST", - url: "/rest/wiki/upload", - data, - contentType: "json", - }); - return res as { url: string }; + return await post({ url: "/wiki/upload", data, resType: { url: "" } }); } -function handleVeranstaltungen(result?: any[]): Konzert[] { - return map(result, (each: any) => new Konzert(each)) || []; +function handleVeranstaltungen(result?: Konzert[]): Konzert[] { + return map(result, (each) => new Konzert(each)); } export async function konzerteBetweenYYYYMM(start: string, end: string) { - const result = await getForType("json", `/rest/konzerte/${start}/${end}`); + const result = await get({ url: `/konzerte/${start}/${end}`, resType: [new Konzert()] }); return handleVeranstaltungen(result); } export async function konzerteForToday() { - const result = await getForType("json", `/rest/konzerte/fortoday`); + const result = await get({ url: `/konzerte/fortoday`, resType: [new Konzert()] }); return handleVeranstaltungen(result); } -export async function konzerteForTeam(selector: "zukuenftige" | "vergangene" | "alle") { - const result = await getForType("json", `/rest/konzerte/${selector}`); +export async function konzerteForTeam(selector: Period) { + const result = await get({ url: `/konzerte/${selector}`, resType: [new Konzert()] }); return handleVeranstaltungen(result); } @@ -124,7 +99,7 @@ export async function konzertWithRiderForUrl(url: string): Promise new Vermietung(each)) || []; +function handleVermietungen(result?: Vermietung[]): Vermietung[] { + return map(result, (each) => new Vermietung(each)); } -export async function vermietungenForTeam(selector: "zukuenftige" | "vergangene" | "alle") { - const result = await getForType("json", `/rest/vermietungen/${selector}`); +export async function vermietungenForTeam(selector: Period) { + const result = await get({ url: `/vermietungen/${selector}`, resType: [new Vermietung()] }); return handleVermietungen(result); } export async function vermietungenBetweenYYYYMM(start: string, end: string) { - const result = await getForType("json", `/rest/vermietungen/${start}/${end}`); + const result = await get({ url: `/vermietungen/${start}/${end}`, resType: [new Vermietung()] }); return handleVermietungen(result); } @@ -198,7 +156,7 @@ export async function vermietungForUrl(url: string): Promise { } if (url.startsWith("copy-of-")) { const realUrl = url.substring(8); - const result = await getForType("json", `/rest/vermietung/${encodeURIComponent(realUrl)}`); + const result = await get({ url: `/vermietung/${encodeURIComponent(realUrl)}`, resType: new Vermietung() }); if (result) { const vermietung = new Vermietung(result); vermietung.reset(); @@ -207,33 +165,23 @@ export async function vermietungForUrl(url: string): Promise { return result; } } - const result = await getForType("json", `/rest/vermietung/${encodeURIComponent(url)}`); + const result = await get({ url: `/vermietung/${encodeURIComponent(url)}`, resType: new Vermietung() }); return result ? new Vermietung(result) : result; } export async function saveVermietung(vermietung: Vermietung) { - const result = await standardFetch({ - method: "POST", - url: "/rest/vermietung", - data: vermietung.toJSON(), - contentType: "json", - }); + const result = await post({ url: "/vermietung", data: vermietung }); return new Vermietung(result); } export async function deleteVermietungWithId(id: string) { - return standardFetch({ - method: "DELETE", - url: "/rest/vermietung", - data: { id }, - contentType: "json", - }); + return loeschen({ url: "/vermietung", data: { id } }); } // User export async function currentUser() { try { - const result = await getForType("json", "/rest/users/current"); + const result = await get({ url: "/users/current", resType: {} as User }); return new User(result); } catch { return new User({ id: "invalidUser" }); @@ -241,222 +189,152 @@ export async function currentUser() { } export async function allUsers(): Promise { - const result = await getForType("json", "/rest/users"); - return map(result?.users, (user: any) => new User(user)) || []; + const result = await get({ url: "/users", resType: [{} as User] }); + return map(result, (user) => new User(user)); } export async function saveUser(user: User) { - return standardFetch({ - method: "POST", - url: "/rest/user", - data: user.toJSON(), - contentType: "json", - }); + const result = await post({ url: "/user", data: user }); + return new User(result); } export async function deleteUser(user: User) { - return standardFetch({ - method: "DELETE", - url: "/rest/user", - data: user.toJSON(), - contentType: "json", - }); + return loeschen({ url: "/user", data: user }); } export async function saveNewUser(user: User) { - return standardFetch({ - method: "PUT", - url: "/rest/user", - data: user.toJSON(), - contentType: "json", - }); + return standardFetch({ method: "PUT", url: "/user", data: user }); } export async function changePassword(user: User) { - return standardFetch({ - method: "POST", - url: "/rest/user/changePassword", - data: user.toJSON(), - contentType: "json", - }); + const result = await post({ url: "/user/changePassword", data: user }); + return new User(result); } // Programmheft export async function kalenderFor(jahrMonat: string) { - const result = await getForType("json", `/rest/programmheft/${jahrMonat}`); + const result = await get({ url: `/programmheft/${jahrMonat}`, resType: new Kalender() }); return result?.id ? new Kalender(result) : new Kalender({ id: jahrMonat }); } -export async function alleKalender(): Promise { - const result = await getForType("json", "/rest/programmheft/alle"); - return result.length > 0 ? map(result, (r: any) => new Kalender(r)) : []; +export async function alleKalender() { + const result = await get({ url: "/programmheft/alle", resType: [new Kalender()] }); + return result.length > 0 ? map(result, (r) => new Kalender(r)) : []; } export async function saveProgrammheft(kalender: Kalender) { - return standardFetch({ - method: "POST", - url: "/rest/programmheft", - data: kalender, - contentType: "json", - }); + const result = await post({ url: "/programmheft", data: kalender }); + return new Kalender(result); } // Rider export async function saveRider(rider: Rider) { - const result = await standardFetch({ - method: "POST", - url: "/rest/riders", - data: rider, - contentType: "json", - }); + const result = await post({ url: "/riders", data: rider }); return new Rider(result); } // Optionen & Termine export async function optionen(): Promise { - const result = await getForType("json", "/rest/optionen"); - return result ? new OptionValues(result) : result; + const result = await get({ url: "/optionen", resType: new OptionValues() }); + return new OptionValues(result); } export async function saveOptionen(optionen: OptionValues) { - const result = await standardFetch({ - method: "POST", - url: "/rest/optionen", - data: optionen.toJSON(), - contentType: "json", - }); + const result = await post({ url: "/optionen", data: optionen }); return new OptionValues(result); } export async function orte() { - const result = await getForType("json", "/rest/orte"); - return result ? new Orte(result) : result; + const result = await get({ url: "/orte", resType: new Orte() }); + return new Orte(result); } export async function saveOrte(orte: Orte) { - return standardFetch({ - method: "POST", - url: "/rest/orte", - data: orte.toJSON(), - contentType: "json", - }); + const result = await post({ url: "/orte", data: orte }); + return new Orte(result); } -export async function termine(): Promise { - const result = await getForType("json", "/rest/termine"); - return map(result, (r: any) => new Termin(r)) ?? []; +export async function termine() { + const result = await get({ url: "/termine", resType: [new Termin()] }); + return map(result, (r) => new Termin(r)) ?? []; } export async function saveTermine(termine: Termin[]) { - const result = await standardFetch({ - method: "POST", - url: "/rest/termine", - data: termine, - contentType: "json", - }); - return map(result, (r: any) => new Termin(r)) ?? []; + const result = await post({ url: "/termine", data: termine }); + return map(result, (r) => new Termin(r)) ?? []; } export async function kalender() { - const result = await getForType("json", "/rest/kalender"); + const result = await get({ url: "/kalender", resType: new FerienIcals() }); return result ? new FerienIcals(result) : result; } export async function saveKalender(kalender: FerienIcals) { - const result = await standardFetch({ - method: "POST", - url: "/rest/kalender", - data: kalender, - contentType: "json", - }); + const result = await post({ url: "/kalender", data: kalender }); return result ? new FerienIcals(result) : result; } // Image export async function imagenames() { - const result = await getForType("json", "/rest/imagenames"); - return (result?.names as string[]) || []; + const result = await get({ url: "/imagenames", resType: { names: [""] } }); + return result?.names ?? []; } export async function saveImagenames(rows: ImageOverviewRow[]) { - return standardFetch({ - method: "POST", - url: "/rest/imagenames", - data: rows, - contentType: "json", - }); + await post({ url: "/imagenames", data: rows, resType: { names: [""] } }); + return rows; } //Mails intern export async function sendMail(formData: FormData) { - return standardFetch({ - method: "POST", - url: "/rest/rundmail", - data: formData, - contentType: "json", - }) as Promise; -} - -export async function saveMailinglists(lists: Mailingliste[]) { - return standardFetch({ - method: "POST", - url: "/rest/mailinglisten", - data: lists, - contentType: "json", - }); + return post({ url: "/rundmail", data: formData, resType: {} as SentMessageInfo }); +} + +export async function allMailinglists() { + const result = await get({ url: "/mailinglisten", resType: [] as User[] }); + return { lists: sortBy(new Users(result ?? []).mailinglisten, "name") }; +} + +export async function saveMailinglists({ lists }: { lists: Mailingliste[] }) { + const result = await post({ url: "/mailinglisten", data: lists, resType: [] as User[] }); + return { lists: sortBy(new Users(result ?? []).mailinglisten, "name") }; } // Mails für Veranstaltungen -export async function mailRules(): Promise { - const result = await getForType("json", "/rest/mailrule"); - return map(result, (each: any) => new MailRule(each)) || []; +export async function mailRules() { + const result = await get({ url: "/mailrule", resType: [new MailRule()] }); + return map(result, (each) => new MailRule(each)); } export async function saveMailRules(rules: MailRule[]) { - return standardFetch({ - method: "POST", - url: "/rest/mailrules", - data: rules, - contentType: "json", - }); + return post({ url: "/mailrules", data: rules }); } // Wiki -export async function wikisubdirs(): Promise<{ dirs: string[] }> { - const json = await getForType("json", "/rest/wikidirs"); - return json || { dirs: [] }; +export async function wikisubdirs() { + const json = await get({ url: "/wikidirs", resType: { dirs: [""] } }); + return json ?? { dirs: [] }; } export async function wikiPage(subdir: string, page: string) { - const result = await getForType("json", `/rest/wikipage/${subdir}/${page}`); - return result?.content || ""; + const result = await get({ url: `/wikipage/${subdir}/${page}`, resType: { content: "" } }); + return result?.content ?? ""; } -export async function saveWikiPage(subdir: string, page: string, content: string) { - return standardFetch({ - method: "POST", - url: `/rest/wikipage/${subdir}/${page}`, - data: { content }, - contentType: "json", - }); +export async function saveWikiPage(subdir: string, page: string, data: { content: string }) { + return post({ url: `/wikipage/${subdir}/${page}`, data }); } export async function searchWiki(suchtext: string) { - return standardFetch({ - method: "POST", - url: "/rest/wikipage/search", + return post({ + url: "/wikipage/search", data: { suchtext }, - contentType: "json", + resType: { searchtext: "", matches: [{ pageName: "", line: "", text: "" }] }, }); } export async function deleteWikiPage(subdir: string, page: string) { - return standardFetch({ - method: "DELETE", - url: `/rest/wikipage/${subdir}/${page}`, - data: { data: "" }, - contentType: "json", - }); + return loeschen({ url: `/wikipage/${subdir}/${page}`, data: { data: "" } }); } // Calendar @@ -471,21 +349,21 @@ export async function calendarEventSources({ options?: TerminFilterOptions; isDarkMode: boolean; }) { - const segments = [`/rest/fullcalendarevents.json?start=${start.toISOString()}&end=${end.toISOString()}&darkMode=${isDarkMode}`]; + const segments = [`/fullcalendarevents.json?start=${start.toISOString()}&end=${end.toISOString()}&darkMode=${isDarkMode}`]; if (options) { segments.push(`&options=${JSON.stringify(options)}`); } - return getForType("json", segments.join()); + return get({ url: segments.join(), resType: [{} as TerminEvent] }); } // History export async function historyIdsFor(collection: string) { - const result = await getForType("json", `/rest/history/${collection}`); + const result = await get({ url: `/history/${collection}` }); return result as HistoryObjectOverview[]; } export async function historyRowsFor(collection: string, id: string) { - const result = await getForType("json", `/rest/history/${collection}/${encodeURIComponent(id)}`); + const result = await get({ url: `/history/${collection}/${encodeURIComponent(id)}`, resType: [{} as HistoryDBType] }); return historyFromRawRows(result); } @@ -508,15 +386,15 @@ export async function openAngebotRechnung(vermietung: Vermietung) { window.open(`/pdf/vermietungAngebot/${encodeURIComponent(filename)}?url=${encodeURIComponent(vermietung.url!)}&art=${vermietung.art}`); } -export async function imgFullsize(url: any) { - const img = await getForType("other", `/upload/${url}`); +export async function imgFullsize(url: string) { + const img = await get({ contentType: "other", url: `/upload/${url}`, resType: new Blob() }); if (img) { showFile(img, url); } } export async function imgzipForVeranstaltung(konzert: Konzert) { - const zip = await getForType("zip", `/imgzipForVeranstaltung/${konzert.url}`); + const zip = await get({ contentType: "zip", url: `/imgzipForVeranstaltung/${konzert.url}`, resType: new Blob() }); if (zip) { showFile(zip, `JazzClub_Bilder_${konzert.kopf.titel}.zip`); } diff --git a/application/vue/src/widgets/PreisprofilSelect.tsx b/application/vue/src/widgets/PreisprofilSelect.tsx index da046061..f607272c 100644 --- a/application/vue/src/widgets/PreisprofilSelect.tsx +++ b/application/vue/src/widgets/PreisprofilSelect.tsx @@ -4,6 +4,7 @@ import OptionValues, { Preisprofil } from "jc-shared/optionen/optionValues"; import useFormInstance from "antd/es/form/hooks/useFormInstance"; import find from "lodash/find"; import map from "lodash/map"; +import sortBy from "lodash/sortBy"; interface PreisprofilSelectParams { readonly optionen: OptionValues; @@ -54,7 +55,7 @@ function InternalPreisprofilSelect({ id, onValueAsObject, optionen, valueAsObjec if (valueAsObject && !find(optionen.preisprofile, { name: valueAsObject.name })) { result.push({ ...valueAsObject, veraltet: true }); } - return result.sort((a, b) => (a.regulaer > b.regulaer ? 1 : -1)); + return sortBy(result, "regulaer"); }, [optionen, valueAsObject]); const displayProfile = useMemo(() => { diff --git a/application/vue/src/widgets/Uploader.tsx b/application/vue/src/widgets/Uploader.tsx index fa66e515..533df746 100644 --- a/application/vue/src/widgets/Uploader.tsx +++ b/application/vue/src/widgets/Uploader.tsx @@ -42,7 +42,8 @@ export default function Uploader({ name, typ, onlyImages = false }: UploaderPara try { const newVeranstaltung = await uploadFile(formData); setFileList([]); - const strings = name.reduce((prev, curr) => prev[curr], newVeranstaltung); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const strings = name.reduce((prev, curr) => (prev as any)[curr], newVeranstaltung); form.setFieldValue(name, strings); } catch { // eslint-disable-next-line no-console diff --git a/application/vue/test/components/team/applyTeamFilter.test.ts b/application/vue/test/components/team/applyTeamFilter.test.ts index faaf208d..58cc4cdb 100644 --- a/application/vue/test/components/team/applyTeamFilter.test.ts +++ b/application/vue/test/components/team/applyTeamFilter.test.ts @@ -30,13 +30,11 @@ const hotelNichtBestatigt = new Konzert({ unterkunft: { bestaetigt: false }, }); -const mitEventTyp1 = new Konzert({ - kopf: { titel: "mitEventTyp1", eventTyp: "eventTyp1" }, -}); +const mitEventTyp1 = new Konzert({ kopf: { titel: "mitEventTyp1", eventTyp: "eventTyp1" } }); +const mitEventTyp2 = new Konzert({ kopf: { titel: "mitEventTyp2", eventTyp: "eventTyp2" } }); -const mitEventTyp2 = new Konzert({ - kopf: { titel: "mitEventTyp2", eventTyp: "eventTyp2" }, -}); +const booker1 = new Konzert({ kopf: { titel: "booker1" }, booker: ["user1"] }); +const booker2 = new Konzert({ kopf: { titel: "booker2" }, booker: ["user2"] }); const alleKonzerte = [ neutral, @@ -55,6 +53,8 @@ const alleKonzerte = [ hotelNichtBestatigt, mitEventTyp1, mitEventTyp2, + booker1, + booker2, ]; function checkResult(teamFilter: (ver: Veranstaltung) => boolean) { @@ -188,4 +188,19 @@ describe("applyTeamFilter", () => { const filter = applyTeamFilter({ kopf: { eventTyp: ["eventTyp1", "eventTyp2"] } }); expect(checkResult(filter)).to.eql(["mitEventTyp1", "mitEventTyp2"]); }); + + it("should return for one eventTyp", () => { + const filter = applyTeamFilter({ kopf: { eventTyp: ["eventTyp1"] } }); + expect(checkResult(filter)).to.eql(["mitEventTyp1"]); + }); + + it("should return for booker1", () => { + const filter = applyTeamFilter({ booker: ["user1"] }); + expect(checkResult(filter)).to.eql(["booker1"]); + }); + + it("should return for many bookers", () => { + const filter = applyTeamFilter({ booker: ["user1", "user2"] }); + expect(checkResult(filter)).to.eql(["booker1", "booker2"]); + }); }); diff --git a/frontendtests/tests/20_filter_test.ts b/frontendtests/tests/20_filter_test.ts index c6c18cd5..3bd803c6 100644 --- a/frontendtests/tests/20_filter_test.ts +++ b/frontendtests/tests/20_filter_test.ts @@ -100,4 +100,5 @@ Data(menuToClick).Scenario("Viele'", async ({ I, current, filters }) => { I.see("HotelNichtBestatigt"); I.dontSee("HotelBestaetigt"); I.dontSee("Neutral"); + I.click(locate("button").withText("Zurücksetzen").inside(".ant-space-item")); });