import { CalendarDays, FolderKanban, Play, Plus, Save, Search, Target } from "lucide-react"; import { FormEvent, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { HelpLink } from "@/components/HelpLink"; import { OrganizationSelect } from "@/components/OrganizationSelect"; import { cn } from "@/lib/utils"; import { createProject, getProjects, lookupTicket, updateProject } from "../api"; import { currentMonth, formatMinutes } from "../format"; import { ticketPattern } from "../timers"; import type { ProjectSummary, TicketMeta } from "../types"; type ProjectsPageProps = { onNavigate: (to: string) => void; onStartTimer: (ticketNumber: string) => Promise; }; type ProjectForm = { ticketNumber: string; title: string; organizationId: string; organizationName: string | null; teamspaceProject: string; description: string; goals: string; startsOn: string; endsOn: string; hourBudget: string; }; function emptyProjectForm(): ProjectForm { return { ticketNumber: "", title: "", organizationId: "", organizationName: null, teamspaceProject: "", description: "", goals: "", startsOn: "", endsOn: "", hourBudget: "" }; } function formatHoursInput(minutes?: number | null) { if (minutes === null || minutes === undefined) { return ""; } return new Intl.NumberFormat("de-DE", { maximumFractionDigits: 2 }).format(minutes / 60); } function parseHoursInput(value: string) { const trimmed = value.trim(); if (!trimmed) { return null; } const hours = Number(trimmed.replace(",", ".")); if (!Number.isFinite(hours) || hours < 0) { return undefined; } return Math.round(hours * 60); } function formFromProject(project: ProjectSummary): ProjectForm { return { ticketNumber: project.ticket_number, title: project.title ?? "", organizationId: project.organization_id ?? "", organizationName: project.organization_name ?? project.customer_name ?? null, teamspaceProject: project.teamspace_project ?? "", description: project.description, goals: project.goals, startsOn: project.starts_on ?? "", endsOn: project.ends_on ?? "", hourBudget: formatHoursInput(project.hour_budget_minutes) }; } type ProjectFormFieldsProps = { form: ProjectForm; setForm: (form: ProjectForm) => void; organizationLocked: boolean; lookupStatus: "idle" | "loading" | "found" | "not-found"; saving: boolean; submitLabel: string; onSubmit: (event: FormEvent) => void; }; function ProjectFormFields({ form, setForm, organizationLocked, lookupStatus, saving, submitLabel, onSubmit }: ProjectFormFieldsProps) { return (
setForm({ ...form, ticketNumber: event.currentTarget.value, organizationId: "", organizationName: null }) } required /> {form.ticketNumber && !ticketPattern.test(form.ticketNumber.trim()) ?

Format: Ticket#XXXXXX

: null} {lookupStatus === "loading" ?

Suche Ticket...

: null} {lookupStatus === "found" ?

Ticket gefunden. Daten werden übernommen.

: null} {lookupStatus === "not-found" ?

Neues Ticket. Organisation bitte wählen.

: null}
setForm({ ...form, title: event.currentTarget.value })} />
Consulting
setForm({ ...form, organizationId: organization.id, organizationName: organization.name })} required />
setForm({ ...form, teamspaceProject: event.currentTarget.value })} />
setForm({ ...form, startsOn: event.currentTarget.value })} />
setForm({ ...form, endsOn: event.currentTarget.value })} />
setForm({ ...form, hourBudget: event.currentTarget.value })} />