import { create } from "zustand"; import { persist, createJSONStorage } from "zustand/middleware"; import { ApplicationDetails, ApplicationListItem, FormData, Variant, CreateResponse, UpdateResponse, RootPayload, } from "../types/api"; import { apiClient, isSuccessResponse, getErrorMessage } from "../api/client"; interface ApplicationState { // Current application currentApplication: ApplicationDetails | null; applicationList: ApplicationListItem[]; // Authentication paId: string | null; paKey: string | null; masterKey: string | null; isAdmin: boolean; // UI State isLoading: boolean; isSubmitting: boolean; error: string | null; successMessage: string | null; // Form state formData: Partial; isDirty: boolean; // Pagination currentPage: number; totalItems: number; itemsPerPage: number; // Filters statusFilter: string | null; variantFilter: string | null; searchQuery: string | null; // Sorting sortBy: string; sortOrder: "asc" | "desc"; } interface ApplicationActions { // Authentication setApplicationCredentials: (paId: string, paKey: string) => void; setMasterKey: (masterKey: string) => void; validateMasterKey: () => Promise; clearCredentials: () => void; // Application CRUD createApplication: ( pdf?: File, formData?: Partial, variant?: Variant, ) => Promise; updateApplication: ( pdf?: File, formData?: Partial, variant?: Variant, ) => Promise; loadApplication: (paId?: string, paKey?: string) => Promise; deleteApplication: (paId?: string, paKey?: string) => Promise; downloadApplicationPdf: (paId?: string, paKey?: string) => Promise; // Application list management loadApplicationList: (refresh?: boolean) => Promise; searchApplications: (query: string) => Promise; searchApplicationsAdvanced: (params: any) => Promise; setStatusFilter: (status: string | null) => void; setVariantFilter: (variant: string | null) => void; setPage: (page: number) => void; setSorting: (sortBy: string, sortOrder: "asc" | "desc") => void; // Admin operations loadApplicationAdmin: (paId: string) => Promise; loadApplicationListAdmin: () => Promise; updateApplicationStatusAdmin: ( paId: string, status: string, ) => Promise; deleteApplicationAdmin: (paId: string) => Promise; downloadApplicationPdfAdmin: (paId: string) => Promise; updateApplicationAdmin: ( paId: string, pdf?: File, formData?: Partial, variant?: Variant, ) => Promise; resetApplicationCredentials: ( paId: string, ) => Promise<{ pa_id: string; pa_key: string } | null>; // Bulk operations bulkDeleteApplications: (paIds: string[]) => Promise; bulkApproveApplications: (paIds: string[]) => Promise; bulkRejectApplications: (paIds: string[]) => Promise; bulkSetInReviewApplications: (paIds: string[]) => Promise; bulkSetNewApplications: (paIds: string[]) => Promise; // Form management updateFormData: (data: Partial) => void; resetFormData: () => void; setFormData: (data: Partial) => void; markFormDirty: () => void; markFormClean: () => void; // UI state management setLoading: (loading: boolean) => void; setSubmitting: (submitting: boolean) => void; setError: (error: string | null) => void; setSuccessMessage: (message: string | null) => void; clearMessages: () => void; // Utility reset: () => void; isCurrentUserApplication: (paId: string) => boolean; } const initialFormData: Partial = { applicantType: "person", institutionType: "-", institutionName: "", firstName: "", lastName: "", email: "", phone: "", course: "-", role: "-", projectName: "", startDate: "", endDate: "", participants: undefined, description: "", participatingFaculties: { inf: false, esb: false, ls: false, tec: false, tex: false, nxt: false, open: false, }, costs: [], requestedAmountEur: 0, variant: "VSM", }; const initialState: ApplicationState = { currentApplication: null, applicationList: [], paId: null, paKey: null, masterKey: null, isAdmin: false, isLoading: false, isSubmitting: false, error: null, successMessage: null, formData: initialFormData, isDirty: false, currentPage: 0, totalItems: 0, itemsPerPage: 50, statusFilter: null, variantFilter: null, searchQuery: null, // Sorting sortBy: "created_at", sortOrder: "desc", }; export const useApplicationStore = create< ApplicationState & ApplicationActions >()( persist( (set, get) => ({ ...initialState, // Authentication setApplicationCredentials: (paId: string, paKey: string) => { set({ paId, paKey, isAdmin: false }); apiClient.clearMasterKey(); }, setMasterKey: (masterKey: string) => { set({ masterKey, isAdmin: true }); apiClient.setMasterKey(masterKey); }, validateMasterKey: async (): Promise => { const { masterKey } = get(); if (!masterKey) { set({ error: "Master key is required" }); return false; } set({ isLoading: true, error: null }); try { // Try to fetch applications list to validate master key const response = await apiClient.listApplicationsAdmin({ limit: 1, offset: 0, }); if (isSuccessResponse(response)) { set({ isLoading: false, isAdmin: true }); return true; } else { set({ error: "Invalid master key", isLoading: false, isAdmin: false, masterKey: null, }); apiClient.clearMasterKey(); return false; } } catch (error) { set({ error: "Invalid master key", isLoading: false, isAdmin: false, masterKey: null, }); apiClient.clearMasterKey(); return false; } }, clearCredentials: () => { set({ paId: null, paKey: null, masterKey: null, isAdmin: false, currentApplication: null, applicationList: [], }); apiClient.clearMasterKey(); }, // Application CRUD createApplication: async ( pdf?: File, formData?: Partial, variant?: Variant, ) => { set({ isSubmitting: true, error: null }); try { const request = { pdf, variant, return_format: "json" as const, form_json_b64: formData ? createFormJsonFromData(formData) : undefined, }; const response = await apiClient.createApplication(request); if (isSuccessResponse(response)) { const data = response.data as CreateResponse; // Validate response data if (!data.pa_id || !data.pa_key) { set({ error: "Unvollständige Antwort vom Server - ID oder Schlüssel fehlt", isSubmitting: false, }); return false; } set({ paId: data.pa_id, paKey: data.pa_key, successMessage: `Antrag erfolgreich erstellt! ID: ${data.pa_id}`, isSubmitting: false, }); return true; } else { set({ error: getErrorMessage(response), isSubmitting: false, }); return false; } } catch (error) { set({ error: error instanceof Error ? error.message : "Unbekannter Fehler", isSubmitting: false, }); return false; } }, updateApplication: async ( pdf?: File, formData?: Partial, variant?: Variant, ) => { const { paId, paKey } = get(); if (!paId || !paKey) { set({ error: "Keine Anmeldedaten verfügbar" }); return false; } set({ isSubmitting: true, error: null }); try { const request = { pdf, variant, return_format: "json" as const, form_json_b64: formData ? createFormJsonFromData(formData) : undefined, }; const response = await apiClient.updateApplication( paId, paKey, request, ); if (isSuccessResponse(response)) { const data = response.data as UpdateResponse; set({ successMessage: `Antrag erfolgreich aktualisiert: ${data.pa_id}`, isSubmitting: false, isDirty: false, }); // Reload current application await get().loadApplication(); return true; } else { set({ error: getErrorMessage(response), isSubmitting: false, }); return false; } } catch (error) { set({ error: error instanceof Error ? error.message : "Unbekannter Fehler", isSubmitting: false, }); return false; } }, loadApplication: async (paId?: string, paKey?: string) => { const state = get(); const id = paId || state.paId; const key = paKey || state.paKey; if (!id || !key) { set({ error: "Keine Anmeldedaten verfügbar" }); return false; } set({ isLoading: true, error: null }); try { const response = await apiClient.getApplication(id, key, "json"); if (isSuccessResponse(response)) { const application = response.data as ApplicationDetails; // Convert application payload to form data const convertedFormData = convertPayloadToFormData( application.payload, ); set({ currentApplication: application, formData: { ...initialFormData, ...convertedFormData }, isLoading: false, isDirty: false, }); return true; } else { set({ error: getErrorMessage(response), isLoading: false, }); return false; } } catch (error) { set({ error: error instanceof Error ? error.message : "Unbekannter Fehler", isLoading: false, }); return false; } }, deleteApplication: async (paId?: string, paKey?: string) => { const state = get(); const id = paId || state.paId; const key = paKey || state.paKey; if (!id || !key) { set({ error: "Keine Anmeldedaten verfügbar" }); return false; } set({ isLoading: true, error: null }); try { const response = await apiClient.deleteApplication(id, key); if (isSuccessResponse(response)) { set({ currentApplication: null, successMessage: "Antrag erfolgreich gelöscht", isLoading: false, }); return true; } else { set({ error: getErrorMessage(response), isLoading: false, }); return false; } } catch (error) { set({ error: error instanceof Error ? error.message : "Unbekannter Fehler", isLoading: false, }); return false; } }, downloadApplicationPdf: async (paId?: string, paKey?: string) => { const state = get(); const id = paId || state.paId; const key = paKey || state.paKey; console.log("Starting PDF download for application:", id); if (!id || !key) { console.error("PDF download failed: Missing credentials", { id, key: key ? "***" : null, }); set({ error: "Keine Anmeldedaten verfügbar" }); return false; } set({ isLoading: true, error: null }); try { console.log("Making API call for PDF download..."); const response = await apiClient.getApplication(id, key, "pdf"); if (isSuccessResponse(response)) { const blob = response.data as Blob; console.log("Received PDF blob:", { size: blob?.size, type: blob?.type, }); // Check if blob is valid if (!blob || blob.size === 0) { set({ error: "PDF konnte nicht generiert werden - leere Antwort vom Server", isLoading: false, }); return false; } // Check for browser support if (!window.URL || !window.URL.createObjectURL) { set({ error: "Ihr Browser unterstützt das Herunterladen von Dateien nicht", isLoading: false, }); return false; } try { console.log("Creating download link for PDF..."); const url = window.URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = `antrag-${id}.pdf`; // Make link invisible and add to DOM link.style.display = "none"; document.body.appendChild(link); // Trigger download console.log("Triggering PDF download..."); link.click(); // Cleanup setTimeout(() => { document.body.removeChild(link); window.URL.revokeObjectURL(url); }, 100); set({ successMessage: "PDF wird heruntergeladen...", isLoading: false, }); return true; } catch (downloadError) { console.error("Download error:", downloadError); set({ error: "Fehler beim Herunterladen der PDF-Datei. Bitte versuchen Sie es erneut.", isLoading: false, }); return false; } } else { set({ error: getErrorMessage(response), isLoading: false, }); return false; } } catch (error) { console.error("PDF download error:", error); let errorMessage = "Unbekannter Fehler beim Herunterladen"; if (error instanceof Error) { if (error.message.includes("404")) { errorMessage = "Antrag nicht gefunden"; } else if (error.message.includes("403")) { errorMessage = "Keine Berechtigung zum Herunterladen"; } else if (error.message.includes("500")) { errorMessage = "Server-Fehler beim Generieren der PDF"; } else { errorMessage = `Download-Fehler: ${error.message}`; } } set({ error: errorMessage, isLoading: false, }); return false; } }, // Application list management loadApplicationList: async (refresh = false) => { const { paId, paKey, isAdmin, currentPage, itemsPerPage, statusFilter, variantFilter, } = get(); if (!isAdmin && (!paId || !paKey)) { set({ error: "Keine Anmeldedaten verfügbar" }); return false; } set({ isLoading: true, error: null }); try { let response; if (isAdmin) { // Ensure master key is set before calling admin API if (!get().masterKey) { set({ error: "Master key not available. Please login again.", isLoading: false, }); return false; } response = await apiClient.listApplicationsAdmin({ limit: itemsPerPage, offset: currentPage * itemsPerPage, status: statusFilter || undefined, variant: variantFilter || undefined, order_by: get().sortBy, order: get().sortOrder, }); } else { response = await apiClient.listApplications(paId!, paKey!, { limit: itemsPerPage, offset: currentPage * itemsPerPage, status: statusFilter || undefined, variant: variantFilter || undefined, order_by: get().sortBy, order: get().sortOrder, }); } if (isSuccessResponse(response)) { const applications = response.data; set({ applicationList: refresh ? applications : [...get().applicationList, ...applications], isLoading: false, }); return true; } else { set({ error: getErrorMessage(response), isLoading: false, }); return false; } } catch (error) { set({ error: error instanceof Error ? error.message : "Unbekannter Fehler", isLoading: false, }); return false; } }, searchApplications: async (query: string) => { if (!get().isAdmin) { set({ error: "Suche nur für Administratoren verfügbar" }); return false; } // Ensure master key is set before calling admin API if (!get().masterKey) { set({ error: "Master key not available. Please login again.", isLoading: false, }); return false; } set({ isLoading: true, error: null, searchQuery: query }); try { const response = await apiClient.searchApplications({ q: query, limit: get().itemsPerPage, offset: 0, status: get().statusFilter || undefined, variant: get().variantFilter || undefined, order_by: get().sortBy, order: get().sortOrder, }); if (isSuccessResponse(response)) { set({ applicationList: response.data, currentPage: 0, isLoading: false, }); return true; } else { set({ error: getErrorMessage(response), isLoading: false, }); return false; } } catch (error) { set({ error: error instanceof Error ? error.message : "Unbekannter Fehler", isLoading: false, }); return false; } }, searchApplicationsAdvanced: async (params: any) => { if (!get().isAdmin) { set({ error: "Suche nur für Administratoren verfügbar" }); return false; } if (!get().masterKey) { set({ error: "Master key not available. Please login again.", isLoading: false, }); return false; } set({ isLoading: true, error: null }); try { const response = await apiClient.searchApplications({ q: params.q, status: params.status, variant: params.variant, amount_min: params.amount_min, amount_max: params.amount_max, date_from: params.date_from, date_to: params.date_to, created_by: params.created_by, has_attachments: params.has_attachments, limit: params.limit || get().itemsPerPage, offset: params.offset || 0, order_by: get().sortBy, order: get().sortOrder, }); if (isSuccessResponse(response)) { set({ applicationList: response.data, currentPage: 0, isLoading: false, searchQuery: params.q || null, }); return true; } else { set({ error: getErrorMessage(response), isLoading: false, }); return false; } } catch (error: any) { set({ error: error.message || "Suche fehlgeschlagen", isLoading: false, }); return false; } }, setStatusFilter: (status) => { set({ statusFilter: status, currentPage: 0 }); }, setVariantFilter: (variant) => { set({ variantFilter: variant, currentPage: 0 }); }, setPage: (page) => { set({ currentPage: page }); }, setSorting: (sortBy, sortOrder) => { set({ sortBy, sortOrder, currentPage: 0 }); }, // Admin operations loadApplicationAdmin: async (paId: string) => { if (!get().isAdmin) { set({ error: "Admin-Berechtigung erforderlich" }); return false; } // Ensure master key is set before calling admin API if (!get().masterKey) { set({ error: "Master key not available. Please login again.", isLoading: false, }); return false; } set({ isLoading: true, error: null }); try { const response = await apiClient.getApplicationAdmin(paId, "json"); if (isSuccessResponse(response)) { const application = response.data as ApplicationDetails; const convertedFormData = convertPayloadToFormData( application.payload, ); set({ currentApplication: application, formData: { ...initialFormData, ...convertedFormData }, isLoading: false, }); return true; } else { set({ error: getErrorMessage(response), isLoading: false, }); return false; } } catch (error) { set({ error: error instanceof Error ? error.message : "Unbekannter Fehler", isLoading: false, }); return false; } }, loadApplicationListAdmin: async () => { return get().loadApplicationList(true); }, updateApplicationStatusAdmin: async (paId: string, status: string) => { if (!get().isAdmin) { set({ error: "Admin-Berechtigung erforderlich" }); return false; } // Ensure master key is set before calling admin API if (!get().masterKey) { set({ error: "Master key not available. Please login again.", isLoading: false, }); return false; } set({ isLoading: true, error: null }); try { const response = await apiClient.setApplicationStatusAdmin( paId, status, ); if (isSuccessResponse(response)) { // Update the application in the list const updatedList = get().applicationList.map((app) => app.pa_id === paId ? { ...app, status } : app, ); // Also update current application if it matches const currentApp = get().currentApplication; if (currentApp && currentApp.pa_id === paId) { set({ currentApplication: { ...currentApp, status }, applicationList: updatedList, successMessage: `Status erfolgreich auf "${status}" geändert`, isLoading: false, }); } else { set({ applicationList: updatedList, successMessage: `Status erfolgreich auf "${status}" geändert`, isLoading: false, }); } return true; } else { const errorMsg = getErrorMessage(response); console.error(`Status update failed for ${paId}:`, errorMsg); set({ error: `Fehler beim Ändern des Status: ${errorMsg}`, isLoading: false, }); return false; } } catch (error) { console.error(`Status update error for ${paId}:`, error); let errorMessage = "Unbekannter Fehler beim Ändern des Status"; if (error instanceof Error) { if (error.message.includes("500")) { errorMessage = "Server-Fehler beim Ändern des Status. Bitte versuchen Sie es später erneut."; } else if (error.message.includes("403")) { errorMessage = "Keine Berechtigung zum Ändern des Status"; } else if (error.message.includes("404")) { errorMessage = "Antrag nicht gefunden"; } else { errorMessage = `Fehler beim Ändern des Status: ${error.message}`; } } set({ error: errorMessage, isLoading: false, }); return false; } }, downloadApplicationPdfAdmin: async (paId: string) => { console.log("Starting admin PDF download for application:", paId); if (!get().isAdmin) { console.error("Admin PDF download failed: Not an admin"); set({ error: "Admin-Berechtigung erforderlich" }); return false; } set({ isLoading: true, error: null }); try { console.log("Making admin API call for PDF download..."); const response = await apiClient.getApplicationAdmin(paId, "pdf"); if (isSuccessResponse(response)) { const blob = response.data as Blob; console.log("Received admin PDF blob:", { size: blob?.size, type: blob?.type, }); // Check if blob is valid if (!blob || blob.size === 0) { set({ error: "PDF konnte nicht generiert werden - leere Antwort vom Server", isLoading: false, }); return false; } // Check for browser support if (!window.URL || !window.URL.createObjectURL) { set({ error: "Ihr Browser unterstützt das Herunterladen von Dateien nicht", isLoading: false, }); return false; } try { console.log("Creating admin download link for PDF..."); const url = window.URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = `antrag-${paId}.pdf`; // Make link invisible and add to DOM link.style.display = "none"; document.body.appendChild(link); // Trigger download console.log("Triggering admin PDF download..."); link.click(); // Cleanup setTimeout(() => { document.body.removeChild(link); window.URL.revokeObjectURL(url); }, 100); set({ successMessage: "PDF wird heruntergeladen...", isLoading: false, }); return true; } catch (downloadError) { console.error("Admin download error:", downloadError); set({ error: "Fehler beim Herunterladen der PDF-Datei. Bitte versuchen Sie es erneut.", isLoading: false, }); return false; } } else { set({ error: getErrorMessage(response), isLoading: false, }); return false; } } catch (error) { console.error("Admin PDF download error:", error); let errorMessage = "Unbekannter Fehler beim Herunterladen"; if (error instanceof Error) { if (error.message.includes("404")) { errorMessage = "Antrag nicht gefunden"; } else if (error.message.includes("403")) { errorMessage = "Keine Berechtigung zum Herunterladen"; } else if (error.message.includes("500")) { errorMessage = "Server-Fehler beim Generieren der PDF"; } else { errorMessage = `Download-Fehler: ${error.message}`; } } set({ error: errorMessage, isLoading: false, }); return false; } }, deleteApplicationAdmin: async (paId: string) => { if (!get().isAdmin) { set({ error: "Admin-Berechtigung erforderlich" }); return false; } // Ensure master key is set before calling admin API if (!get().masterKey) { set({ error: "Master key not available. Please login again.", isLoading: false, }); return false; } set({ isLoading: true, error: null }); try { const response = await apiClient.deleteApplicationAdmin(paId); if (isSuccessResponse(response)) { // Remove from the list const updatedList = get().applicationList.filter( (app) => app.pa_id !== paId, ); set({ applicationList: updatedList, successMessage: "Antrag erfolgreich gelöscht", isLoading: false, }); return true; } else { set({ error: getErrorMessage(response), isLoading: false, }); return false; } } catch (error) { set({ error: error instanceof Error ? error.message : "Unbekannter Fehler", isLoading: false, }); return false; } }, updateApplicationAdmin: async ( paId: string, pdf?: File, formData?: Partial, variant?: Variant, ) => { if (!get().isAdmin) { set({ error: "Admin-Berechtigung erforderlich" }); return false; } set({ isSubmitting: true, error: null }); try { const request = { pdf, variant, return_format: "json" as const, form_json_b64: formData ? createFormJsonFromData(formData) : undefined, }; const response = await apiClient.updateApplicationAdmin( paId, request, ); if (isSuccessResponse(response)) { const data = response.data as UpdateResponse; set({ successMessage: `Antrag erfolgreich aktualisiert: ${data.pa_id}`, isSubmitting: false, isDirty: false, }); // Reload current application await get().loadApplicationAdmin(paId); return true; } else { set({ error: getErrorMessage(response), isSubmitting: false, }); return false; } } catch (error) { set({ error: error instanceof Error ? error.message : "Unbekannter Fehler", isSubmitting: false, }); return false; } }, resetApplicationCredentials: async (paId: string) => { if (!get().isAdmin) { set({ error: "Admin-Berechtigung erforderlich" }); return null; } set({ isLoading: true, error: null }); try { const response = await apiClient.resetApplicationCredentials(paId); if (!response.success) { throw new Error(response.error.detail); } set({ successMessage: `Neue Zugangsdaten generiert für Antrag ${paId}`, isLoading: false, }); return { pa_id: response.data.pa_id, pa_key: response.data.pa_key, }; } catch (error) { set({ error: error instanceof Error ? error.message : "Fehler beim Zurücksetzen der Zugangsdaten", isLoading: false, }); return null; } }, // Form management updateFormData: (data) => { set((state) => ({ formData: { ...state.formData, ...data }, isDirty: true, })); }, resetFormData: () => { set({ formData: initialFormData, isDirty: false }); }, setFormData: (data) => { set({ formData: { ...initialFormData, ...data }, isDirty: false }); }, markFormDirty: () => { set({ isDirty: true }); }, markFormClean: () => { set({ isDirty: false }); }, // UI state management setLoading: (loading) => set({ isLoading: loading }), setSubmitting: (submitting) => set({ isSubmitting: submitting }), setError: (error) => set({ error }), setSuccessMessage: (message) => set({ successMessage: message }), clearMessages: () => { set({ error: null, successMessage: null }); }, // Utility reset: () => { set(initialState); apiClient.clearMasterKey(); }, isCurrentUserApplication: (paId: string) => { const { paId: currentPaId, isAdmin } = get(); return isAdmin || currentPaId === paId; }, // Bulk operations bulkDeleteApplications: async (paIds: string[]) => { if (!get().isAdmin || !get().masterKey) { set({ error: "Admin access required" }); return false; } set({ isLoading: true, error: null }); try { const response = await apiClient.bulkOperation({ pa_ids: paIds, operation: "delete", }); if (isSuccessResponse(response)) { // Reload application list to reflect changes await get().loadApplicationListAdmin(); set({ successMessage: `${response.data.success.length} Anträge erfolgreich gelöscht`, isLoading: false, }); return true; } else { set({ error: getErrorMessage(response), isLoading: false, }); return false; } } catch (error) { set({ error: error instanceof Error ? error.message : "Fehler beim Löschen", isLoading: false, }); return false; } }, bulkApproveApplications: async (paIds: string[]) => { if (!get().isAdmin || !get().masterKey) { set({ error: "Admin access required" }); return false; } set({ isLoading: true, error: null }); try { const response = await apiClient.bulkOperation({ pa_ids: paIds, operation: "approve", }); if (isSuccessResponse(response)) { await get().loadApplicationListAdmin(); set({ successMessage: `${response.data.success.length} Anträge erfolgreich genehmigt`, isLoading: false, }); return true; } else { set({ error: getErrorMessage(response), isLoading: false, }); return false; } } catch (error) { set({ error: error instanceof Error ? error.message : "Fehler beim Genehmigen", isLoading: false, }); return false; } }, bulkRejectApplications: async (paIds: string[]) => { if (!get().isAdmin || !get().masterKey) { set({ error: "Admin access required" }); return false; } set({ isLoading: true, error: null }); try { const response = await apiClient.bulkOperation({ pa_ids: paIds, operation: "reject", }); if (isSuccessResponse(response)) { await get().loadApplicationListAdmin(); set({ successMessage: `${response.data.success.length} Anträge erfolgreich abgelehnt`, isLoading: false, }); return true; } else { set({ error: getErrorMessage(response), isLoading: false, }); return false; } } catch (error) { set({ error: error instanceof Error ? error.message : "Fehler beim Ablehnen", isLoading: false, }); return false; } }, bulkSetInReviewApplications: async (paIds: string[]) => { if (!get().isAdmin || !get().masterKey) { set({ error: "Admin access required" }); return false; } set({ isLoading: true, error: null }); try { const response = await apiClient.bulkOperation({ pa_ids: paIds, operation: "set_in_review", }); if (isSuccessResponse(response)) { await get().loadApplicationListAdmin(); set({ successMessage: `${response.data.success.length} Anträge für Bearbeitung gesperrt`, isLoading: false, }); return true; } else { set({ error: getErrorMessage(response), isLoading: false, }); return false; } } catch (error) { set({ error: error instanceof Error ? error.message : "Fehler beim Status-Update", isLoading: false, }); return false; } }, bulkSetNewApplications: async (paIds: string[]) => { if (!get().isAdmin || !get().masterKey) { set({ error: "Admin access required" }); return false; } set({ isLoading: true, error: null }); try { const response = await apiClient.bulkOperation({ pa_ids: paIds, operation: "set_new", }); if (isSuccessResponse(response)) { await get().loadApplicationListAdmin(); set({ successMessage: `${response.data.success.length} Anträge auf "Beantragt" gesetzt`, isLoading: false, }); return true; } else { set({ error: getErrorMessage(response), isLoading: false, }); return false; } } catch (error) { set({ error: error instanceof Error ? error.message : "Fehler beim Status-Update", isLoading: false, }); return false; } }, }), { name: "stupa-application-storage", storage: createJSONStorage(() => localStorage), partialize: (state) => ({ paId: state.paId, paKey: state.paKey, masterKey: state.masterKey, isAdmin: state.isAdmin, }), onRehydrateStorage: () => (state) => { // Immediately set master key in API client after rehydration if (state?.masterKey && state?.isAdmin) { apiClient.setMasterKey(state.masterKey); } }, }, ), ); // Helper functions function createFormJsonFromData(formData: Partial): string { const formJson: Record = {}; // Map form data to PDF field names based on the mapping const fieldMappings: Record = { // Meta paId: "pa-id", paKey: "pa-key", // Applicant applicantType: "pa-applicant-type", institutionType: "pa-institution-type", institutionName: "pa-institution", firstName: "pa-first-name", lastName: "pa-last-name", email: "pa-email", phone: "pa-phone", course: "pa-course", role: "pa-role", // Project projectName: "pa-project-name", startDate: "pa-start-date", endDate: "pa-end-date", participants: "pa-participants", description: "pa-project-description", // Totals requestedAmountEur: "pa-requested-amount-euro-sum", }; // Add basic fields for (const [formKey, pdfKey] of Object.entries(fieldMappings)) { const value = (formData as any)[formKey]; if (value !== undefined && value !== null && value !== "") { formJson[pdfKey] = { "/V": value }; } } // Add participating faculties if (formData.participatingFaculties) { const faculties = formData.participatingFaculties; formJson["pa-participating-faculties-inf"] = { "/V": faculties.inf }; formJson["pa-participating-faculties-esb"] = { "/V": faculties.esb }; formJson["pa-participating-faculties-ls"] = { "/V": faculties.ls }; formJson["pa-participating-faculties-tec"] = { "/V": faculties.tec }; formJson["pa-participating-faculties-tex"] = { "/V": faculties.tex }; formJson["pa-participating-faculties-nxt"] = { "/V": faculties.nxt }; formJson["pa-participating-faculties-open"] = { "/V": faculties.open }; } // Add costs if (formData.costs) { formData.costs.forEach((cost, index) => { const i = index + 1; // 1-based indexing if (cost.name) { formJson[`pa-cost-${i}-name`] = { "/V": cost.name }; } if (cost.amountEur !== undefined) { formJson[`pa-cost-${i}-amount-euro`] = { "/V": cost.amountEur }; } }); } // Add variant-specific fields if (formData.variant === "QSM") { if (formData.financingCode || formData.qsmFinancing) { formJson["pa-qsm-financing"] = { "/V": formData.financingCode || formData.qsmFinancing, }; } if (formData.qsmFlags) { formJson["pa-qsm-stellenfinanzierungen"] = { "/V": formData.qsmFlags.stellenfinanzierungen, }; formJson["pa-qsm-studierende"] = { "/V": formData.qsmFlags.studierende }; formJson["pa-qsm-individuell"] = { "/V": formData.qsmFlags.individuell }; formJson["pa-qsm-exkursion-genehmigt"] = { "/V": formData.qsmFlags.exkursionGenehmigt, }; formJson["pa-qsm-exkursion-bezuschusst"] = { "/V": formData.qsmFlags.exkursionBezuschusst, }; } } else if (formData.variant === "VSM") { if (formData.financingCode || formData.vsmFinancing) { formJson["pa-vsm-financing"] = { "/V": formData.financingCode || formData.vsmFinancing, }; } if (formData.vsmFlags) { formJson["pa-vsm-aufgaben"] = { "/V": formData.vsmFlags.aufgaben }; formJson["pa-vsm-individuell"] = { "/V": formData.vsmFlags.individuell }; } } const jsonString = JSON.stringify(formJson); return btoa(unescape(encodeURIComponent(jsonString))); // Base64 encode with UTF-8 support } function convertPayloadToFormData(payload: RootPayload): Partial { const pa = payload.pa; return { paId: pa.meta.id, paKey: pa.meta.key, applicantType: pa.applicant.type as any, institutionType: pa.applicant.institution.type as any, institutionName: pa.applicant.institution.name || "", firstName: pa.applicant.name.first || "", lastName: pa.applicant.name.last || "", email: pa.applicant.contact.email || "", phone: pa.applicant.contact.phone || "", course: pa.applicant.course as any, role: pa.applicant.role as any, projectName: pa.project.name || "", startDate: pa.project.dates.start || "", endDate: pa.project.dates.end || "", participants: pa.project.participants, description: pa.project.description || "", participatingFaculties: { inf: pa.project.participation.faculties.inf || false, esb: pa.project.participation.faculties.esb || false, ls: pa.project.participation.faculties.ls || false, tec: pa.project.participation.faculties.tec || false, tex: pa.project.participation.faculties.tex || false, nxt: pa.project.participation.faculties.nxt || false, open: pa.project.participation.faculties.open || false, }, costs: (pa.project.costs || []).map((cost) => ({ name: cost.name || "", amountEur: cost.amountEur || 0, })), variant: (pa.project.financing.vsm?.code ? "VSM" : pa.project.financing.qsm?.code ? "QSM" : "COMMON") as Variant, financingCode: pa.project.financing.qsm?.code || pa.project.financing.vsm?.code || "", qsmFinancing: pa.project.financing.qsm?.code as any, qsmFlags: pa.project.financing.qsm?.flags ? { stellenfinanzierungen: pa.project.financing.qsm.flags.stellenfinanzierungen || false, studierende: pa.project.financing.qsm.flags.studierende || false, individuell: pa.project.financing.qsm.flags.individuell || false, exkursionGenehmigt: pa.project.financing.qsm.flags.exkursionGenehmigt || false, exkursionBezuschusst: pa.project.financing.qsm.flags.exkursionBezuschusst || false, } : undefined, vsmFinancing: pa.project.financing.vsm?.code as any, vsmFlags: pa.project.financing.vsm?.flags ? { aufgaben: pa.project.financing.vsm.flags.aufgaben || false, individuell: pa.project.financing.vsm.flags.individuell || false, } : undefined, }; }