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,175 @@
|
||||
import type { AllocationWithDetails, AllocationStatus } from "@capakraken/shared";
|
||||
import { ALLOCATION_STATUS_BADGE as STATUS_BADGE } from "~/lib/status-styles.js";
|
||||
import { ConfirmDialog } from "~/components/ui/ConfirmDialog.js";
|
||||
import { BatchActionBar } from "~/components/ui/BatchActionBar.js";
|
||||
import { BatchDateShiftModal } from "./BatchDateShiftModal.js";
|
||||
|
||||
const ALL_ALLOC_STATUSES = [
|
||||
{ value: "PROPOSED", label: "Proposed" },
|
||||
{ value: "CONFIRMED", label: "Confirmed" },
|
||||
{ value: "ACTIVE", label: "Active" },
|
||||
{ value: "COMPLETED", label: "Completed" },
|
||||
{ value: "CANCELLED", label: "Cancelled" },
|
||||
] as const;
|
||||
|
||||
type ConfirmDeleteState = {
|
||||
single?: AllocationWithDetails;
|
||||
ids?: string[];
|
||||
} | null;
|
||||
|
||||
type ConfirmBatchStatusState = {
|
||||
ids: string[];
|
||||
status: string;
|
||||
} | null;
|
||||
|
||||
type AllocationBatchDialogsProps = {
|
||||
selectionCount: number;
|
||||
selectedMutationIds: string[];
|
||||
onClearSelection: () => void;
|
||||
|
||||
// Batch status picker
|
||||
batchStatusPickerOpen: boolean;
|
||||
onOpenBatchStatusPicker: () => void;
|
||||
onCloseBatchStatusPicker: () => void;
|
||||
batchStatusPending: boolean;
|
||||
onBatchStatusConfirm: (ids: string[], status: AllocationStatus) => void;
|
||||
|
||||
// Delete
|
||||
confirmDelete: ConfirmDeleteState;
|
||||
onSetConfirmDelete: (state: ConfirmDeleteState) => void;
|
||||
onSingleDelete: (alloc: AllocationWithDetails) => void;
|
||||
onBatchDelete: (ids: string[]) => void;
|
||||
batchDeletePending: boolean;
|
||||
|
||||
// Date shift
|
||||
showDateShiftModal: boolean;
|
||||
onOpenDateShiftModal: () => void;
|
||||
onCloseDateShiftModal: () => void;
|
||||
onDateShiftConfirm: (daysDelta: number) => void;
|
||||
dateShiftPending: boolean;
|
||||
};
|
||||
|
||||
export function AllocationBatchDialogs({
|
||||
selectionCount,
|
||||
selectedMutationIds,
|
||||
onClearSelection,
|
||||
batchStatusPickerOpen,
|
||||
onOpenBatchStatusPicker,
|
||||
onCloseBatchStatusPicker,
|
||||
batchStatusPending,
|
||||
onBatchStatusConfirm,
|
||||
confirmDelete,
|
||||
onSetConfirmDelete,
|
||||
onSingleDelete,
|
||||
onBatchDelete,
|
||||
batchDeletePending,
|
||||
showDateShiftModal,
|
||||
onOpenDateShiftModal,
|
||||
onCloseDateShiftModal,
|
||||
onDateShiftConfirm,
|
||||
dateShiftPending,
|
||||
}: AllocationBatchDialogsProps) {
|
||||
return (
|
||||
<>
|
||||
{/* Batch Status Picker */}
|
||||
{batchStatusPickerOpen && (
|
||||
<div
|
||||
className="fixed inset-0 bg-black/30 z-50 flex items-center justify-center p-4"
|
||||
onClick={onCloseBatchStatusPicker}
|
||||
>
|
||||
<div
|
||||
className="min-w-[220px] rounded-2xl bg-white p-5 shadow-2xl dark:bg-gray-900"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<h3 className="mb-3 text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
Set status for {selectionCount} allocations
|
||||
</h3>
|
||||
<div className="flex flex-col gap-1">
|
||||
{ALL_ALLOC_STATUSES.map((s) => (
|
||||
<button
|
||||
key={s.value}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onBatchStatusConfirm(selectedMutationIds, s.value as AllocationStatus);
|
||||
onCloseBatchStatusPicker();
|
||||
}}
|
||||
className="w-full rounded-xl px-3 py-2 text-left text-sm transition-colors hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
>
|
||||
<span
|
||||
className={`inline-block px-2 py-0.5 text-xs rounded-full font-medium ${STATUS_BADGE[s.value]}`}
|
||||
>
|
||||
{s.label}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Confirm single delete */}
|
||||
{confirmDelete?.single && (
|
||||
<ConfirmDialog
|
||||
title="Delete Allocation"
|
||||
message={`Delete allocation for ${confirmDelete.single.resource?.displayName ?? "resource"} on ${confirmDelete.single.project?.name ?? "project"}?`}
|
||||
confirmLabel="Delete"
|
||||
variant="danger"
|
||||
onConfirm={() => {
|
||||
onSingleDelete(confirmDelete.single!);
|
||||
onSetConfirmDelete(null);
|
||||
}}
|
||||
onCancel={() => onSetConfirmDelete(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Confirm batch delete */}
|
||||
{confirmDelete?.ids && (
|
||||
<ConfirmDialog
|
||||
title="Delete Allocations"
|
||||
message={`Delete ${confirmDelete.ids.length} selected allocation${confirmDelete.ids.length !== 1 ? "s" : ""}? This cannot be undone.`}
|
||||
confirmLabel="Delete All"
|
||||
variant="danger"
|
||||
onConfirm={() => {
|
||||
onBatchDelete(confirmDelete.ids!);
|
||||
onSetConfirmDelete(null);
|
||||
}}
|
||||
onCancel={() => onSetConfirmDelete(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Batch Action Bar */}
|
||||
<BatchActionBar
|
||||
count={selectionCount}
|
||||
onClear={onClearSelection}
|
||||
actions={[
|
||||
{
|
||||
label: "Set Status…",
|
||||
onClick: onOpenBatchStatusPicker,
|
||||
disabled: batchStatusPending,
|
||||
},
|
||||
{
|
||||
label: "Shift Dates…",
|
||||
onClick: onOpenDateShiftModal,
|
||||
disabled: dateShiftPending,
|
||||
},
|
||||
{
|
||||
label: `Delete (${selectionCount})`,
|
||||
variant: "danger",
|
||||
onClick: () => onSetConfirmDelete({ ids: selectedMutationIds }),
|
||||
disabled: batchDeletePending,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* Batch date shift modal */}
|
||||
{showDateShiftModal && (
|
||||
<BatchDateShiftModal
|
||||
count={selectionCount}
|
||||
isPending={dateShiftPending}
|
||||
onConfirm={onDateShiftConfirm}
|
||||
onClose={onCloseDateShiftModal}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user