refactor(web): decompose TimelineView, ReportBuilder, and ResourceModal into focused components

Extract overlay/popover JSX from TimelineView (1268→1037 lines) into TimelineDragOverlays and
TimelinePopovers. Extract ResourceMonthConfigSection from ReportBuilder (1132→1018 lines).
Extract ResourceSkillsEditor and ResourceOrgClassification from ResourceModal (1035→714 lines).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-11 23:16:38 +02:00
parent 5a4836d292
commit bfcadd2c52
8 changed files with 1224 additions and 785 deletions
+15 -129
View File
@@ -10,6 +10,7 @@ import {
type ReportExplainability,
} from "./reportBuilderExplainability.js";
import { ReportResultsPanel } from "./ReportResultsPanel.js";
import { ResourceMonthConfigSection } from "./ResourceMonthConfigSection.js";
// ─── Types ──────────────────────────────────────────────────────────────────
@@ -753,135 +754,20 @@ export function ReportBuilder() {
))}
</div>
{entity === "resource_month" && (
<div className="mt-4 space-y-4 rounded-2xl border border-emerald-200 bg-emerald-50/70 p-4 dark:border-emerald-900/60 dark:bg-emerald-950/20">
<div className="flex flex-wrap items-end gap-4">
<div>
<label className="mb-1 block text-sm font-medium text-emerald-900 dark:text-emerald-200">
Period month
</label>
<input
type="month"
value={periodMonth}
onChange={(e) => setPeriodMonth(e.target.value)}
className="rounded-xl border border-emerald-300 bg-white px-3 py-2 text-sm text-gray-700 focus:border-emerald-500 focus:ring-emerald-500 dark:border-emerald-900 dark:bg-slate-950 dark:text-gray-300"
/>
</div>
<p className="max-w-2xl text-sm text-emerald-900/80 dark:text-emerald-200/80">
Resource Months uses the CapaKraken holiday and absence logic directly. SAH,
booked hours and chargeability are calculated per resource and month with country,
state and city context.
</p>
</div>
<div className="grid gap-3 lg:grid-cols-3">
{resourceMonthBlueprints.map((blueprint) => (
<button
key={blueprint.id}
type="button"
onClick={() => applyBlueprint(blueprint)}
className="rounded-2xl border border-emerald-200 bg-white/80 p-4 text-left transition hover:border-emerald-400 hover:bg-white dark:border-emerald-900/70 dark:bg-slate-950/60 dark:hover:border-emerald-700"
>
<div className="text-sm font-semibold text-emerald-950 dark:text-emerald-100">
{blueprint.label}
</div>
<p className="mt-1 text-xs leading-5 text-emerald-900/75 dark:text-emerald-200/75">
{blueprint.description}
</p>
</button>
))}
</div>
<div className="rounded-2xl border border-emerald-200/80 bg-white/60 p-4 dark:border-emerald-900/60 dark:bg-slate-950/40">
{displayedResourceMonthCompleteness ? (
<div className="mb-4 rounded-2xl border border-emerald-200/80 bg-emerald-50/80 p-4 dark:border-emerald-900/60 dark:bg-emerald-950/20">
<div className="flex flex-wrap items-center gap-2">
<span
className={clsx(
"rounded-full px-2.5 py-1 text-[11px] font-semibold uppercase tracking-[0.14em]",
displayedResourceMonthCompleteness.isAuditReady
? "bg-emerald-500 text-white"
: "bg-amber-100 text-amber-800 dark:bg-amber-950/60 dark:text-amber-200",
)}
>
{displayedResourceMonthCompleteness.isAuditReady
? "Audit ready"
: "Audit gap"}
</span>
<span className="rounded-full bg-white px-2.5 py-1 text-[11px] font-medium text-emerald-900 dark:bg-slate-950 dark:text-emerald-100">
{displayedResourceMonthCompleteness.selectedMinimumAuditColumnCount}/
{displayedResourceMonthCompleteness.minimumAuditColumnCount} minimum audit
columns
</span>
<span className="rounded-full bg-white px-2.5 py-1 text-[11px] font-medium text-emerald-900 dark:bg-slate-950 dark:text-emerald-100">
{displayedResourceMonthCompleteness.selectedRecommendedColumnCount}/
{displayedResourceMonthCompleteness.recommendedColumnCount} recommended
columns
</span>
<span className="rounded-full bg-white px-2.5 py-1 text-[11px] text-emerald-900/80 dark:bg-slate-950 dark:text-emerald-200/80">
{selectedTemplate && !hasTemplateDraftChanges
? "Saved template status"
: "Current builder status"}
</span>
</div>
{displayedResourceMonthCompleteness.missingMinimumAuditColumns.length > 0 ? (
<p className="mt-3 text-xs text-amber-800 dark:text-amber-200">
Missing audit/export basis columns:{" "}
{summarizeMissingColumns(
displayedResourceMonthCompleteness.missingMinimumAuditColumns,
columnLabelMap,
)}
</p>
) : displayedResourceMonthCompleteness.missingRecommendedColumns.length > 0 ? (
<p className="mt-3 text-xs text-emerald-900/80 dark:text-emerald-200/80">
Audit-ready, but still missing recommended basis columns:{" "}
{summarizeMissingColumns(
displayedResourceMonthCompleteness.missingRecommendedColumns,
columnLabelMap,
)}
</p>
) : (
<p className="mt-3 text-xs text-emerald-900/80 dark:text-emerald-200/80">
This view includes the full recommended audit/export basis set for monthly
SAH and chargeability checks.
</p>
)}
</div>
) : null}
<div className="text-sm font-medium text-emerald-950 dark:text-emerald-100">
Recommended transparency columns
</div>
<div className="mt-2 flex flex-wrap gap-2">
{RESOURCE_MONTH_RECOMMENDED_COLUMNS.map((column) => (
<button
key={column}
type="button"
onClick={() => toggleColumn(column)}
className={clsx(
"rounded-full border px-3 py-1 text-xs font-medium transition",
selectedColumns.has(column)
? "border-emerald-500 bg-emerald-500 text-white"
: "border-emerald-200 bg-white text-emerald-900 hover:border-emerald-400 dark:border-emerald-900 dark:bg-slate-950 dark:text-emerald-200 dark:hover:border-emerald-700",
)}
>
{columnLabelMap.get(column) ?? column}
</button>
))}
</div>
<p className="mt-3 text-xs text-emerald-900/75 dark:text-emerald-200/75">
Formula reference: base available hours - holiday deduction - absence deduction =
monthly SAH. Chargeability uses booked hours divided by monthly SAH.
</p>
<p className="mt-2 text-xs text-emerald-900/75 dark:text-emerald-200/75">
Export recommendation: include both basis columns and computed metrics in the CSV.
That keeps Excel as a review layer instead of rebuilding CapaKraken logic outside
the product.
</p>
<p className="mt-2 text-xs text-emerald-900/75 dark:text-emerald-200/75">
Minimum audit set: month, location context, SAH, holiday deductions, absence
deductions, target hours, booked hours and unassigned hours.
</p>
</div>
</div>
<ResourceMonthConfigSection
periodMonth={periodMonth}
onPeriodMonthChange={setPeriodMonth}
blueprints={resourceMonthBlueprints}
onApplyBlueprint={applyBlueprint}
completeness={displayedResourceMonthCompleteness}
selectedTemplate={selectedTemplate}
hasTemplateDraftChanges={hasTemplateDraftChanges}
selectedColumns={selectedColumns}
onToggleColumn={toggleColumn}
columnLabelMap={columnLabelMap}
recommendedColumns={RESOURCE_MONTH_RECOMMENDED_COLUMNS}
summarizeMissing={summarizeMissingColumns}
/>
)}
</div>