Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | 76x 48x 53x 48x 48x 57x 48x 48x 48x 48x 1x 48x 48x 48x 48x 48x 5x 48x 53x 1x 1x 89x 312x 52x 48x 22x 192x | import { Library } from "lucide-react";
import { useState } from "react";
import { Spinner } from "@/ahuora-design-system/ui/spinner";
import type {
CostCurveAuthoring,
CostCurveEquipmentCategoryRead,
CostCurveRead,
PatchedCostCurveAuthoring,
} from "@/api/apiStore.gen";
import { CostCurveDialog } from "../authoring/CostCurveDialog";
import { type CostCurveDraft, costCurveCreatePayload } from "../model/payloads";
import type { CostCurveUsageSummary, SaveState } from "../types";
import { FormState } from "../ui/FormState";
import { CostCurveLibraryCard } from "./CostCurveLibraryCard";
const emptyUsage: CostCurveUsageSummary = {
unitNames: [],
capitalLineLabels: [],
};
export function CostCurveLibraryPanel({
curves,
equipmentOptions = [],
canEdit,
saving,
loading = false,
loadError = false,
onCreateCurve,
onPatchCurve,
onDeleteCurve,
usageByCurveId = {},
}: {
curves: CostCurveRead[];
equipmentOptions?: readonly CostCurveEquipmentCategoryRead[];
canEdit: boolean;
saving: boolean;
loading?: boolean;
loadError?: boolean;
onCreateCurve: (curve: CostCurveAuthoring) => Promise<void | CostCurveRead>;
onPatchCurve: (
curveId: number,
patch: PatchedCostCurveAuthoring,
) => Promise<void>;
onDeleteCurve: (curveId: number) => Promise<void>;
usageByCurveId?: Record<number, CostCurveUsageSummary>;
}) {
const activeCurves = curves.filter((curve) => curve.active !== false);
const [libraryState, setLibraryState] = useState<SaveState>({
kind: "idle",
});
const markSaved = (message: string) => {
setLibraryState({ kind: "saved", message });
};
return (
<section
className="rounded-md border bg-card p-4"
aria-label="Cost curve library"
>
<div className="mb-3 flex flex-wrap items-center justify-between gap-2">
<div className="flex items-start gap-2">
<Library className="mt-0.5 size-4 text-primary" aria-hidden="true" />
<div>
<h4 className="text-sm font-semibold">Cost curve library</h4>
<p className="mt-1.5 text-xs text-muted-foreground">
Create or edit study curves before assigning them to capital
lines.
</p>
</div>
</div>
<CostCurveDialog
canEdit={canEdit && !loading && !loadError}
saving={saving}
draftDefaults={{
equipment_category: equipmentOptions[0]?.value ?? "",
}}
equipmentOptions={equipmentOptions}
existingCurves={curves}
onSubmit={async (draft: CostCurveDraft) => {
await onCreateCurve(costCurveCreatePayload(draft));
markSaved("Cost curve created");
}}
/>
</div>
<FormState state={libraryState} />
{loading ? (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Spinner size="small" />
Loading cost curves
</div>
) : loadError ? (
<div
className="rounded-md border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive"
role="alert"
>
Could not load cost curve library.
</div>
) : curves.length === 0 ? (
<div className="text-sm text-muted-foreground">No cost curves</div>
) : (
<div className="grid gap-3">
{activeCurves.map((curve) => (
<CostCurveLibraryCard
key={curve.id}
curve={curve}
curves={curves}
canEdit={canEdit}
saving={saving}
equipmentOptions={equipmentOptions}
usage={usageByCurveId[curve.id] ?? emptyUsage}
onPatchCurve={onPatchCurve}
onDeleteCurve={onDeleteCurve}
onSaved={markSaved}
/>
))}
</div>
)}
</section>
);
}
|