Reorganize into monorepo layout, move mitarbeiter-app to legacy reference

Consolidates the previously separate omsorgapp and omsorgCore repos
(each had their own nested .git with GitHub history) plus the old
root-level website/mitarbeiter-app into a single monorepo, matching
the structure already documented in the root CLAUDE.md. Also moves
the PHP employee app aside as omsorgWeb/mitarbeiter-app-legacy/ to
serve as a template for a ground-up rewrite.

Fixes .gitignore in the same pass: the config-secrets/uploads/data
patterns were unanchored (relative to repo root, not depth-agnostic),
so they silently stopped matching once the app moved under omsorgWeb/.
Patterns are now **/-prefixed and cover both mitarbeiter-app and
mitarbeiter-app-legacy, keeping DB/SMTP credentials and uploaded
employee documents out of version control.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Felix Kemmler
2026-08-07 14:21:37 +02:00
co-authored by Claude Sonnet 5
parent 8beb0fcf52
commit b6c1389c55
355 changed files with 24348 additions and 361 deletions
@@ -0,0 +1,197 @@
import { useEffect, useState } from "react";
import { Pencil } from "lucide-react";
import OmsorgCard from "../../components/OmsorgCard";
import OmsorgBadge from "../../components/ui/OmsorgBadge";
import OmsorgButton from "../../components/ui/OmsorgButton";
import { useAuth } from "../../app/AuthContext";
import EmployeeTabs from "./EmployeeTabs";
import EditEmployeeDialog from "./EditEmployeeDialog";
function getInitials(firstName = "", lastName = "") {
return `${firstName[0] ?? ""}${lastName[0] ?? ""}`.toUpperCase();
}
function formatDate(value) {
if (!value) return "—";
return new Date(value).toLocaleDateString("de-DE");
}
function formatAddress(employee) {
const line = [employee.postalCode, employee.city].filter(Boolean).join(" ");
const parts = [employee.street, line].filter(Boolean);
if (employee.country && employee.country !== "Deutschland") {
parts.push(employee.country);
}
return parts.length > 0 ? parts.join(", ") : "—";
}
export default function EmployeeDetailPanel({ employee, onUpdated }) {
const { hasPermission } = useAuth();
const canEdit = hasPermission("Employees", "Edit");
const [activeTab, setActiveTab] = useState("overview");
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
useEffect(() => {
setActiveTab("overview");
}, [employee?.id]);
if (!employee) {
return (
<OmsorgCard>
<div className="employee-detail-empty">
<h2>Keine Personalakte ausgewählt</h2>
<p>Wähle links einen Mitarbeiter aus.</p>
</div>
</OmsorgCard>
);
}
const renderTabContent = () => {
switch (activeTab) {
case "documents":
return (
<div className="employee-tab-content">
<h3>Dokumente</h3>
<p>
Hier werden später Arbeitsverträge,
Nachweise und weitere Dokumente angezeigt.
</p>
</div>
);
case "contracts":
return (
<div className="employee-tab-content">
<h3>Verträge</h3>
<p>
Hier erscheint später der Arbeitsvertrag
mit Beginn, Ende und Konditionen.
</p>
</div>
);
case "qualifications":
return (
<div className="employee-tab-content">
<h3>Qualifikationen</h3>
<p>
Hier erscheinen später Ausbildung,
Zertifikate und Fortbildungen.
</p>
</div>
);
case "assignments":
return (
<div className="employee-tab-content">
<h3>Einsätze</h3>
<p>
Hier wird später die komplette
Einsatzhistorie angezeigt.
</p>
</div>
);
case "overview":
default:
return (
<div className="employee-detail-grid">
<div>
<strong>Telefon</strong>
<p>{employee.phoneNumber ?? "—"}</p>
</div>
<div>
<strong>E-Mail</strong>
<p>{employee.email ?? "—"}</p>
</div>
<div>
<strong>Adresse</strong>
<p>{formatAddress(employee)}</p>
</div>
<div>
<strong>Geburtsdatum</strong>
<p>{formatDate(employee.dateOfBirth)}</p>
</div>
<div>
<strong>Eintritt</strong>
<p>{formatDate(employee.entryDate)}</p>
</div>
<div>
<strong>Austritt</strong>
<p>{formatDate(employee.exitDate)}</p>
</div>
<div>
<strong>Beschäftigungsart</strong>
<p>{employee.employmentType ?? "—"}</p>
</div>
<div>
<strong>Qualifikation</strong>
<p>{employee.qualification ?? "—"}</p>
</div>
<div>
<strong>Notfallkontakt</strong>
<p>
{employee.emergencyContactName
? `${employee.emergencyContactName}${
employee.emergencyContactRelation ? ` (${employee.emergencyContactRelation})` : ""
}${employee.emergencyContactPhone ? `${employee.emergencyContactPhone}` : ""}`
: "—"}
</p>
</div>
</div>
);
}
};
return (
<OmsorgCard>
<div className="employee-detail">
<div className="employee-detail-header">
<div className="employee-detail-avatar">
{getInitials(employee.firstName, employee.lastName)}
</div>
<div>
<h2>{`${employee.firstName} ${employee.lastName}`.trim()}</h2>
<OmsorgBadge status={employee.status?.toLowerCase()} />
</div>
{canEdit && (
<OmsorgButton variant="secondary" icon={Pencil} onClick={() => setIsEditDialogOpen(true)}>
Bearbeiten
</OmsorgButton>
)}
</div>
<EmployeeTabs
activeTab={activeTab}
onChange={setActiveTab}
/>
{renderTabContent()}
</div>
{isEditDialogOpen && (
<EditEmployeeDialog
employee={employee}
onClose={() => setIsEditDialogOpen(false)}
onUpdated={(updatedEmployee) => {
setIsEditDialogOpen(false);
onUpdated?.(updatedEmployee);
}}
/>
)}
</OmsorgCard>
);
}