+ |
Total
|
{data.weeks.map((week) => {
@@ -312,13 +312,13 @@ export function WeeklyPhasingView({ estimateId, canEdit }: WeeklyPhasingViewProp
return (
{total > 0 ? total.toFixed(1) : "-"}
|
);
})}
-
+ |
{Object.values(columnTotals)
.reduce((sum, h) => sum + h, 0)
.toFixed(1)}
@@ -331,12 +331,12 @@ export function WeeklyPhasingView({ estimateId, canEdit }: WeeklyPhasingViewProp
)}
{data?.hasPhasing && viewMode === "by_chapter" && (
-
+
-
- |
+ |
+ |
Chapter
|
{data.weeks.map((week) => {
@@ -344,13 +344,13 @@ export function WeeklyPhasingView({ estimateId, canEdit }: WeeklyPhasingViewProp
return (
{week.label}
|
);
})}
-
+ |
Total
|
@@ -366,9 +366,9 @@ export function WeeklyPhasingView({ estimateId, canEdit }: WeeklyPhasingViewProp
return (
- |
+ |
{chapter}
|
{data.weeks.map((week) => {
@@ -378,7 +378,7 @@ export function WeeklyPhasingView({ estimateId, canEdit }: WeeklyPhasingViewProp
@@ -386,7 +386,7 @@ export function WeeklyPhasingView({ estimateId, canEdit }: WeeklyPhasingViewProp
|
);
})}
-
+ |
{chapterTotal.toFixed(1)}
|
@@ -394,8 +394,8 @@ export function WeeklyPhasingView({ estimateId, canEdit }: WeeklyPhasingViewProp
})}
-
- |
+ |
+ |
Total
|
{data.weeks.map((week) => {
@@ -404,13 +404,13 @@ export function WeeklyPhasingView({ estimateId, canEdit }: WeeklyPhasingViewProp
return (
{total > 0 ? total.toFixed(1) : "-"}
|
);
})}
-
+ |
{Object.values(chapterColumnTotals)
.reduce((sum, h) => sum + h, 0)
.toFixed(1)}
@@ -424,8 +424,8 @@ export function WeeklyPhasingView({ estimateId, canEdit }: WeeklyPhasingViewProp
{/* Info about current phasing config */}
{data?.hasPhasing && data.config && (
-
-
+
+
Current phasing:{" "}
{data.config.pattern.replace("_", " ")} distribution from{" "}
{data.config.startDate} to {data.config.endDate} across{" "}
diff --git a/apps/web/src/components/estimates/tabs/AssumptionsTab.tsx b/apps/web/src/components/estimates/tabs/AssumptionsTab.tsx
index 7046f05..6eef8c5 100644
--- a/apps/web/src/components/estimates/tabs/AssumptionsTab.tsx
+++ b/apps/web/src/components/estimates/tabs/AssumptionsTab.tsx
@@ -8,7 +8,7 @@ import type {
function EmptyState({ children }: { children: React.ReactNode }) {
return (
-
+
{children}
);
@@ -24,25 +24,25 @@ export function AssumptionsTab({ estimate }: { estimate: EstimateWorkspaceView }
}
return (
-
-
- Commercial and delivery assumptions
+
+
+ Commercial and delivery assumptions
-
+
{assumptions.map((assumption) => (
Category
- {assumption.category}
+ {assumption.category}
Label
- {assumption.label}
+ {assumption.label}
{assumption.key}
Value
- {String(assumption.value)}
+ {String(assumption.value)}
))}
diff --git a/apps/web/src/components/estimates/tabs/ExportsTab.tsx b/apps/web/src/components/estimates/tabs/ExportsTab.tsx
index 75e24a9..bb0f596 100644
--- a/apps/web/src/components/estimates/tabs/ExportsTab.tsx
+++ b/apps/web/src/components/estimates/tabs/ExportsTab.tsx
@@ -98,11 +98,11 @@ export function ExportsTab({
return (
-
+
- Export delivery
-
+ Export delivery
+
Generate format-specific artifacts from the current version and download them directly from the stored serializer payload.
@@ -114,7 +114,7 @@ export function ExportsTab({
type="button"
onClick={() => onCreateExport(latestVersion.id, format)}
disabled={isCreatingExport}
- className="rounded-2xl border border-brand-200 bg-white px-3 py-2 text-sm font-semibold text-brand-700 transition hover:border-brand-300 hover:bg-brand-50 disabled:cursor-not-allowed disabled:opacity-60"
+ className="rounded-2xl border border-brand-200 bg-white dark:bg-gray-800 px-3 py-2 text-sm font-semibold text-brand-700 transition hover:border-brand-300 hover:bg-brand-50 disabled:cursor-not-allowed disabled:opacity-60"
>
{isCreatingExport ? "Generating..." : `Create ${format}`}
@@ -124,16 +124,16 @@ export function ExportsTab({
-
-
- Generated exports
+
+
+ Generated exports
{exports.length === 0 ? (
No exports have been generated for the current version yet.
) : (
-
+
{exports.map((estimateExport) => {
const payload = isEstimateExportArtifactPayload(estimateExport.payload)
? estimateExport.payload
@@ -144,57 +144,57 @@ export function ExportsTab({
- {estimateExport.fileName}
-
+ {estimateExport.fileName}
+
{estimateExport.format}
{payload?.sheetNames?.length ? (
-
+
{payload.sheetNames.length} sheets
) : null}
-
+
{formatDateLong(estimateExport.createdAt)}
{payload ? {formatBytes(payload.byteLength)} : null}
{payload?.rowCount != null ? {payload.rowCount} rows : null}
{payload?.lineCount != null ? {payload.lineCount} lines : null}
{payload ? (
-
-
+
+
Hours
-
+
{payload.summary.totalHours.toFixed(1)}
-
+
Cost
-
+
{formatMoney(
payload.summary.totalCostCents,
payload.summary.baseCurrency,
)}
-
+
Price
-
+
{formatMoney(
payload.summary.totalPriceCents,
payload.summary.baseCurrency,
)}
-
+
Margin
-
+
{payload.summary.marginPercent.toFixed(0)}%
) : (
-
+
Legacy export record detected. Regenerate it to get downloadable serializer output.
)}
@@ -204,7 +204,7 @@ export function ExportsTab({
diff --git a/apps/web/src/components/estimates/tabs/FinancialsTab.tsx b/apps/web/src/components/estimates/tabs/FinancialsTab.tsx
index 40bf582..a1b1cd7 100644
--- a/apps/web/src/components/estimates/tabs/FinancialsTab.tsx
+++ b/apps/web/src/components/estimates/tabs/FinancialsTab.tsx
@@ -11,7 +11,7 @@ import { formatMoney } from "~/lib/format.js";
function EmptyState({ children }: { children: React.ReactNode }) {
return (
-
+
{children}
);
@@ -77,33 +77,33 @@ export function FinancialsTab({ estimate, canEdit }: { estimate: EstimateWorkspa
{/* Summary cards */}
-
+
Total Cost
- {formatMoney(totals.costCents, estimate.baseCurrency)}
- Avg {formatMoney(Math.round(avgCostRate), estimate.baseCurrency)}/h
+ {formatMoney(totals.costCents, estimate.baseCurrency)}
+ Avg {formatMoney(Math.round(avgCostRate), estimate.baseCurrency)}/h
-
+
Total Price
- {formatMoney(totals.priceCents, estimate.baseCurrency)}
- Avg {formatMoney(Math.round(avgBillRate), estimate.baseCurrency)}/h
+ {formatMoney(totals.priceCents, estimate.baseCurrency)}
+ Avg {formatMoney(Math.round(avgBillRate), estimate.baseCurrency)}/h
-
+
Margin
- = 0 ? "text-emerald-700" : "text-red-700")}>
+ = 0 ? "text-emerald-700 dark:text-emerald-400" : "text-red-700 dark:text-red-400")}>
{formatMoney(marginCents, estimate.baseCurrency)}
- {marginPercent.toFixed(1)}% of price
+ {marginPercent.toFixed(1)}% of price
-
+
Total Hours
- {totals.hours.toFixed(1)} h
- {demandLines.length} demand lines
+ {totals.hours.toFixed(1)} h
+ {demandLines.length} demand lines
{/* Margin waterfall: Cost -> Margin -> Price */}
-
- Cost to price bridge
+
+ Cost to price bridge
{(() => {
const maxVal = Math.max(totals.costCents, totals.priceCents, 1);
@@ -113,22 +113,22 @@ export function FinancialsTab({ estimate, canEdit }: { estimate: EstimateWorkspa
return (
<>
-
- Cost
- {formatMoney(totals.costCents, estimate.baseCurrency)}
+
+ Cost
+ {formatMoney(totals.costCents, estimate.baseCurrency)}
= 0 ? "bg-emerald-400" : "bg-red-400")}
style={{ height: `${marginH}%` }}
/>
- Margin
- {formatMoney(marginCents, estimate.baseCurrency)}
+ Margin
+ {formatMoney(marginCents, estimate.baseCurrency)}
- Price
- {formatMoney(totals.priceCents, estimate.baseCurrency)}
+ Price
+ {formatMoney(totals.priceCents, estimate.baseCurrency)}
>
);
@@ -137,12 +137,12 @@ export function FinancialsTab({ estimate, canEdit }: { estimate: EstimateWorkspa
{/* Chapter breakdown */}
-
- Breakdown by chapter
+
+ Breakdown by chapter
-
+
| Chapter |
Lines |
Hours |
@@ -157,31 +157,31 @@ export function FinancialsTab({ estimate, canEdit }: { estimate: EstimateWorkspa
const chapterMargin = data.priceCents - data.costCents;
const chapterMarginPct = data.priceCents > 0 ? (chapterMargin / data.priceCents) * 100 : 0;
return (
-
- | {chapter} |
- {data.count} |
- {data.hours.toFixed(1)} |
- {formatMoney(data.costCents, estimate.baseCurrency)} |
- {formatMoney(data.priceCents, estimate.baseCurrency)} |
- = 0 ? "text-emerald-700" : "text-red-700")}>
+ |
+ | {chapter} |
+ {data.count} |
+ {data.hours.toFixed(1)} |
+ {formatMoney(data.costCents, estimate.baseCurrency)} |
+ {formatMoney(data.priceCents, estimate.baseCurrency)} |
+ = 0 ? "text-emerald-700 dark:text-emerald-400" : "text-red-700 dark:text-red-400")}>
{formatMoney(chapterMargin, estimate.baseCurrency)}
|
- = 0 ? "text-emerald-700" : "text-red-700")}>
+ | = 0 ? "text-emerald-700 dark:text-emerald-400" : "text-red-700 dark:text-red-400")}>
{chapterMarginPct.toFixed(1)}%
|
);
})}
-
- | Total |
- {demandLines.length} |
- {totals.hours.toFixed(1)} |
- {formatMoney(totals.costCents, estimate.baseCurrency)} |
- {formatMoney(totals.priceCents, estimate.baseCurrency)} |
- = 0 ? "text-emerald-700" : "text-red-700")}>
+ |
+ | Total |
+ {demandLines.length} |
+ {totals.hours.toFixed(1)} |
+ {formatMoney(totals.costCents, estimate.baseCurrency)} |
+ {formatMoney(totals.priceCents, estimate.baseCurrency)} |
+ = 0 ? "text-emerald-700 dark:text-emerald-400" : "text-red-700 dark:text-red-400")}>
{formatMoney(marginCents, estimate.baseCurrency)}
|
- = 0 ? "text-emerald-700" : "text-red-700")}>
+ | = 0 ? "text-emerald-700 dark:text-emerald-400" : "text-red-700 dark:text-red-400")}>
{marginPercent.toFixed(1)}%
|
@@ -192,12 +192,12 @@ export function FinancialsTab({ estimate, canEdit }: { estimate: EstimateWorkspa
{/* Monthly cost/price phasing */}
{sortedMonths.length > 0 && (
-
- Monthly financial phasing
+
+ Monthly financial phasing
-
+
| Month |
Hours |
Cost |
@@ -210,12 +210,12 @@ export function FinancialsTab({ estimate, canEdit }: { estimate: EstimateWorkspa
const data = monthlyFinancials.get(month)!;
const mMargin = data.priceCents - data.costCents;
return (
-
- | {month} |
- {data.hours.toFixed(1)} |
- {formatMoney(data.costCents, estimate.baseCurrency)} |
- {formatMoney(data.priceCents, estimate.baseCurrency)} |
- = 0 ? "text-emerald-700" : "text-red-700")}>
+ |
+ | {month} |
+ {data.hours.toFixed(1)} |
+ {formatMoney(data.costCents, estimate.baseCurrency)} |
+ {formatMoney(data.priceCents, estimate.baseCurrency)} |
+ = 0 ? "text-emerald-700 dark:text-emerald-400" : "text-red-700 dark:text-red-400")}>
{formatMoney(mMargin, estimate.baseCurrency)}
|
diff --git a/apps/web/src/components/estimates/tabs/OverviewTab.tsx b/apps/web/src/components/estimates/tabs/OverviewTab.tsx
index a0314e7..4856057 100644
--- a/apps/web/src/components/estimates/tabs/OverviewTab.tsx
+++ b/apps/web/src/components/estimates/tabs/OverviewTab.tsx
@@ -11,18 +11,18 @@ import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
import { formatDateLong, formatMoney } from "~/lib/format.js";
const STATUS_STYLES: Record = {
- DRAFT: "bg-slate-100 text-slate-700",
- IN_REVIEW: "bg-amber-100 text-amber-700",
- APPROVED: "bg-emerald-100 text-emerald-700",
- ARCHIVED: "bg-zinc-200 text-zinc-700",
+ DRAFT: "bg-slate-100 text-slate-700 dark:bg-slate-900/30 dark:text-slate-300",
+ IN_REVIEW: "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300",
+ APPROVED: "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300",
+ ARCHIVED: "bg-zinc-200 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-300",
};
const VERSION_STYLES: Record = {
- WORKING: "bg-sky-100 text-sky-700",
- BASELINE: "bg-violet-100 text-violet-700",
- SUBMITTED: "bg-amber-100 text-amber-700",
- APPROVED: "bg-emerald-100 text-emerald-700",
- SUPERSEDED: "bg-zinc-200 text-zinc-700",
+ WORKING: "bg-sky-100 text-sky-700 dark:bg-sky-900/30 dark:text-sky-300",
+ BASELINE: "bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-300",
+ SUBMITTED: "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300",
+ APPROVED: "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300",
+ SUPERSEDED: "bg-zinc-200 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-300",
};
function formatMetricValue(metric: EstimateMetricView) {
@@ -43,13 +43,13 @@ export function OverviewTab({ estimate }: { estimate: EstimateWorkspaceView }) {
return (
-
+
{estimate.status.replace("_", " ")}
{estimate.project && (
-
+
{estimate.project.shortCode}
)}
@@ -58,43 +58,43 @@ export function OverviewTab({ estimate }: { estimate: EstimateWorkspaceView }) {
Opportunity
- {estimate.opportunityId ?? "Not set"}
+ {estimate.opportunityId ?? "Not set"}
Base currency
- {estimate.baseCurrency}
+ {estimate.baseCurrency}
Latest version
-
+
{latestVersion ? `v${latestVersion.versionNumber}${latestVersion.label ? ` - ${latestVersion.label}` : ""}` : "No version"}
Updated
- {formatDateLong(estimate.updatedAt)}
+ {formatDateLong(estimate.updatedAt)}
{latestVersion?.notes && (
-
+
Version notes
- {latestVersion.notes}
+ {latestVersion.notes}
)}
-
+
- Scope items
+ Scope items
{latestVersion?.scopeItems.length ?? 0}
{(latestVersion?.scopeItems ?? []).slice(0, 4).map((item) => (
-
+
- {item.name}
+ {item.name}
{item.scopeType}
@@ -103,17 +103,17 @@ export function OverviewTab({ estimate }: { estimate: EstimateWorkspaceView }) {
-
+
- Demand lines
+ Demand lines
{latestVersion?.demandLines.length ?? 0}
{(latestVersion?.demandLines ?? []).slice(0, 4).map((line) => (
-
+
- {line.name}
- {line.hours.toFixed(1)} h
+ {line.name}
+ {line.hours.toFixed(1)} h
))}
@@ -124,44 +124,44 @@ export function OverviewTab({ estimate }: { estimate: EstimateWorkspaceView }) {
| |