Initial scaffold: React/Vite/TS PWA + Express/Prisma/Postgres backend
Build and Deploy / version (push) Successful in 1s
Build and Deploy / build (push) Failing after 1s
Build and Deploy / deploy (push) Skipped

Materialschein & Kabelrechner für BergVOLT Elektrotechnik, portiert von der
vorherigen Single-File-HTML-Version. Microsoft-Entra-ID-Login (MSAL), Projekt-
Dashboard statt einer Datei pro Kunde, Retour-Buchen-Zusammenführung, 111
Kabeltypen aus den offiziellen Meinhart-Datenblättern. gitops/ nach
PROX/deploy-standard: git-backed Portainer-Stack, versionierte Docker-Images,
CI-Commit-back auf gitops/stack.yml.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Fidelis Huber
2026-08-21 17:24:38 +02:00
co-authored by Claude Sonnet 5
commit 13cef3c96d
39 changed files with 1798 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
import { Router } from 'express';
import { prisma } from '../prisma.js';
import type { AuthedRequest } from '../auth.js';
export const materialRouter = Router();
// GET /api/material?projektId=xxx
materialRouter.get('/', async (req, res) => {
const projektId = String(req.query.projektId ?? '');
if (!projektId) { res.status(400).json({ error: 'projektId erforderlich.' }); return; }
const items = await prisma.material.findMany({
where: { projektId },
orderBy: { createdAt: 'asc' }
});
res.json(items);
});
materialRouter.post('/', async (req: AuthedRequest, res) => {
const { projektId, bezeichnung, artNr, menge, einheit, notiz } = req.body ?? {};
if (!projektId || !bezeichnung || menge === undefined || !einheit) {
res.status(400).json({ error: 'projektId, bezeichnung, menge und einheit sind erforderlich.' });
return;
}
const item = await prisma.material.create({
data: {
projektId,
bezeichnung,
artNr: artNr || null,
menge,
einheit,
notiz: notiz || null,
erstelltVon: req.user?.email ?? null
}
});
res.status(201).json(item);
});
materialRouter.patch('/:id', async (req, res) => {
const { id } = req.params;
const { menge, notiz } = req.body ?? {};
const data: Record<string, unknown> = {};
if (menge !== undefined) data.menge = menge;
if (notiz !== undefined) data.notiz = notiz || null;
try {
const item = await prisma.material.update({ where: { id }, data });
res.json(item);
} catch {
res.status(404).json({ error: 'Position nicht gefunden.' });
}
});
materialRouter.delete('/:id', async (req, res) => {
const { id } = req.params;
try {
await prisma.material.delete({ where: { id } });
res.status(204).end();
} catch {
res.status(404).json({ error: 'Position nicht gefunden.' });
}
});
+46
View File
@@ -0,0 +1,46 @@
import { Router } from 'express';
import { prisma } from '../prisma.js';
import type { AuthedRequest } from '../auth.js';
export const projekteRouter = Router();
projekteRouter.get('/', async (_req, res) => {
const projekte = await prisma.projekt.findMany({ orderBy: { createdAt: 'desc' } });
res.json(projekte);
});
projekteRouter.post('/', async (req: AuthedRequest, res) => {
const { titel, auftraggeber, datum, monteur, notiz } = req.body ?? {};
if (!titel || typeof titel !== 'string' || !titel.trim()) {
res.status(400).json({ error: 'Titel (Projekt/Baustelle) ist erforderlich.' });
return;
}
const projekt = await prisma.projekt.create({
data: {
titel: titel.trim(),
auftraggeber: auftraggeber || null,
datum: datum ? new Date(datum) : null,
monteur: monteur || null,
notiz: notiz || null
}
});
res.status(201).json(projekt);
});
projekteRouter.patch('/:id', async (req, res) => {
const { id } = req.params;
const { titel, auftraggeber, datum, monteur, notiz, status } = req.body ?? {};
const data: Record<string, unknown> = {};
if (titel !== undefined) data.titel = titel;
if (auftraggeber !== undefined) data.auftraggeber = auftraggeber || null;
if (datum !== undefined) data.datum = datum ? new Date(datum) : null;
if (monteur !== undefined) data.monteur = monteur || null;
if (notiz !== undefined) data.notiz = notiz || null;
if (status !== undefined) data.status = status;
try {
const projekt = await prisma.projekt.update({ where: { id }, data });
res.json(projekt);
} catch {
res.status(404).json({ error: 'Projekt nicht gefunden.' });
}
});