feat: timeline multi-select, demand popover, resource hover card, merged tooltips, dark mode fixes
Major timeline enhancements: - Right-click drag multi-selection with floating action bar (batch delete/assign) - DemandPopover for demand strip details (replaces broken "Loading" modal) - ResourceHoverCard on name hover showing skills, rates, role, chapter - Merged heatmap+vacation tooltips into unified TimelineTooltip component - Fixed overbooking blink animation (date normalization, z-index ordering) - Fixed dark mode sticky column bleed-through in project view - System roles admin page, notification task management, performance review docs Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -98,11 +98,11 @@ export function ExportsTab({
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-3xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<div className="rounded-3xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-5 shadow-sm">
|
||||
<div className="flex flex-wrap items-start justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-600">Export delivery <InfoTooltip content="Generate downloadable files from the current estimate version. Each format includes demand lines, scope, and financial summaries." /></h2>
|
||||
<p className="mt-2 text-sm text-gray-500">
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-600 dark:text-gray-400">Export delivery <InfoTooltip content="Generate downloadable files from the current estimate version. Each format includes demand lines, scope, and financial summaries." /></h2>
|
||||
<p className="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Generate format-specific artifacts from the current version and download them directly from the stored serializer payload.
|
||||
</p>
|
||||
</div>
|
||||
@@ -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}`}
|
||||
</button>
|
||||
@@ -124,16 +124,16 @@ export function ExportsTab({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-3xl border border-gray-200 bg-white shadow-sm">
|
||||
<div className="border-b border-gray-100 px-6 py-4">
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-600">Generated exports <InfoTooltip content="Previously generated export artifacts. XLSX/CSV contain tabular data; JSON is machine-readable; SAP/MMP are ERP integration formats." /></h2>
|
||||
<div className="rounded-3xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-sm">
|
||||
<div className="border-b border-gray-100 dark:border-gray-700/50 px-6 py-4">
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-600 dark:text-gray-400">Generated exports <InfoTooltip content="Previously generated export artifacts. XLSX/CSV contain tabular data; JSON is machine-readable; SAP/MMP are ERP integration formats." /></h2>
|
||||
</div>
|
||||
{exports.length === 0 ? (
|
||||
<div className="px-6 py-8">
|
||||
<p className="text-sm text-gray-400">No exports have been generated for the current version yet.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="divide-y divide-gray-100">
|
||||
<div className="divide-y divide-gray-100 dark:divide-gray-700/50">
|
||||
{exports.map((estimateExport) => {
|
||||
const payload = isEstimateExportArtifactPayload(estimateExport.payload)
|
||||
? estimateExport.payload
|
||||
@@ -144,57 +144,57 @@ export function ExportsTab({
|
||||
<div className="flex flex-wrap items-start justify-between gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<p className="text-sm font-medium text-gray-900">{estimateExport.fileName}</p>
|
||||
<span className="rounded-full bg-gray-100 px-2.5 py-1 text-[11px] font-semibold uppercase tracking-wide text-gray-600">
|
||||
<p className="text-sm font-medium text-gray-900 dark:text-gray-100">{estimateExport.fileName}</p>
|
||||
<span className="rounded-full bg-gray-100 dark:bg-gray-800 px-2.5 py-1 text-[11px] font-semibold uppercase tracking-wide text-gray-600 dark:text-gray-400">
|
||||
{estimateExport.format}
|
||||
</span>
|
||||
{payload?.sheetNames?.length ? (
|
||||
<span className="rounded-full bg-sky-50 px-2.5 py-1 text-[11px] font-semibold text-sky-700">
|
||||
<span className="rounded-full bg-sky-50 dark:bg-sky-900/30 px-2.5 py-1 text-[11px] font-semibold text-sky-700 dark:text-sky-300">
|
||||
{payload.sheetNames.length} sheets
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mt-2 flex flex-wrap gap-x-4 gap-y-1 text-xs text-gray-500">
|
||||
<div className="mt-2 flex flex-wrap gap-x-4 gap-y-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
<span>{formatDateLong(estimateExport.createdAt)}</span>
|
||||
{payload ? <span>{formatBytes(payload.byteLength)}</span> : null}
|
||||
{payload?.rowCount != null ? <span>{payload.rowCount} rows</span> : null}
|
||||
{payload?.lineCount != null ? <span>{payload.lineCount} lines</span> : null}
|
||||
</div>
|
||||
{payload ? (
|
||||
<div className="mt-3 grid gap-2 text-xs text-gray-600 md:grid-cols-2 xl:grid-cols-4">
|
||||
<div className="rounded-2xl bg-gray-50 px-3 py-2">
|
||||
<div className="mt-3 grid gap-2 text-xs text-gray-600 dark:text-gray-400 md:grid-cols-2 xl:grid-cols-4">
|
||||
<div className="rounded-2xl bg-gray-50 dark:bg-gray-900 px-3 py-2">
|
||||
<p className="uppercase tracking-wide text-gray-400">Hours</p>
|
||||
<p className="mt-1 font-semibold text-gray-900">
|
||||
<p className="mt-1 font-semibold text-gray-900 dark:text-gray-100">
|
||||
{payload.summary.totalHours.toFixed(1)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-2xl bg-gray-50 px-3 py-2">
|
||||
<div className="rounded-2xl bg-gray-50 dark:bg-gray-900 px-3 py-2">
|
||||
<p className="uppercase tracking-wide text-gray-400">Cost</p>
|
||||
<p className="mt-1 font-semibold text-gray-900">
|
||||
<p className="mt-1 font-semibold text-gray-900 dark:text-gray-100">
|
||||
{formatMoney(
|
||||
payload.summary.totalCostCents,
|
||||
payload.summary.baseCurrency,
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-2xl bg-gray-50 px-3 py-2">
|
||||
<div className="rounded-2xl bg-gray-50 dark:bg-gray-900 px-3 py-2">
|
||||
<p className="uppercase tracking-wide text-gray-400">Price</p>
|
||||
<p className="mt-1 font-semibold text-gray-900">
|
||||
<p className="mt-1 font-semibold text-gray-900 dark:text-gray-100">
|
||||
{formatMoney(
|
||||
payload.summary.totalPriceCents,
|
||||
payload.summary.baseCurrency,
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-2xl bg-gray-50 px-3 py-2">
|
||||
<div className="rounded-2xl bg-gray-50 dark:bg-gray-900 px-3 py-2">
|
||||
<p className="uppercase tracking-wide text-gray-400">Margin</p>
|
||||
<p className="mt-1 font-semibold text-gray-900">
|
||||
<p className="mt-1 font-semibold text-gray-900 dark:text-gray-100">
|
||||
{payload.summary.marginPercent.toFixed(0)}%
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="mt-3 text-xs text-amber-700">
|
||||
<p className="mt-3 text-xs text-amber-700 dark:text-amber-300">
|
||||
Legacy export record detected. Regenerate it to get downloadable serializer output.
|
||||
</p>
|
||||
)}
|
||||
@@ -204,7 +204,7 @@ export function ExportsTab({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => downloadEstimateExport(estimateExport)}
|
||||
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"
|
||||
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"
|
||||
>
|
||||
Download
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user