diff --git a/frontend/public/Beispiel.csv b/frontend/public/Beispiel.csv new file mode 100644 index 0000000..6f6fa2d --- /dev/null +++ b/frontend/public/Beispiel.csv @@ -0,0 +1,40 @@ +a7B9-kD3f-92Lm-Q1z +Z1x9-3kLp-a8D2-mN7 +pQ4d-T7v2-bK9X-f5G +m3N8-wQ2z-V6bP-tR4 +gH7k-Lp2Q-xD5f-sE9 +R8tV-c2Bq-J7mZ-yK1 +nK5w-D9sP-T3vL-rU6 +Y4dE-q7Wm-hP2k-Js9 +c2Fh-X8nR-L5vq-tG7 +bM6x-pQ3Z-s7Kf-U1h +V9cQ-h3Tn-2KxF-z6P +k5Lr-M8pQ-w2Tg-Y9d +H2sN-v7Qb-c4XK-p8J +q8Wk-R5nD-1zLp-G6y +T7bH-y2Kq-M9vF-c3R +j4Dq-P6sL-t8XG-r1V +F6kP-q1Xv-3TzB-n7H +x9Nf-L2dQ-5vHk-S8p +G3rH-t9Kc-Q4sV-m1Z +s1Vx-B7mQ-8kDt-P5g +P5kZ-w4Sg-2HnV-y9M +L8cJ-q3Vt-7pXh-D2f +d7Xv-T1kL-6mQp-R8s +K2nP-z6Hq-9vBd-J3t +u9Hc-R4sV-5kLm-Q2x +N3vG-y8Kp-1TqD-s6F +h5Qm-C9tB-4vXn-Z7k +W6zF-p2Jc-8KqH-d1L +r2Kp-G5sN-7tVh-M4q +J4mT-x9Hc-2QvP-k8S +f8Pq-D3kV-6zLn-R1y +Q7zD-h1Sg-9KpM-t4V +z6Kx-P5vH-1cQn-Y8d +C9tB-u4Hq-7mKs-p2J +y1Vd-G7nP-3kLq-S6h +M2cL-t5Xh-9vQp-r8K +b4Sg-N6kP-2HqV-J9t +X5nV-q8Tz-1pKd-L3h +e3Kq-R7vH-5nXc-P2m +H1pM-w9Dk-4sQv-T7g \ No newline at end of file diff --git a/frontend/src/components/CircleUpload.tsx b/frontend/src/components/CircleUpload.tsx new file mode 100644 index 0000000..4f9b17a --- /dev/null +++ b/frontend/src/components/CircleUpload.tsx @@ -0,0 +1,97 @@ +import React, { useRef, useState, useCallback } from "react"; + +type CircleUploadProps = { + onFiles?: (files: File[]) => void; + accept?: string; + multiple?: boolean; +}; + +const CircleUpload: React.FC = ({ + onFiles, + accept = "", + multiple = true, +}) => { + const [isDragging, setIsDragging] = useState(false); + const [files, setFiles] = useState([]); + const inputRef = useRef(null); + + const processFiles = useCallback( + (fileList: FileList | null) => { + if (!fileList) return; + const arr = Array.from(fileList); + setFiles(arr); + onFiles?.(arr); + }, + [onFiles] + ); + + const openFileDialog = () => inputRef.current?.click(); + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setIsDragging(false); + processFiles(e.dataTransfer.files); + }; + + const handleDrag = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (e.type === "dragenter" || e.type === "dragover") setIsDragging(true); + else if (e.type === "dragleave") setIsDragging(false); + }; + + return ( +
+
+ (e.key === "Enter" || e.key === " ") && openFileDialog() + } + onDrop={handleDrop} + onDragEnter={handleDrag} + onDragOver={handleDrag} + onDragLeave={handleDrag} + className={[ + "w-48 h-48 rounded-full border-2 border-dashed flex flex-col items-center justify-center text-center px-4 transition-colors cursor-pointer select-none", + "border-blue-400 text-blue-500 bg-blue-50/40 hover:bg-blue-50", + isDragging && "bg-blue-100 border-blue-500", + ].join(" ")} + > + + Dateien hierher ziehen +
+ oder klicken +
+ + Upload + + processFiles(e.target.files)} + /> +
+ + {files.length > 0 && ( +
    + {files.map((f) => ( +
  • + {f.name}{" "} + + ({Math.round(f.size / 1024)} KB) + +
  • + ))} +
+ )} +
+ ); +}; + +export default CircleUpload; diff --git a/frontend/src/components/ImportGUI.tsx b/frontend/src/components/ImportGUI.tsx new file mode 100644 index 0000000..5f76bab --- /dev/null +++ b/frontend/src/components/ImportGUI.tsx @@ -0,0 +1,83 @@ +import React from "react"; +import CircleUpload from "./CircleUpload"; +import { CircleX } from "lucide-react"; + +type ImportGUIProps = { + onClose: () => void; +}; + +const ImportGUI: React.FC = ({ onClose }) => { + return ( +
+ {/* Backdrop */} +
+ + {/* Dialog Panel */} +
+
+
+

+ Losnummern importieren +

+

+ Importieren Sie Losnummern als strukturierte Datei. Unterstützte + Formate: +

+
+ {[".csv"].map((fileFormat) => ( + + {fileFormat} + + ))} +
+ + + +
+ +
+ +
+ +
+ + {/* Placeholder for optional file summary / mapping UI */} +
+ Ausgewählte Dateien werden hier aufgelistet. +
+ + {/* Actions */} +
+ + +
+
+
+ ); +}; + +export default ImportGUI; diff --git a/frontend/src/components/LoginForm.tsx b/frontend/src/components/LoginForm.tsx index 56951d3..e0e52a9 100644 --- a/frontend/src/components/LoginForm.tsx +++ b/frontend/src/components/LoginForm.tsx @@ -40,13 +40,13 @@ const LoginForm: React.FC = ({ onClose, onLoginSuccess }) => { className="block text-sm font-medium text-zinc-800" htmlFor="username" > - Username + Admin Benutzername @@ -55,13 +55,13 @@ const LoginForm: React.FC = ({ onClose, onLoginSuccess }) => { className="mt-3 block text-sm font-medium text-zinc-800" htmlFor="password" > - Password + Admin Passwort diff --git a/frontend/src/components/SubHeaderAdmin.tsx b/frontend/src/components/SubHeaderAdmin.tsx index b6a4399..736e410 100644 --- a/frontend/src/components/SubHeaderAdmin.tsx +++ b/frontend/src/components/SubHeaderAdmin.tsx @@ -1,47 +1,55 @@ import React from "react"; import { Sheet, WholeWord } from "lucide-react"; +import { useState } from "react"; +import ImportGUI from "./ImportGUI"; // Sub navigation bar for admin views: provides import + clear selection actions const SubHeaderAdmin: React.FC = () => { + const [showImport, setShowImport] = useState(false); + return ( -
-
-
-

- Verwaltung -

-
-
- - +

+ Aktionen für Daten in der Datenbank +

+
+
+ + +
-
- + + {showImport && setShowImport(false)} />} + ); }; diff --git a/frontend/src/utils/fileHandler.ts b/frontend/src/utils/fileHandler.ts new file mode 100644 index 0000000..ae41ee8 --- /dev/null +++ b/frontend/src/utils/fileHandler.ts @@ -0,0 +1,11 @@ +export function downloadHandler(filename: string, content: string) { + const blob = new Blob([content], { type: "text/plain" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); +}