"use client"; import { useState, useMemo } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { formatCents, formatDate, formatMoney } from "~/lib/format.js"; import { InfoTooltip } from "~/components/ui/InfoTooltip.js"; import { ALLOCATION_STATUS_BADGE } from "~/lib/status-styles.js"; import { usePermissions } from "~/hooks/usePermissions.js"; import { trpc } from "~/lib/trpc/client.js"; function countWorkingDays(start: Date | string, end: Date | string): number { const s = new Date(start); const e = new Date(end); let days = 0; const cur = new Date(s); while (cur <= e) { if (cur.getDay() !== 0 && cur.getDay() !== 6) days++; cur.setDate(cur.getDate() + 1); } return days; } interface AssignmentRow { id: string; role: string | null; startDate: Date | string; endDate: Date | string; hoursPerDay: number; dailyCostCents: number; status: string; resource?: { id: string; displayName: string; eid: string } | null; } interface ProjectAssignmentsTableProps { assignments: AssignmentRow[]; } export function ProjectAssignmentsTable({ assignments }: ProjectAssignmentsTableProps) { const { canEdit } = usePermissions(); const router = useRouter(); const utils = trpc.useUtils(); const [deletingId, setDeletingId] = useState(null); const [confirmId, setConfirmId] = useState(null); const deleteMutation = trpc.allocation.deleteAssignment.useMutation({ onSuccess: () => { void utils.allocation.list.invalidate(); void utils.allocation.listView.invalidate(); void utils.timeline.getEntries.invalidate(); void utils.timeline.getEntriesView.invalidate(); void utils.timeline.getBudgetStatus.invalidate(); router.refresh(); }, }); async function handleDelete(id: string) { setDeletingId(id); try { await deleteMutation.mutateAsync({ id }); } finally { setDeletingId(null); setConfirmId(null); } } return (

Assignments ({assignments.length})

{canEdit && ( )} {assignments.map((assignment) => ( {canEdit && ( )} ))} {assignments.length > 0 && (() => { const totalHours = assignments.reduce((sum, a) => sum + countWorkingDays(a.startDate, a.endDate) * a.hoursPerDay, 0); const totalCostCents = assignments.reduce((sum, a) => sum + countWorkingDays(a.startDate, a.endDate) * a.dailyCostCents, 0); return ( {canEdit && ); })()}
Resource Role Period Hours/Day Daily Cost Total Hours Total Cost Status Actions
{assignment.resource?.displayName ?? "\u2014"} {assignment.resource?.eid && ( {assignment.resource.eid} )} {assignment.role || "\u2014"} {formatDate(assignment.startDate)} {"\u2192"} {formatDate(assignment.endDate)} {assignment.hoursPerDay}h {formatCents(assignment.dailyCostCents)} € {(countWorkingDays(assignment.startDate, assignment.endDate) * assignment.hoursPerDay).toLocaleString("de-DE", { minimumFractionDigits: 1 })}h {formatMoney(countWorkingDays(assignment.startDate, assignment.endDate) * assignment.dailyCostCents, "EUR", 2)} {assignment.status} {confirmId === assignment.id ? (
) : ( )}
Totals {"\u2014"} {totalHours.toLocaleString("de-DE", { minimumFractionDigits: 1 })}h {formatMoney(totalCostCents, "EUR", 2)} {"\u2014"}}
{assignments.length === 0 && (

No assignments for this project.

{canEdit && (

Create staffing entries via{" "} Allocations → New Planning Entry .

)}
)}
); }