diff --git a/apps/web/src/app/(app)/estimates/EstimatesClient.tsx b/apps/web/src/app/(app)/estimates/EstimatesClient.tsx index 6ed7780..a145fc1 100644 --- a/apps/web/src/app/(app)/estimates/EstimatesClient.tsx +++ b/apps/web/src/app/(app)/estimates/EstimatesClient.tsx @@ -2,18 +2,71 @@ import { useMemo, useState } from "react"; import Link from "next/link"; -import type { AppRouter } from "@planarchy/api/router"; import { EstimateStatus, type EstimateVersionStatus } from "@planarchy/shared"; -import type { inferRouterOutputs } from "@trpc/server"; import { clsx } from "clsx"; import { EstimateWizard } from "~/components/estimates/EstimateWizard.js"; import { usePermissions } from "~/hooks/usePermissions.js"; import { formatDateLong, formatMoney } from "~/lib/format.js"; import { trpc } from "~/lib/trpc/client.js"; -type RouterOutput = inferRouterOutputs; -type EstimateListItem = RouterOutput["estimate"]["list"][number]; -type EstimateDetail = RouterOutput["estimate"]["getById"]; +type EstimateMetric = { + id: string; + key: string; + label: string; + valueDecimal: number; + valueCents: number | null; + currency: string | null; +}; + +type EstimateScopeItem = { + id: string; + name: string; + description: string | null; + scopeType: string; +}; + +type EstimateDemandLine = { + id: string; + name: string; + hours: number; + costTotalCents: number; + priceTotalCents: number; + currency: string; + chapter: string | null; +}; + +type EstimateVersion = { + versionNumber: number; + label: string | null; + status: EstimateVersionStatus; + notes: string | null; + metrics: EstimateMetric[]; + scopeItems: EstimateScopeItem[]; + demandLines: EstimateDemandLine[]; +}; + +type EstimateProjectRef = { + shortCode: string; + name: string; +}; + +type EstimateListItem = { + id: string; + name: string; + status: EstimateStatus; + opportunityId: string | null; + updatedAt: Date | string; + project: EstimateProjectRef | null; + versions: Array>; +}; + +type EstimateDetail = { + id: string; + name: string; + status: EstimateStatus; + project: EstimateProjectRef | null; + versions: EstimateVersion[]; +}; const STATUS_STYLES: Record = { DRAFT: "bg-slate-100 text-slate-700", @@ -30,7 +83,7 @@ const VERSION_STYLES: Record = { SUPERSEDED: "bg-zinc-200 text-zinc-700", }; -function formatMetricValue(metric: EstimateDetail["versions"][number]["metrics"][number]) { +function formatMetricValue(metric: EstimateMetric) { if (metric.valueCents != null) { return formatMoney(metric.valueCents, metric.currency ?? "EUR"); } @@ -42,7 +95,13 @@ function formatMetricValue(metric: EstimateDetail["versions"][number]["metrics"] function getLatestVersion(estimate: EstimateDetail | null | undefined) { if (!estimate) return null; - return [...estimate.versions].sort((left, right) => right.versionNumber - left.versionNumber)[0] ?? null; + let latest = estimate.versions[0] ?? null; + for (const version of estimate.versions) { + if (!latest || version.versionNumber > latest.versionNumber) { + latest = version; + } + } + return latest; } function EstimateDetailPanel({ @@ -58,16 +117,27 @@ function EstimateDetailPanel({ const latestMetrics = latestVersion?.metrics ?? []; return ( -