diff --git a/.dockerignore b/.dockerignore
index 80054d0..69951f8 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,8 +1,8 @@
.git
+.agents
+.codex
.next
+data
dist
node_modules
-data
-*.db
-.env
-.env.*
+npm-debug.log*
diff --git a/Dockerfile b/Dockerfile
index 41a08fd..70e7761 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,14 +1,12 @@
-FROM node:22
+FROM node:22-bookworm-slim
+
WORKDIR /app
-COPY package*.json ./
+ENV NEXT_TELEMETRY_DISABLED=1
+
+COPY package.json package-lock.json ./
RUN npm ci
COPY . .
-RUN npm run build:api && npm run build:web
-RUN mkdir -p data && chmod +x scripts/docker-start.sh
-
-EXPOSE 3001
-
-CMD ["sh", "scripts/docker-start.sh"]
+EXPOSE 3000 3001
diff --git a/scripts/docker-start.sh b/scripts/docker-start.sh
deleted file mode 100644
index bf0d856..0000000
--- a/scripts/docker-start.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/sh
-set -e
-
-echo "Running migrations..."
-node scripts/run-migrations.js
-
-echo "Starting API server on :3000..."
-node dist/server/index.js &
-
-echo "Waiting for API..."
-until node -e "require('http').get('http://localhost:3000/health', r => process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))" 2>/dev/null; do
- sleep 1
-done
-
-echo "Starting Next.js on :3001..."
-exec node_modules/.bin/next start -p 3001
diff --git a/scripts/run-migrations.js b/scripts/run-migrations.js
deleted file mode 100644
index ebbed8a..0000000
--- a/scripts/run-migrations.js
+++ /dev/null
@@ -1,16 +0,0 @@
-const { migrate } = require('drizzle-orm/better-sqlite3/migrator');
-const { drizzle } = require('drizzle-orm/better-sqlite3');
-const Database = require('better-sqlite3');
-const fs = require('node:fs');
-const path = require('node:path');
-
-const dataDir = path.resolve('data');
-if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
-
-const sqlite = new Database(path.join(dataDir, 'leistungsbilanz.db'));
-const db = drizzle(sqlite);
-
-migrate(db, { migrationsFolder: path.resolve('src/db/migrations') });
-sqlite.close();
-
-console.log('Migrations applied');
diff --git a/src/app/globals.css b/src/app/globals.css
index 22da5df..c73e0f8 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1396,68 +1396,3 @@ a.kpi:hover {
max-height: calc(100vh - 5.5rem);
}
}
-
-/* ── Seitenleiste: eingeklappter Zustand ─────────────────────────────── */
-
-.app-shell {
- transition: grid-template-columns 0.18s ease;
-}
-
-.app-shell-collapsed {
- grid-template-columns: 64px 1fr;
-}
-
-.sidebar {
- transition: padding 0.18s ease;
-}
-
-.sidebar-brand {
- display: flex;
- align-items: flex-start;
- justify-content: space-between;
- gap: 8px;
-}
-
-.sidebar-toggle {
- background: rgba(255, 255, 255, 0.08);
- border: 0;
- border-radius: 6px;
- color: #fff;
- cursor: pointer;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- font-size: 0.8rem;
- line-height: 1;
- width: 26px;
- height: 26px;
-}
-
-.sidebar-toggle:hover {
- background: rgba(255, 255, 255, 0.18);
-}
-
-.sidebar-collapsed .sidebar-brand {
- padding: 16px 10px;
- flex-direction: column;
- align-items: center;
-}
-
-.sidebar-collapsed .sidebar-brand-text {
- display: none;
-}
-
-.sidebar-collapsed .sidebar-nav {
- padding: 12px 8px;
-}
-
-.sidebar-collapsed .sidebar-link {
- justify-content: center;
- padding: 10px;
-}
-
-.sidebar-collapsed .sidebar-section-label,
-.sidebar-collapsed .sidebar-label {
- display: none;
-}
diff --git a/src/app/projects/page.tsx b/src/app/projects/page.tsx
index 21409fd..93a2d14 100644
--- a/src/app/projects/page.tsx
+++ b/src/app/projects/page.tsx
@@ -14,7 +14,6 @@ import type {
CreateGlobalDeviceInput,
GlobalDeviceDto,
} from "../../frontend/types";
-import { GzfPickerModal } from "../../frontend/components/gzf-picker-modal";
const PROJECTS_CHANGED_EVENT = "leistungsbilanz:projects-changed";
@@ -68,7 +67,6 @@ export default function ProjectsPage() {
id: string;
name: string;
} | null>(null);
- const [isGzfPickerOpen, setIsGzfPickerOpen] = useState(false);
async function loadData() {
setError(null);
@@ -382,28 +380,18 @@ export default function ProjectsPage() {
}
/>
-
-
-
- setGlobalDeviceForm((current) => ({ ...current, demandFactor: event.target.value }))
- }
- />
-
-
+
+
+ setGlobalDeviceForm((current) => ({ ...current, demandFactor: event.target.value }))
+ }
+ />
-
- {isGzfPickerOpen ? (
-
setIsGzfPickerOpen(false)}
- onSelect={(value) => {
- setGlobalDeviceForm((current) => ({ ...current, demandFactor: String(value) }));
- setIsGzfPickerOpen(false);
- }}
- />
- ) : null}
);
}
diff --git a/src/frontend/components/AppShell.tsx b/src/frontend/components/AppShell.tsx
index bbfa0ae..ba08175 100644
--- a/src/frontend/components/AppShell.tsx
+++ b/src/frontend/components/AppShell.tsx
@@ -1,36 +1,21 @@
"use client";
-import { useEffect, useState } from "react";
import { usePathname } from "next/navigation";
import { Sidebar } from "./Sidebar";
const PROJECT_AREA_PATTERN = /^\/projects(\/[^/]+)?$/;
-const SIDEBAR_COLLAPSED_STORAGE_KEY = "leistungsbilanz:sidebar-collapsed";
export function AppShell({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
const showSidebar = PROJECT_AREA_PATTERN.test(pathname ?? "");
- const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
-
- useEffect(() => {
- setIsSidebarCollapsed(window.localStorage.getItem(SIDEBAR_COLLAPSED_STORAGE_KEY) === "1");
- }, []);
-
- function toggleSidebarCollapsed() {
- setIsSidebarCollapsed((current) => {
- const next = !current;
- window.localStorage.setItem(SIDEBAR_COLLAPSED_STORAGE_KEY, next ? "1" : "0");
- return next;
- });
- }
if (!showSidebar) {
return <>{children}>;
}
return (
-
-
+
);
diff --git a/src/frontend/components/Sidebar.tsx b/src/frontend/components/Sidebar.tsx
index b3cbb4f..d84141f 100644
--- a/src/frontend/components/Sidebar.tsx
+++ b/src/frontend/components/Sidebar.tsx
@@ -15,12 +15,7 @@ const PROJECT_SECTIONS = [
{ id: "projektgeraete", label: "Projektgeräte", icon: "⚙" },
] as const;
-interface SidebarProps {
- isCollapsed: boolean;
- onToggleCollapsed: () => void;
-}
-
-export function Sidebar({ isCollapsed, onToggleCollapsed }: SidebarProps) {
+export function Sidebar() {
const pathname = usePathname();
const projectMatch = pathname?.match(/^\/projects\/([^/]+)$/);
const activeProjectId = projectMatch ? projectMatch[1] : null;
@@ -72,31 +67,19 @@ export function Sidebar({ isCollapsed, onToggleCollapsed }: SidebarProps) {
}, [activeProjectId]);
return (
-
- {isGzfPickerOpen ? (
- setIsGzfPickerOpen(false)}
- onSelect={(value) => {
- update("simultaneityFactor", String(value));
- setIsGzfPickerOpen(false);
- }}
- />
- ) : null}
- >
);
}
diff --git a/src/shared/constants/amev-gzf.ts b/src/shared/constants/amev-gzf.ts
deleted file mode 100644
index 0833091..0000000
--- a/src/shared/constants/amev-gzf.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-export interface AmevGzfEntry {
- group: string;
- range: string;
- mean: number;
- overallRange?: string;
-}
-
-// Quelle: AMEV „Planungshilfe für elektrische Leistungsbilanzen" (rev. 4),
-// Tabelle 3 – Gleichzeitigkeitsfaktoren für Verbrauchergruppen.
-export const AMEV_GZF_TABLE: AmevGzfEntry[] = [
- { group: "Absaugung, Digestorien", range: "0,7", mean: 0.7 },
- { group: "Aufzüge/Rolltreppen", range: "0,2 – 0,7", mean: 0.5, overallRange: "0,2" },
- { group: "Beleuchtungsanlagen", range: "0,7 – 0,8", mean: 0.75, overallRange: "0,4" },
- { group: "Beleuchtungsanlagen in innenliegenden Räumen", range: "0,7 – 0,9", mean: 0.8 },
- { group: "IuK-Anlagen", range: "1", mean: 1, overallRange: "1" },
- { group: "Experimentieranlagen", range: "0,2 – 0,4", mean: 0.3 },
- { group: "Heizung", range: "0,7 – 1", mean: 0.85 },
- { group: "Kälteanlagen", range: "0,8 – 1", mean: 0.9, overallRange: "0,4 – 0,5" },
- { group: "Küchen elektrisch ohne Energieoptimierung", range: "0,3 – 0,7", mean: 0.6 },
- { group: "Küchen elektrisch mit Energieoptimierung", range: "0,2 – 0,6", mean: 0.4 },
- { group: "Lastenaufzüge, Krananlagen", range: "0,2", mean: 0.2 },
- {
- group: "Lüftungsanlagen, kontrollierte Lüftung",
- range: "0,7",
- mean: 0.7,
- overallRange: "0,4 – 0,5",
- },
- {
- group: "Steckdosen 230 V allg. Verbraucher (100 W/Steckdose)",
- range: "0,1 – 0,3",
- mean: 0.2,
- },
- { group: "Steckdosen 230 V für IuK (100 W/Steckdose)", range: "0,7 – 0,9", mean: 0.8 },
- { group: "Steckdosen 400 V (1000 W/Steckdose)", range: "0,1 – 0,5", mean: 0.35 },
- { group: "Schmutz-, Warmwasserpumpen", range: "0,2 – 0,4", mean: 0.3 },
- { group: "Umwälzpumpen", range: "0,6 – 1", mean: 0.8 },
- { group: "Werkstätten", range: "0,2 – 0,4", mean: 0.3 },
-];