17f2de5f48
AllocationsClient (1364→962 lines): extracted AllocationRow, AllocationGroupedBody, OpenDemandsPanel, and AllocationBatchDialogs. UsersClient (1338→895 lines): extracted UserEditModal and UserCreateModal. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
144 lines
4.5 KiB
TypeScript
144 lines
4.5 KiB
TypeScript
import type { AllocationWithDetails, ColumnDef } from "@capakraken/shared";
|
|
import { ALLOCATION_STATUS_BADGE as STATUS_BADGE } from "~/lib/status-styles.js";
|
|
|
|
const STATUS_LEFT_BORDER: Record<string, string> = {
|
|
ACTIVE: "border-l-green-500",
|
|
PROPOSED: "border-l-amber-500",
|
|
CONFIRMED: "border-l-blue-500",
|
|
COMPLETED: "border-l-gray-400",
|
|
CANCELLED: "border-l-red-500",
|
|
};
|
|
|
|
type AllocationRowProps = {
|
|
alloc: AllocationWithDetails;
|
|
visibleColumns: ColumnDef[];
|
|
isGrouped?: boolean;
|
|
rowIndex?: number;
|
|
isSelected: boolean;
|
|
onToggleSelection: () => void;
|
|
onEdit: () => void;
|
|
onRequestDelete: () => void;
|
|
deleteDisabled: boolean;
|
|
formatPeriod: (alloc: AllocationWithDetails) => string;
|
|
};
|
|
|
|
export function AllocationRow({
|
|
alloc,
|
|
visibleColumns,
|
|
isGrouped = false,
|
|
rowIndex = 0,
|
|
isSelected,
|
|
onToggleSelection,
|
|
onEdit,
|
|
onRequestDelete,
|
|
deleteDisabled,
|
|
formatPeriod,
|
|
}: AllocationRowProps) {
|
|
const leftBorder = STATUS_LEFT_BORDER[alloc.status] ?? "border-l-gray-300";
|
|
return (
|
|
<tr
|
|
key={alloc.id}
|
|
data-testid="allocation-row"
|
|
className={`border-l-[3px] transition-colors hover:bg-gray-50/80 dark:hover:bg-gray-900/60 animate-row-enter ${leftBorder} ${isSelected ? "bg-brand-50 dark:bg-brand-900/20" : ""}`}
|
|
style={{ animationDelay: `${Math.min(rowIndex * 15, 300)}ms` }}
|
|
>
|
|
<td className="px-4 py-3">
|
|
<input
|
|
type="checkbox"
|
|
checked={isSelected}
|
|
onChange={onToggleSelection}
|
|
className="rounded border-gray-300 dark:border-gray-600"
|
|
/>
|
|
</td>
|
|
{visibleColumns.map((col) => {
|
|
switch (col.key) {
|
|
case "resource":
|
|
return (
|
|
<td
|
|
key={col.key}
|
|
className="px-4 py-3 text-sm font-medium text-gray-900 dark:text-gray-100"
|
|
>
|
|
{isGrouped ? (
|
|
<span className="text-gray-400 dark:text-gray-500">↳</span>
|
|
) : (
|
|
(alloc.resource?.displayName ?? "—")
|
|
)}
|
|
</td>
|
|
);
|
|
case "project":
|
|
return (
|
|
<td key={col.key} className="px-4 py-3 text-sm text-gray-600 dark:text-gray-300">
|
|
{alloc.project ? (
|
|
<>
|
|
<span className="font-mono text-xs">{alloc.project.shortCode}</span>{" "}
|
|
{alloc.project.name}
|
|
</>
|
|
) : (
|
|
"—"
|
|
)}
|
|
</td>
|
|
);
|
|
case "role":
|
|
return (
|
|
<td key={col.key} className="px-4 py-3 text-sm text-gray-600 dark:text-gray-300">
|
|
{alloc.role}
|
|
</td>
|
|
);
|
|
case "dates":
|
|
return (
|
|
<td
|
|
key={col.key}
|
|
className="whitespace-nowrap px-4 py-3 text-xs text-gray-500 dark:text-gray-400"
|
|
>
|
|
{formatPeriod(alloc)}
|
|
</td>
|
|
);
|
|
case "hoursPerDay":
|
|
return (
|
|
<td key={col.key} className="px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
|
|
{alloc.hoursPerDay}h
|
|
</td>
|
|
);
|
|
case "cost":
|
|
return (
|
|
<td key={col.key} className="px-4 py-3 text-sm text-gray-900 dark:text-gray-100">
|
|
{(alloc.dailyCostCents / 100).toFixed(0)} €
|
|
</td>
|
|
);
|
|
case "status":
|
|
return (
|
|
<td key={col.key} className="px-4 py-3">
|
|
<span
|
|
className={`inline-block px-2 py-0.5 text-xs rounded-full font-medium ${STATUS_BADGE[alloc.status] ?? "bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400"}`}
|
|
>
|
|
{alloc.status}
|
|
</span>
|
|
</td>
|
|
);
|
|
default:
|
|
return (
|
|
<td key={col.key} className="px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
|
|
—
|
|
</td>
|
|
);
|
|
}
|
|
})}
|
|
<td className="px-4 py-3">
|
|
<div className="flex items-center justify-end gap-2">
|
|
<button type="button" onClick={onEdit} className="app-action-edit">
|
|
Edit
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={onRequestDelete}
|
|
disabled={deleteDisabled}
|
|
className="app-action-delete disabled:opacity-50"
|
|
>
|
|
Delete
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
);
|
|
}
|