This commit is contained in:
Andreas Wilms
2025-09-08 18:30:35 +02:00
commit f12cc8b2ce
130 changed files with 16911 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
"use server";
//import { promises as fs } from "fs";
const apiUrl = process.env.NEXT_SERVER_API_URL;
//const sharedFolder = process.env.SHARED_FOLDER;
export async function getVorlagen() {
try {
const response = await fetch(
`${apiUrl}/vorlageFlaechendruck/getAllFlaechendruckVorlagen`
);
if (!response.ok) {
console.error("Response status:", response.status);
throw new Error("Failed to fetch vorlagen");
}
return await response.json();
} catch (error) {
console.error("Error fetching vorlagen:", error);
throw new Error("An error occurred while fetching vorlagen.");
}
}
export async function generateFlaechendruck(formData: FormData) {
const vorlageIds = formData.getAll("vorlageIds") as string[];
const requestBody = {
rootDirectory: "Orders",
vorlageIds: vorlageIds,
};
const response = await fetch(`${apiUrl}/vorlageFlaechendruck/generate`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(requestBody),
cache: "no-store",
});
if (!response.ok) {
const body = await response.text().catch(() => null);
const msg = body ? body : `HTTP ${response.status}`;
return { message: msg, status: false };
} else {
return { message: "Erfolgreiche Flächendruckgenerierung!", status: true };
}
}

View File

@@ -0,0 +1,173 @@
"use client";
import { useState, useEffect } from "react";
import { getVorlagen, generateFlaechendruck } from "./action";
import { toast, Bounce } from "react-toastify";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Label } from "@/components/ui/label";
export default function Page() {
const [vorlagen, setVorlagen] = useState<
{ id: number; name: string; printer: string }[]
>([]);
const [selectedVorlagen, setSelectedVorlagen] = useState<number[]>([]);
const [isUploading, setIsUploading] = useState(false); // Zustand für den Upload
// API-Aufruf, um Vorlagen zu laden
useEffect(() => {
async function fetchData() {
try {
const data = await getVorlagen(); // Server-Action aufrufen
setVorlagen(
data.map(
(item: { id: number; product_type: string; printer: string }) => ({
id: item.id,
name: item.product_type,
printer: item.printer,
})
)
);
} catch (err) {
console.error(err);
}
}
fetchData();
}, []);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (selectedVorlagen.length === 0) {
return;
}
const formData = new FormData();
selectedVorlagen.forEach((id) => {
formData.append("vorlageIds", id.toString());
});
setIsUploading(true);
const res = await generateFlaechendruck(formData);
if (res.status) {
toast.success(res.message, {
position: "bottom-right",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
} else {
toast.error(res.message, {
position: "bottom-right",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
}
setIsUploading(false);
};
return (
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
<>
<form onSubmit={handleSubmit}>
<Card className="w-[350px]">
<CardHeader>
<CardTitle>Generierung Flächendruck</CardTitle>
<CardDescription>
Wählen sie Vorlagen zur Generierung aus.
</CardDescription>
</CardHeader>
<CardContent>
{!isUploading ? (
<div className="grid w-full items-center gap-4">
<div className="flex flex-col space-y-1.5">
<Label htmlFor="framework" className="mb-4">
Vorlagen
</Label>
<div className="flex flex-col space-y-2">
{vorlagen.map((vorlage) => (
<div
key={vorlage.id}
className="flex items-start space-x-2"
>
<input
type="checkbox"
id={`vorlage-${vorlage.id}`}
value={vorlage.id}
onChange={(e) => {
const id = parseInt(e.target.value, 10);
if (e.target.checked) {
setSelectedVorlagen((prev) => [...prev, id]);
} else {
setSelectedVorlagen((prev) =>
prev.filter((v) => v !== id)
);
}
}}
className="mt-1"
/>
<label
htmlFor={`vorlage-${vorlage.id}`}
className="text-gray-700"
>
Printer: {vorlage.printer} - {vorlage.name}
</label>
</div>
))}
</div>
</div>
</div>
) : (
<div className="flex items-center justify-center w-full h-32">
<div role="status">
<svg
aria-hidden="true"
className="w-20 h-20 text-gray-200 animate-spin dark:text-gray-600 fill-black"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill"
/>
</svg>
<span className="sr-only">Loading...</span>
</div>
</div>
)}
</CardContent>
<CardFooter className="flex justify-between">
{!isUploading ? <Button type="submit">Start</Button> : null}
</CardFooter>
</Card>
</form>
</>
</div>
);
}

View File

@@ -0,0 +1,45 @@
"use server";
//import { promises as fs } from "fs";
const apiUrl = process.env.NEXT_SERVER_API_URL;
//const sharedFolder = process.env.SHARED_FOLDER;
export async function getVorlagen() {
try {
const response = await fetch(
`${apiUrl}/VorlageRollenDruck/getAllRollendruckVorlagen`
);
if (!response.ok) {
console.error("Response status:", response.status);
throw new Error("Failed to fetch vorlagen");
}
return await response.json();
} catch (error) {
console.error("Error fetching vorlagen:", error);
throw new Error("An error occurred while fetching vorlagen.");
}
}
export async function uploadFiles(formData: FormData) {
const vorlageIds = formData.getAll("vorlageIds") as string[];
// JSON-Daten erstellen
const requestBody = {
rootDirectory: "Rollendruck",
vorlageIds: vorlageIds,
};
const response = await fetch(`${apiUrl}/VorlageRollenDruck/generate`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(requestBody),
cache: "no-store",
});
if (!response.ok) {
const body = await response.text().catch(() => null);
const msg = body ? body : `HTTP ${response.status}`;
return { message: msg, status: false };
} else {
return { message: "Erfolgreiche Rollendruckgenerierung!", status: true };
}
}

View File

@@ -0,0 +1,180 @@
"use client";
import { useState, useEffect } from "react";
import { getVorlagen, uploadFiles } from "./action";
import { toast, Bounce } from "react-toastify";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Label } from "@/components/ui/label";
export default function Page() {
const [vorlagen, setVorlagen] = useState<
{ id: number; printer: string; height: number; width: number }[]
>([]);
const [selectedVorlagen, setSelectedVorlagen] = useState<number[]>([]);
const [isUploading, setIsUploading] = useState(false); // Zustand für den Upload
// API-Aufruf, um Vorlagen zu laden
useEffect(() => {
async function fetchData() {
try {
const data = await getVorlagen(); // Server-Action aufrufen
setVorlagen(
data.map(
(item: {
id: number;
printer: string;
height: number;
width: number;
}) => ({
id: item.id,
printer: item.printer,
height: item.height,
width: item.width,
})
)
);
} catch (err) {
console.error("Error fetching vorlagen:", err);
}
}
fetchData();
}, []);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (selectedVorlagen.length === 0) {
alert("Please select at least one Vorlage before submitting.");
return;
}
const formData = new FormData();
selectedVorlagen.forEach((id) => {
formData.append("vorlageIds", id.toString()); // Fügt die ausgewählten Vorlagen-IDs hinzu
});
setIsUploading(true);
const res = await uploadFiles(formData);
if (res.status) {
toast.success(res.message, {
position: "bottom-right",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
} else {
toast.error(res.message, {
position: "bottom-right",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
}
setIsUploading(false);
};
return (
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
<>
<form onSubmit={handleSubmit}>
<Card className="w-[350px]">
<CardHeader>
<CardTitle>Generierung Rollendruck</CardTitle>
<CardDescription>
Wählen sie Vorlagen zur Generierung aus.
</CardDescription>
</CardHeader>
<CardContent>
{!isUploading ? (
<div className="grid w-full items-center gap-4">
<div className="flex flex-col space-y-1.5">
<Label htmlFor="framework" className="mb-4">
Vorlagen
</Label>
<div className="flex flex-col space-y-2">
{vorlagen.map((vorlage) => (
<div
key={vorlage.id}
className="flex items-start space-x-2"
>
<input
type="checkbox"
id={`vorlage-${vorlage.id}`}
value={vorlage.id}
onChange={(e) => {
const id = parseInt(e.target.value, 10);
if (e.target.checked) {
setSelectedVorlagen((prev) => [...prev, id]);
} else {
setSelectedVorlagen((prev) =>
prev.filter((v) => v !== id)
);
}
}}
className="mt-1"
/>
<label
htmlFor={`vorlage-${vorlage.id}`}
className="text-gray-700"
>
Printer: {vorlage.printer} - {vorlage.height}mm x{" "}
{vorlage.width}mm
</label>
</div>
))}
</div>
</div>
</div>
) : (
<div className="flex items-center justify-center w-full h-32">
<div role="status">
<svg
aria-hidden="true"
className="w-20 h-20 text-gray-200 animate-spin dark:text-gray-600 fill-black"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill"
/>
</svg>
<span className="sr-only">Loading...</span>
</div>
</div>
)}
</CardContent>
<CardFooter className="flex justify-between">
{!isUploading ? <Button type="submit">Start</Button> : null}
</CardFooter>
</Card>
</form>
</>
</div>
);
}

View File

@@ -0,0 +1,13 @@
import AdminPanelLayout from "@/components/admin-panel/admin-panel-layout";
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<AdminPanelLayout>
<main>{children}</main>
</AdminPanelLayout>
);
}

View File

@@ -0,0 +1,14 @@
export default function Page() {
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100 text-gray-800">
<div className="bg-white shadow-md rounded-lg p-8 max-w-lg text-center">
<h1 className="text-2xl font-bold mb-4 text-gray-900">
Willkommen im Dashboard
</h1>
<h2 className="text-lg text-gray-600">
Navigieren Sie über das Menü auf der linken Seite zu den Anwendungen.
</h2>
</div>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import UploadForm from "@/components/upload-form/page";
export default async function Home() {
return (
<main>
<UploadForm />
</main>
);
}

View File

@@ -0,0 +1,42 @@
"use server";
const apiUrl = process.env.NEXT_SERVER_API_URL;
export async function getTableData() {
try {
const res = await fetch(
`${apiUrl}/vorlageFlaechendruck/getAllFlaechendruckVorlagen`
);
if (!res.ok) {
console.error("Response status:", res.status);
}
const data: {
id: number;
product_type: string;
height: number;
width: number;
printer: string;
coordinates: {
x: number;
y: number;
rotation: number;
}[];
}[] = await res.json();
return data;
} catch (error) {
console.error("Error fetching vorlagen:", error);
throw new Error("An error occurred while fetching vorlagen.");
}
}
export async function deleteVorlageFlaechendruck(id: number) {
const res = await fetch(`${apiUrl}/vorlageFlaechendruck/delete/${id}`, {
method: "DELETE",
});
if (!res.ok) {
throw new Error("Failed to delete item");
}
return;
}

View File

@@ -0,0 +1,83 @@
"use server";
const apiUrl = process.env.NEXT_SERVER_API_URL;
export async function createVorlage(formData: FormData) {
const printer = formData.get("printer") as string;
const product_type = formData.get("product_type") as string;
const height = Number(formData.get("height"));
const width = Number(formData.get("width"));
const coordinatesJson = formData.get("coordinates") as string;
const coordinates = JSON.parse(coordinatesJson);
const payload = {
printer,
product_type,
height,
width,
coordinates,
};
try {
const response = await fetch(
`${apiUrl}/vorlageFlaechendruck/createWithCoordinates`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
cache: "no-store",
}
);
if (!response.ok) {
console.error("Response status:", response.status);
throw new Error("Failed to submit form");
}
} catch (error) {
console.error("Error submitting form:", error);
throw new Error("An error occurred while submitting the form.");
}
}
export async function alterVorlage(formData: FormData) {
const id = Number(formData.get("id"));
const printer = formData.get("printer") as string;
const product_type = formData.get("product_type") as string;
const height = Number(formData.get("height"));
const width = Number(formData.get("width"));
const coordinatesJson = formData.get("coordinates") as string;
const coordinates = JSON.parse(coordinatesJson);
const payload = {
id,
printer,
product_type,
height,
width,
coordinates,
};
try {
const response = await fetch(
`${apiUrl}/vorlageFlaechendruck/alterVorlage`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
cache: "no-store",
}
);
if (!response.ok) {
console.error("Response status:", response.status);
throw new Error("Failed to submit form");
}
} catch (error) {
console.error("Error submitting form:", error);
throw new Error("An error occurred while submitting the form.");
}
}

View File

@@ -0,0 +1,276 @@
"use Client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Separator } from "@/components/ui/separator";
import { Plus } from "lucide-react";
import { useFieldArray } from "react-hook-form";
import { createVorlage } from "./action";
import { useState } from "react";
export default function AddDialog({ onNewEntry }: { onNewEntry: () => void }) {
const [open, setOpen] = useState(false)
const formSchema = z.object({
Drucker: z.string().min(1, {
message: "Druckername muss mindestens 2 Zeichen lang sein.",
}),
Artikeltyp: z.string().min(1, {
message: "Artikeltyp muss mindestens 2 Zeichen lang sein.",
}),
Höhe: z.preprocess(
(value) => (typeof value === "string" ? parseFloat(value) : value),
z.number({
invalid_type_error: "Höhe muss eine gültige Zahl sein. Bspw. 1.5 .",
}).min(1, {
message: "Höhe muss mindestens 1 sein.",
})
),
Breite: z.preprocess(
(value) => (typeof value === "string" ? parseFloat(value) : value),
z.number({
invalid_type_error: "Breite muss eine gültige Zahl sein. Bspw. 1.5 .",
}).min(1, {
message: "Breite muss mindestens 1 sein.",
})
),
Koordinaten: z.array(
z.object({
x: z.preprocess(
(value) => (typeof value === "string" ? parseFloat(value) : value),
z.number().min(0, {
message: "X-Koordinate muss mindestens 0 sein.",
})
),
y: z.preprocess(
(value) => (typeof value === "string" ? parseFloat(value) : value),
z.number().min(0, {
message: "Y-Koordinate muss mindestens 0 sein.",
})
),
rotation: z.preprocess(
(value) => (typeof value === "string" ? parseFloat(value) : value),
z.number().min(0, {
message: "Rotation muss mindestens 0 sein.",
})
),
})
).min(1, {
message: "Es muss mindestens eine Koordinate angegeben werden.",
}),
});
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
Drucker: "",
Artikeltyp: "",
Höhe: 0,
Breite: 0,
Koordinaten: [{ x: 0, y: 0, rotation: 0 }],
},
});
const { fields, append, remove } = useFieldArray({
control: form.control,
name: "Koordinaten",
});
async function onSubmit(values: z.infer<typeof formSchema>) {
const formData = new FormData();
formData.append("printer", values.Drucker);
formData.append("product_type", values.Artikeltyp);
formData.append("height", values.Höhe.toString());
formData.append("width", values.Breite.toString());
formData.append("coordinates", JSON.stringify(values.Koordinaten));
try {
await createVorlage(formData);
setOpen(false);
form.reset();
onNewEntry();
}
catch (error) {
console.error("Error submitting form:", error);
throw new Error("An error occurred while submitting the form.");
}
}
return (
<div>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="outline" className="mt-2 mb-2 border-green-700 text-green-700">
Vorlage hinzufügen
</Button>
</DialogTrigger>
<DialogContent className="max-w-screen-lg max-h-screen overflow-y-auto">
<DialogHeader>
<DialogTitle className="mb-8">Vorlage hinzufügen</DialogTitle>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="Drucker"
render={({ field }) => (
<FormItem>
<FormLabel>Drucker Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<DialogDescription>
Der Name des Druckers, auf dem die Vorlage gedruckt wird.
</DialogDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="Artikeltyp"
render={({ field }) => (
<FormItem>
<FormLabel>Artikel Typ</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<DialogDescription>
Der Name des Artikels, der auf der Vorlage gedruckt wird.
</DialogDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="Höhe"
render={({ field }) => (
<FormItem>
<FormLabel>Höhe</FormLabel>
<FormControl>
<Input type="number" step="any" {...field} />
</FormControl>
<FormDescription>
Die Höhe der Vorlage in Millimeter.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="Breite"
render={({ field }) => (
<FormItem>
<FormLabel>Breite</FormLabel>
<FormControl>
<Input type="number" step="any" {...field} />
</FormControl>
<FormDescription>
Die Breite der Vorlage in Millimeter.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<div className="max-h-64 overflow-y-auto">
<FormLabel className="mb-4">Koordinaten:</FormLabel>
<Separator />
{fields.map((field, index) => (
<div key={field.id} className="space-y-4">
<FormField
control={form.control}
name={`Koordinaten.${index}.x`}
render={({ field }) => (
<FormItem className="mt-4">
<FormLabel>X-Koordinate</FormLabel>
<FormControl>
<Input type="number" step="any" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name={`Koordinaten.${index}.y`}
render={({ field }) => (
<FormItem>
<FormLabel>Y-Koordinate</FormLabel>
<FormControl>
<Input type="number" step="any" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name={`Koordinaten.${index}.rotation`}
render={({ field }) => (
<FormItem>
<FormLabel>Rotation</FormLabel>
<FormControl>
<Input type="number" step="any" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="button"
variant="destructive"
onClick={() => remove(index)}
>
Entfernen
</Button>
<Separator className="mt-4" />
</div>
))}
<Button
type="button"
variant="outline"
onClick={() => append({ x: 0, y: 0, rotation: 0 })}
className="mt-4"
>
<Plus />
</Button>
</div>
<Button type="submit">
Hinzufügen
</Button>
</form>
</Form>
</DialogHeader>
</DialogContent>
</Dialog>
</div>
);
}

View File

@@ -0,0 +1,294 @@
"use Client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Separator } from "@/components/ui/separator";
import { Plus } from "lucide-react";
import { useFieldArray } from "react-hook-form";
import { alterVorlage } from "./action";
import { useState } from "react";
type TableData = {
id: number;
product_type: string;
height: number;
width: number;
printer: string;
coordinates: { x: number; y: number; rotation: number }[];
};
type AlterDialogProps = {
onNewEntry: () => void;
data: TableData;
};
export default function AlterDialog({ onNewEntry, data }: AlterDialogProps) {
const [open, setOpen] = useState(false)
const formSchema = z.object({
Drucker: z.string().min(1, {
message: "Druckername muss mindestens 2 Zeichen lang sein.",
}),
Artikeltyp: z.string().min(1, {
message: "Artikeltyp muss mindestens 2 Zeichen lang sein.",
}),
Höhe: z.preprocess(
(value) => (typeof value === "string" ? parseFloat(value) : value),
z.number({
invalid_type_error: "Höhe muss eine gültige Zahl sein. Bspw. 1.5 .",
}).min(1, {
message: "Höhe muss mindestens 1 sein.",
})
),
Breite: z.preprocess(
(value) => (typeof value === "string" ? parseFloat(value) : value),
z.number({
invalid_type_error: "Breite muss eine gültige Zahl sein. Bspw. 1.5 .",
}).min(1, {
message: "Breite muss mindestens 1 sein.",
})
),
Koordinaten: z.array(
z.object({
x: z.preprocess(
(value) => (typeof value === "string" ? parseFloat(value) : value),
z.number().min(0, {
message: "X-Koordinate muss mindestens 0 sein.",
})
),
y: z.preprocess(
(value) => (typeof value === "string" ? parseFloat(value) : value),
z.number().min(0, {
message: "Y-Koordinate muss mindestens 0 sein.",
})
),
rotation: z.preprocess(
(value) => (typeof value === "string" ? parseFloat(value) : value),
z.number().min(0, {
message: "Rotation muss mindestens 0 sein.",
})
),
})
).min(1, {
message: "Es muss mindestens eine Koordinate angegeben werden.",
}),
});
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
Drucker: data.printer,
Artikeltyp: data.product_type,
Höhe: data.height,
Breite: data.width,
Koordinaten: data.coordinates.map((coord) => ({
x: coord.x,
y: coord.y,
rotation: coord.rotation,
})),
},
});
const { fields, append, remove } = useFieldArray({
control: form.control,
name: "Koordinaten",
});
async function onSubmit(values: z.infer<typeof formSchema>) {
const formData = new FormData();
formData.append("id", data.id.toString());
formData.append("printer", values.Drucker);
formData.append("product_type", values.Artikeltyp);
formData.append("height", values.Höhe.toString());
formData.append("width", values.Breite.toString());
formData.append("coordinates", JSON.stringify(values.Koordinaten));
try {
await alterVorlage(formData);
setOpen(false);
form.reset();
onNewEntry();
}
catch (error) {
console.error("Error submitting form:", error);
throw new Error("An error occurred while submitting the form.");
}
}
return (
<div>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="ghost" className="font-normal pl-2">
Vorlage bearbeiten
</Button>
</DialogTrigger>
<DialogContent className="max-w-screen-lg max-h-screen overflow-y-auto">
<DialogHeader>
<DialogTitle className="mb-8">Vorlage bearbeiten</DialogTitle>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="Drucker"
render={({ field }) => (
<FormItem>
<FormLabel>Drucker Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<DialogDescription>
Der Name des Druckers, auf dem die Vorlage gedruckt wird.
</DialogDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="Artikeltyp"
render={({ field }) => (
<FormItem>
<FormLabel>Artikel Typ</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<DialogDescription>
Der Name des Artikels, der auf der Vorlage gedruckt wird.
</DialogDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="Höhe"
render={({ field }) => (
<FormItem>
<FormLabel>Höhe</FormLabel>
<FormControl>
<Input type="number" step="any" {...field} />
</FormControl>
<FormDescription>
Die Höhe der Vorlage in Millimeter.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="Breite"
render={({ field }) => (
<FormItem>
<FormLabel>Breite</FormLabel>
<FormControl>
<Input type="number" step="any" {...field} />
</FormControl>
<FormDescription>
Die Breite der Vorlage in Millimeter.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<div className="max-h-64 overflow-y-auto">
<FormLabel className="mb-4">Koordinaten:</FormLabel>
<Separator />
{fields.map((field, index) => (
<div key={field.id} className="space-y-4">
<FormField
control={form.control}
name={`Koordinaten.${index}.x`}
render={({ field }) => (
<FormItem className="mt-4">
<FormLabel>X-Koordinate</FormLabel>
<FormControl>
<Input type="number" step="any" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name={`Koordinaten.${index}.y`}
render={({ field }) => (
<FormItem>
<FormLabel>Y-Koordinate</FormLabel>
<FormControl>
<Input type="number" step="any" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name={`Koordinaten.${index}.rotation`}
render={({ field }) => (
<FormItem>
<FormLabel>Rotation</FormLabel>
<FormControl>
<Input type="number" step="any" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="button"
variant="destructive"
onClick={() => remove(index)}
>
Entfernen
</Button>
<Separator className="mt-4" />
</div>
))}
<Button
type="button"
variant="outline"
onClick={() => append({ x: 0, y: 0, rotation: 0 })}
className="mt-4"
>
<Plus />
</Button>
</div>
<Button type="submit">
Änderungen speichern
</Button>
</form>
</Form>
</DialogHeader>
</DialogContent>
</Dialog>
</div>
);
}

View File

@@ -0,0 +1,199 @@
"use client";
import { useEffect, useState } from "react";
import { Ellipsis } from "lucide-react"
import { getTableData, deleteVorlageFlaechendruck } from "./action";
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import AddDialog from "./components/addDialog";
import AlterDialog from "./components/alterDialog";
type TableData = {
id: number;
product_type: string;
height: number;
width: number;
printer: string;
coordinates: { x: number; y: number; rotation: number }[];
};
export default function Page() {
const [data, setData] = useState<TableData[]>([]);
useEffect(() => {
async function fetchData() {
try {
const result = await getTableData();
setData(result);
} catch (error) {
console.error("Fehler beim Laden der Daten:", error);
}
}
fetchData();
}, []);
const handleNewEntry = async () => {
try {
const result = await getTableData(); // Daten erneut abrufen
setData(result); // Tabelle aktualisieren
} catch (error) {
console.error("Fehler beim Aktualisieren der Daten:", error);
}
};
const deleteEntry = async (id: number) => {
try {
await deleteVorlageFlaechendruck(id);
await handleNewEntry();
}
catch (error) {
console.error("Fehler beim Löschen der Daten:", error);
}
};
return (
<div className="min-h-screen bg-gray-50 p-6">
<div className="max-w-5xl mx-auto">
{/* Table Section */}
<Table className="bg-white shadow-md border border-gray-300">
<TableCaption>Vorlagen Flächendruck</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">Drucker</TableHead>
<TableHead>Artikel Typ</TableHead>
<TableHead>Höhe</TableHead>
<TableHead>Breite</TableHead>
<TableHead>Koordinaten</TableHead>
<TableHead className="text-right">
<AddDialog onNewEntry={handleNewEntry} />
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((item) => (
<TableRow key={item.id}>
<TableCell>{item.printer}</TableCell>
<TableCell>{item.product_type}</TableCell>
<TableCell>{item.height}</TableCell>
<TableCell >{item.width}</TableCell>
<TableCell>
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Anzeigen</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Koordinaten</DialogTitle>
<DialogDescription>
Hier sind die Koordinaten für die ausgewählte Vorlage. X und Y Werte in Millimetern.
</DialogDescription>
</DialogHeader>
<Table>
<TableCaption>Koordinaten Tabelle</TableCaption>
<TableHeader>
<TableRow>
<TableHead>X</TableHead>
<TableHead>Y</TableHead>
<TableHead className="text-right">Rotation</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{item.coordinates.map((coordi) => (
<TableRow key={crypto.randomUUID()}>
<TableCell>{coordi.x}</TableCell>
<TableCell>{coordi.y}</TableCell>
<TableCell className="text-right">{coordi.rotation}°</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</DialogContent>
</Dialog>
</TableCell>
<TableCell className="text-right">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<Ellipsis />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<AlterDialog onNewEntry={handleNewEntry} data={item} />
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
<AlertDialog>
<AlertDialogTrigger
onClick={(event) => {
event.stopPropagation(); // Verhindert das Schließen des Dropdown-Menüs
}}
>
Eintrag Löschen
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Sind Sie sich sicher?</AlertDialogTitle>
<AlertDialogDescription>
Diese Aktion ist permanent und kann nicht rückgängig gemacht werden.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Abbrechen</AlertDialogCancel>
<AlertDialogAction
onClick={() => {
deleteEntry(item.id); // Eintrag löschen
}}
>
Löschen
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
);
}

View File

@@ -0,0 +1,69 @@
"use server";
const apiUrl = process.env.NEXT_SERVER_API_URL;
export async function getTableData() {
try {
const res = await fetch(
`${apiUrl}/VorlageRollenDruck/getAllRollendruckVorlagen`
);
if (!res.ok) {
console.error("Response status:", res.status);
}
const data: {
id: number;
height: number;
width: number;
printer: string;
articleTypes: string;
}[] = await res.json();
return data;
} catch (error) {
console.error("Error fetching vorlagen:", error);
throw new Error("An error occurred while fetching vorlagen.");
}
}
export async function deleteVorlageRollendruck(id: number) {
const res = await fetch(`${apiUrl}/VorlageRollenDruck/delete/${id}`, {
method: "DELETE",
});
if (!res.ok) {
throw new Error("Failed to delete item");
}
return;
}
export async function getTableDataDupli() {
try {
const res = await fetch(
`${apiUrl}/VorlageRollenDruck/getAllDupliArtikel`
);
if (!res.ok) {
console.error("Response status:", res.status);
}
const data: {
id: number;
product_type: string;
}[] = await res.json();
return data;
} catch (error) {
console.error("Error fetching vorlagen:", error);
throw new Error("An error occurred while fetching vorlagen.");
}
}
export async function deleteDupliEntry(id: number) {
const res = await fetch(`${apiUrl}/VorlageRollenDruck/deleteDupli/${id}`, {
method: "DELETE",
});
if (!res.ok) {
throw new Error("Failed to delete item");
}
return;
}

View File

@@ -0,0 +1,107 @@
"use server";
const apiUrl = process.env.NEXT_SERVER_API_URL;
export async function createVorlage(formData: FormData) {
const printer = formData.get("printer") as string;
const height = Number(formData.get("height"));
const width = Number(formData.get("width"));
const articleTypes = formData.get("articleTypes") as string;
const payload = {
printer,
height,
width,
articleTypes
};
try {
const response = await fetch(
`${apiUrl}/VorlageRollenDruck/createRollenDruckVorlage`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
cache: "no-store",
}
);
if (!response.ok) {
console.error("Response status:", response.status);
throw new Error("Failed to submit form");
}
} catch (error) {
console.error("Error submitting form:", error);
throw new Error("An error occurred while submitting the form.");
}
}
export async function alterVorlage(formData: FormData) {
const id = Number(formData.get("id"));
const printer = formData.get("printer") as string;
const height = Number(formData.get("height"));
const width = Number(formData.get("width"));
const articleTypes = formData.get("articleTypes") as string;
const payload = {
id,
printer,
height,
width,
articleTypes
};
try {
const response = await fetch(
`${apiUrl}/VorlageRollenDruck/alterVorlage`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
cache: "no-store",
}
);
if (!response.ok) {
console.error("Response status:", response.status);
throw new Error("Failed to submit form");
}
} catch (error) {
console.error("Error submitting form:", error);
throw new Error("An error occurred while submitting the form.");
}
}
export async function addDupliArtikel(fromData: FormData) {
const artikelTyp = fromData.get("ArtikelTyp") as string;
const payload = {
product_type: artikelTyp,
};
try {
const response = await fetch(
`${apiUrl}/VorlageRollenDruck/addDupliArtikel`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
cache: "no-store",
}
);
if (!response.ok) {
console.error("Response status:", response.status);
throw new Error("Failed to submit form");
}
} catch (error) {
console.error("Error submitting form:", error);
throw new Error("An error occurred while submitting the form.");
}
}

View File

@@ -0,0 +1,177 @@
"use Client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { createVorlage } from "./action";
import { useState } from "react";
export default function AddDialog({ onNewEntry }: { onNewEntry: () => void }) {
const [open, setOpen] = useState(false)
const formSchema = z.object({
Drucker: z.string().min(1, {
message: "Druckername muss mindestens 2 Zeichen lang sein.",
}),
Höhe: z.preprocess(
(value) => (typeof value === "string" ? parseFloat(value) : value),
z.number({
invalid_type_error: "Höhe muss eine gültige Zahl sein. Bspw. 1.5 .",
}).min(1, {
message: "Höhe muss mindestens 1 sein.",
})
),
Breite: z.preprocess(
(value) => (typeof value === "string" ? parseFloat(value) : value),
z.number({
invalid_type_error: "Breite muss eine gültige Zahl sein. Bspw. 1.5 .",
}).min(1, {
message: "Breite muss mindestens 1 sein.",
})
),
ArtikelTypen: z
.string()
.regex(/^(\s*\w+\s*)(,\s*\w+\s*)*$/, {
message: "Bitte geben Sie eine durch Kommas getrennte Liste ein (z.B. te, bs, s, s, s)",
}),
});
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
Drucker: "",
Höhe: 0,
Breite: 0,
ArtikelTypen: "",
},
});
async function onSubmit(values: z.infer<typeof formSchema>) {
const formData = new FormData();
formData.append("printer", values.Drucker);
formData.append("height", values.Höhe.toString());
formData.append("width", values.Breite.toString());
formData.append("articleTypes", values.ArtikelTypen);
try {
await createVorlage(formData);
setOpen(false);
form.reset();
onNewEntry();
}
catch (error) {
console.error("Error submitting form:", error);
throw new Error("An error occurred while submitting the form.");
}
}
return (
<div>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="outline" className="mt-2 mb-2 border-green-700 text-green-700">
Vorlage hinzufügen
</Button>
</DialogTrigger>
<DialogContent className="max-w-screen-lg max-h-screen overflow-y-auto">
<DialogHeader>
<DialogTitle className="mb-8">Vorlage hinzufügen</DialogTitle>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="Drucker"
render={({ field }) => (
<FormItem>
<FormLabel>Drucker Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<DialogDescription>
Der Name des Druckers, auf dem die Vorlage gedruckt wird.
</DialogDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="Höhe"
render={({ field }) => (
<FormItem>
<FormLabel>Höhe</FormLabel>
<FormControl>
<Input type="number" step="any" {...field} />
</FormControl>
<FormDescription>
Die Höhe der Vorlage in Millimeter.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="Breite"
render={({ field }) => (
<FormItem>
<FormLabel>Breite</FormLabel>
<FormControl>
<Input type="number" step="any" {...field} />
</FormControl>
<FormDescription>
Die Breite der Vorlage in Millimeter.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="ArtikelTypen"
render={({ field }) => (
<FormItem>
<FormLabel>Artikel Typen</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
Geben Sie die Artikeltypen als durch Kommas getrennte Liste ein (z.B. te, bs, s, s, s).
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">
Hinzufügen
</Button>
</form>
</Form>
</DialogHeader>
</DialogContent>
</Dialog>
</div>
);
}

View File

@@ -0,0 +1,99 @@
"use Client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import {addDupliArtikel} from "./action";
import { useState } from "react";
import {Plus} from "lucide-react";
export default function AddDialog({ onNewEntry }: { onNewEntry: () => void }) {
const [open, setOpen] = useState(false)
const formSchema = z.object({
ArtikelTyp: z.string().min(1, {
message: "Druckername muss mindestens 2 Zeichen lang sein.",
}),
});
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
ArtikelTyp: "",
},
});
async function onSubmit(values: z.infer<typeof formSchema>) {
const formData = new FormData();
formData.append("ArtikelTyp", values.ArtikelTyp);
try {
await addDupliArtikel(formData);
setOpen(false);
form.reset();
onNewEntry();
}
catch (error) {
console.error("Error submitting form:", error);
throw new Error("An error occurred while submitting the form.");
}
}
return (
<div>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant={"outline"} className="border-green-700 text-green-700 mt-2 mb-2" ><Plus /></Button>
</DialogTrigger>
<DialogContent className="max-w-screen-lg max-h-screen overflow-y-auto">
<DialogHeader>
<DialogTitle className="mb-8">ArtikelTyp hinzufügen</DialogTitle>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="ArtikelTyp"
render={({ field }) => (
<FormItem>
<FormLabel>ArtikelTyp</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<DialogDescription>
Der Typ des Artikels, der dupliziert gedruckt werden soll.
</DialogDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">
Hinzufügen
</Button>
</form>
</Form>
</DialogHeader>
</DialogContent>
</Dialog>
</div>
);
}

View File

@@ -0,0 +1,190 @@
"use Client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { alterVorlage } from "./action";
import { useState } from "react";
type TableData = {
id: number;
printer: string;
height: number;
width: number;
articleTypes: string;
};
type AlterDialogProps = {
onNewEntry: () => void;
data: TableData;
};
export default function AlterDialog({ onNewEntry, data }: AlterDialogProps) {
const [open, setOpen] = useState(false)
const formSchema = z.object({
Drucker: z.string().min(1, {
message: "Druckername muss mindestens 2 Zeichen lang sein.",
}),
Höhe: z.preprocess(
(value) => (typeof value === "string" ? parseFloat(value) : value),
z.number({
invalid_type_error: "Höhe muss eine gültige Zahl sein. Bspw. 1.5 .",
}).min(1, {
message: "Höhe muss mindestens 1 sein.",
})
),
Breite: z.preprocess(
(value) => (typeof value === "string" ? parseFloat(value) : value),
z.number({
invalid_type_error: "Breite muss eine gültige Zahl sein. Bspw. 1.5 .",
}).min(1, {
message: "Breite muss mindestens 1 sein.",
})
),
ArtikelTypen: z
.string()
.regex(/^(\s*\w+\s*)(,\s*\w+\s*)*$/, {
message: "Bitte geben Sie eine durch Kommas getrennte Liste ein (z.B. te, bs, s, s, s)",
}),
});
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
Drucker: data.printer,
Höhe: data.height,
Breite: data.width,
ArtikelTypen: data.articleTypes,
},
});
async function onSubmit(values: z.infer<typeof formSchema>) {
const formData = new FormData();
formData.append("id", data.id.toString());
formData.append("printer", values.Drucker);
formData.append("height", values.Höhe.toString());
formData.append("width", values.Breite.toString());
formData.append("articleTypes", values.ArtikelTypen);
try {
await alterVorlage(formData);
setOpen(false);
form.reset();
onNewEntry();
}
catch (error) {
console.error("Error submitting form:", error);
throw new Error("An error occurred while submitting the form.");
}
}
return (
<div>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="ghost" className="font-normal pl-2">
Vorlage bearbeiten
</Button>
</DialogTrigger>
<DialogContent className="max-w-screen-lg max-h-screen overflow-y-auto">
<DialogHeader>
<DialogTitle className="mb-8">Vorlage bearbeiten</DialogTitle>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="Drucker"
render={({ field }) => (
<FormItem>
<FormLabel>Drucker Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<DialogDescription>
Der Name des Druckers, auf dem die Vorlage gedruckt wird.
</DialogDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="Höhe"
render={({ field }) => (
<FormItem>
<FormLabel>Höhe</FormLabel>
<FormControl>
<Input type="number" step="any" {...field} />
</FormControl>
<FormDescription>
Die Höhe der Vorlage in Millimeter.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="Breite"
render={({ field }) => (
<FormItem>
<FormLabel>Breite</FormLabel>
<FormControl>
<Input type="number" step="any" {...field} />
</FormControl>
<FormDescription>
Die Breite der Vorlage in Millimeter.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="ArtikelTypen"
render={({ field }) => (
<FormItem>
<FormLabel>Artikel Typen</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
Geben Sie eine durch Kommas getrennte Liste von Artikeltypen ein (z.B. te, bs, s, s, s).
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">
Änderungen speichern
</Button>
</form>
</Form>
</DialogHeader>
</DialogContent>
</Dialog>
</div>
);
}

View File

@@ -0,0 +1,227 @@
"use client";
import { useEffect, useState } from "react";
import { Ellipsis } from "lucide-react"
import { getTableData, deleteVorlageRollendruck, getTableDataDupli, deleteDupliEntry } from "./action";
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import AddDialog from "./components/addDialog";
import AlterDialog from "./components/alterDialog";
import AddDialogDupli from "./components/addDialogDupli";
type TableData = {
id: number;
printer: string;
height: number;
width: number;
articleTypes: string;
};
export default function Page() {
const [data, setData] = useState<TableData[]>([]);
const [dupliData, setDupliData] = useState<{ id: number; product_type: string }[]>([]);
useEffect(() => {
async function fetchData() {
try {
const result = await getTableData();
setData(result || []);
const dupliResult = await getTableDataDupli();
setDupliData(Array.isArray(dupliResult) ? dupliResult : []);
} catch (error) {
console.error("Fehler beim Laden der Daten:", error);
}
}
fetchData();
}, []);
const handleNewEntry = async () => {
try {
const result = await getTableData(); // Daten erneut abrufen
setData(result); // Tabelle aktualisieren
} catch (error) {
console.error("Fehler beim Aktualisieren der Daten:", error);
}
};
const handleNewEntry2 = async () => {
try {
const result = await getTableDataDupli(); // Daten erneut abrufen
setDupliData(result) // Tabelle aktualisieren
} catch (error) {
console.error("Fehler beim Aktualisieren der Daten:", error);
}
};
const deleteEntry = async (id: number) => {
try {
await deleteVorlageRollendruck(id);
await handleNewEntry();
}
catch (error) {
console.error("Fehler beim Löschen der Daten:", error);
}
};
const deleteEntryDupli = async (id: number) => {
try {
await deleteDupliEntry(id);
await handleNewEntry2();
}
catch (error) {
console.error("Fehler beim Löschen der Daten:", error);
}
}
return (
<div className="min-h-screen bg-gray-50 p-6">
<div className="flex w-full max-w-6xl mx-auto gap-2">
<div className="w-1/3 flex-shrink-0">
<Table className="bg-white shadow-md border border-gray-300">
<TableCaption>Artikel für Duplizierung</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">Artikel Typ</TableHead>
<TableHead className="text-right">
<AddDialogDupli onNewEntry={handleNewEntry2} />
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{dupliData.map((item) => (
<TableRow key={item.id}>
<TableCell>{item.product_type}</TableCell>
<TableCell className="text-right">
<Button onClick={() => {
deleteEntryDupli(item.id); // Eintrag löschen
}} variant={"outline"} className="border-red-700 text-red-700 h-6">Entfernen</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<div className="flex-1">
<Table className="bg-white shadow-md border border-gray-300">
<TableCaption>Vorlagen Rollendruck</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">Drucker</TableHead>
<TableHead>Höhe</TableHead>
<TableHead>Breite</TableHead>
<TableHead>Artikel Typen</TableHead>
<TableHead className="text-right">
<AddDialog onNewEntry={handleNewEntry} />
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((item) => (
<TableRow key={item.id}>
<TableCell>{item.printer}</TableCell>
<TableCell>{item.height}</TableCell>
<TableCell>{item.width}</TableCell>
<TableCell>
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Anzeigen</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Artikel Typen</DialogTitle>
<DialogDescription>
Hier sind die Artikel Typen gelistet, die von der Vorlage unterstützt werden.
</DialogDescription>
</DialogHeader>
{item.articleTypes}
</DialogContent>
</Dialog>
</TableCell>
<TableCell className="text-right">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<Ellipsis />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<AlterDialog onNewEntry={handleNewEntry} data={item} />
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
<AlertDialog>
<AlertDialogTrigger
onClick={(event) => {
event.stopPropagation(); // Verhindert das Schließen des Dropdown-Menüs
}}
>
Eintrag Löschen
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Sind Sie sich sicher?</AlertDialogTitle>
<AlertDialogDescription>
Diese Aktion ist permanent und kann nicht rückgängig gemacht werden.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Abbrechen</AlertDialogCancel>
<AlertDialogAction
onClick={() => {
deleteEntry(item.id); // Eintrag löschen
}}
>
Löschen
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</div>
);
}

BIN
frontend/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

122
frontend/app/globals.css Normal file
View File

@@ -0,0 +1,122 @@
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.147 0.004 49.25);
--card: oklch(1 0 0);
--card-foreground: oklch(0.147 0.004 49.25);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.147 0.004 49.25);
--primary: oklch(0.216 0.006 56.043);
--primary-foreground: oklch(0.985 0.001 106.423);
--secondary: oklch(0.97 0.001 106.424);
--secondary-foreground: oklch(0.216 0.006 56.043);
--muted: oklch(0.97 0.001 106.424);
--muted-foreground: oklch(0.553 0.013 58.071);
--accent: oklch(0.97 0.001 106.424);
--accent-foreground: oklch(0.216 0.006 56.043);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.923 0.003 48.717);
--input: oklch(0.923 0.003 48.717);
--ring: oklch(0.709 0.01 56.259);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0.001 106.423);
--sidebar-foreground: oklch(0.147 0.004 49.25);
--sidebar-primary: oklch(0.216 0.006 56.043);
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
--sidebar-accent: oklch(0.97 0.001 106.424);
--sidebar-accent-foreground: oklch(0.216 0.006 56.043);
--sidebar-border: oklch(0.923 0.003 48.717);
--sidebar-ring: oklch(0.709 0.01 56.259);
}
.dark {
--background: oklch(0.147 0.004 49.25);
--foreground: oklch(0.985 0.001 106.423);
--card: oklch(0.216 0.006 56.043);
--card-foreground: oklch(0.985 0.001 106.423);
--popover: oklch(0.216 0.006 56.043);
--popover-foreground: oklch(0.985 0.001 106.423);
--primary: oklch(0.923 0.003 48.717);
--primary-foreground: oklch(0.216 0.006 56.043);
--secondary: oklch(0.268 0.007 34.298);
--secondary-foreground: oklch(0.985 0.001 106.423);
--muted: oklch(0.268 0.007 34.298);
--muted-foreground: oklch(0.709 0.01 56.259);
--accent: oklch(0.268 0.007 34.298);
--accent-foreground: oklch(0.985 0.001 106.423);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.553 0.013 58.071);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.216 0.006 56.043);
--sidebar-foreground: oklch(0.985 0.001 106.423);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
--sidebar-accent: oklch(0.268 0.007 34.298);
--sidebar-accent-foreground: oklch(0.985 0.001 106.423);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.553 0.013 58.071);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}

49
frontend/app/layout.tsx Normal file
View File

@@ -0,0 +1,49 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { ToastContainer, Bounce } from "react-toastify";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "ERP - Wolga Kreativ",
description: "Wolga ERP",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<ToastContainer
position="bottom-right"
autoClose={5000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick={false}
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
theme="light"
transition={Bounce}
/>
{children}
</body>
</html>
);
}

View File

@@ -0,0 +1,9 @@
import SignInPage from "@/components/auth/signin-form";
export default function Page() {
return (
<>
<SignInPage></SignInPage>
</>
);
}