refactor(web): decompose AllocationsClient and UsersClient into focused subcomponents
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>
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user