Added Manager Info
CI / build (push) Has been skipped
CI / deploy (push) Successful in 56s

This commit is contained in:
2026-03-19 16:50:07 -05:00
parent 72135a11d2
commit cc0a1ad15d
5 changed files with 254 additions and 87 deletions
+4 -1
View File
@@ -5,6 +5,9 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet" />
<title>item-extractor-solid</title> <title>item-extractor-solid</title>
</head> </head>
@@ -13,4 +16,4 @@
<script type="module" src="/src/index.tsx"></script> <script type="module" src="/src/index.tsx"></script>
</body> </body>
</html> </html>
+18 -2
View File
@@ -38,6 +38,8 @@ interface EstimateBuilderProps {
} }
const EstimateBuilder: Component<EstimateBuilderProps> = (props) => { const EstimateBuilder: Component<EstimateBuilderProps> = (props) => {
type PrintMode = 'finalized' | 'manager';
// 1. Initialize State // 1. Initialize State
const { scopes, setScopes, subScopes, setSubScopes, estimateItems: items, setEstimateItems: setItems, clientInfo, jobInfo, generalEstimateNotes, setGeneralEstimateNotes, generalPreNotes, setGeneralPreNotes } = appStore; const { scopes, setScopes, subScopes, setSubScopes, estimateItems: items, setEstimateItems: setItems, clientInfo, jobInfo, generalEstimateNotes, setGeneralEstimateNotes, generalPreNotes, setGeneralPreNotes } = appStore;
@@ -56,6 +58,7 @@ const EstimateBuilder: Component<EstimateBuilderProps> = (props) => {
const [pendingCsvImportText, setPendingCsvImportText] = createSignal<string | null>(null); const [pendingCsvImportText, setPendingCsvImportText] = createSignal<string | null>(null);
const [excludedScopeIds, setExcludedScopeIds] = createSignal<string[]>([]); const [excludedScopeIds, setExcludedScopeIds] = createSignal<string[]>([]);
const [excludedSubScopeIds, setExcludedSubScopeIds] = createSignal<string[]>([]); const [excludedSubScopeIds, setExcludedSubScopeIds] = createSignal<string[]>([]);
const [printMode, setPrintMode] = createSignal<PrintMode>('finalized');
// 2. Initialize custom hooks/primitives // 2. Initialize custom hooks/primitives
const { selectedIds, setSelectedIds, lastSelectedId, setLastSelectedId, toggleSelection, clearSelection, copyColumnSum } = useSelectionManager(); const { selectedIds, setSelectedIds, lastSelectedId, setLastSelectedId, toggleSelection, clearSelection, copyColumnSum } = useSelectionManager();
@@ -92,7 +95,19 @@ const EstimateBuilder: Component<EstimateBuilderProps> = (props) => {
const anyDescriptionIsLong = createMemo(() => items.some(item => (item.description || '').length > 120)); const anyDescriptionIsLong = createMemo(() => items.some(item => (item.description || '').length > 120));
const finalizeBid = () => { const finalizeBid = () => {
window.print(); runPrint('finalized');
};
const exportManagerInfo = () => {
runPrint('manager');
};
const runPrint = (mode: PrintMode) => {
setPrintMode(mode);
window.requestAnimationFrame(() => {
window.print();
window.setTimeout(() => setPrintMode('finalized'), 0);
});
}; };
const addScope = () => { const addScope = () => {
@@ -347,6 +362,7 @@ const EstimateBuilder: Component<EstimateBuilderProps> = (props) => {
generalPreNotes={generalPreNotes()} generalPreNotes={generalPreNotes()}
formatNumber={formatNumber} formatNumber={formatNumber}
hideColumns={anyDescriptionIsLong()} hideColumns={anyDescriptionIsLong()}
variant={printMode()}
/> />
<div class="no-print flex flex-col min-h-dvh"> <div class="no-print flex flex-col min-h-dvh">
@@ -465,7 +481,7 @@ const EstimateBuilder: Component<EstimateBuilderProps> = (props) => {
<ValidationStatusBar validationStats={validationStats()} /> <ValidationStatusBar validationStats={validationStats()} />
<ExecutiveSummary totals={grandTotals()} onFinalizeBid={finalizeBid} /> <ExecutiveSummary totals={grandTotals()} onFinalizeBid={finalizeBid} onExportManagerInfo={exportManagerInfo} />
</main> </main>
</div> </div>
+216 -75
View File
@@ -28,9 +28,13 @@ interface PrintEstimateProps {
generalEstimateNotes: string; generalEstimateNotes: string;
formatNumber: (num: number) => string; formatNumber: (num: number) => string;
hideColumns?: boolean; hideColumns?: boolean;
variant?: 'finalized' | 'manager';
} }
const PrintEstimate: Component<PrintEstimateProps> = (props) => { const PrintEstimate: Component<PrintEstimateProps> = (props) => {
const variant = () => props.variant ?? 'finalized';
const isManagerVariant = () => variant() === 'manager';
const getScopeTotal = (scopeId: string) => { const getScopeTotal = (scopeId: string) => {
let scopeSubtotal = 0; let scopeSubtotal = 0;
let scopeMarkup = 0; let scopeMarkup = 0;
@@ -75,6 +79,28 @@ const PrintEstimate: Component<PrintEstimateProps> = (props) => {
return scopeSubtotal + scopeMarkup + scopeContingency + mgmtFee + deliveryFee + modifiersTotal; return scopeSubtotal + scopeMarkup + scopeContingency + mgmtFee + deliveryFee + modifiersTotal;
}; };
const getScopeItems = (scopeId: string) => {
const scopeLevelItems = props.items.filter((item: EstimateItem) => item.scopeId === scopeId && !item.subScopeId);
const scopeSubScopes = props.subScopes
.filter((subScope) => subScope.scopeId === scopeId)
.map((subScope) => ({
subScope,
items: props.items.filter((item: EstimateItem) => item.subScopeId === subScope.id)
}))
.filter((entry) => entry.items.length > 0);
return {
scopeLevelItems,
scopeSubScopes
};
};
const getItemSubtotal = (item: EstimateItem) => item.quantity * item.unitPrice;
const getManagerScopeVisibleTotal = (scopeId: string) =>
props.items
.filter((item: EstimateItem) => item.scopeId === scopeId)
.reduce((sum, item) => sum + getItemSubtotal(item), 0);
return ( return (
<div class="print-only p-6 bg-white font-sans max-w-[8.5in] mx-auto text-sm leading-normal text-foreground"> <div class="print-only p-6 bg-white font-sans max-w-[8.5in] mx-auto text-sm leading-normal text-foreground">
{/* Company Header */} {/* Company Header */}
@@ -88,7 +114,9 @@ const PrintEstimate: Component<PrintEstimateProps> = (props) => {
</div> </div>
</div> </div>
<div class="text-right"> <div class="text-right">
<h2 class="text-2xl font-black text-foreground uppercase tracking-tighter leading-none">Estimate</h2> <h2 class="text-2xl font-black text-foreground uppercase tracking-tighter leading-none">
{isManagerVariant() ? 'Manager Info' : 'Estimate'}
</h2>
<p class="text-[10px] text-muted-foreground italic uppercase tracking-widest font-bold mb-1">*** NOT A BILL ***</p> <p class="text-[10px] text-muted-foreground italic uppercase tracking-widest font-bold mb-1">*** NOT A BILL ***</p>
<p class="text-foreground font-bold text-xs">Date: {new Date().toLocaleDateString()}</p> <p class="text-foreground font-bold text-xs">Date: {new Date().toLocaleDateString()}</p>
</div> </div>
@@ -110,6 +138,10 @@ const PrintEstimate: Component<PrintEstimateProps> = (props) => {
<div> <div>
<h3 class="text-[10px] font-bold text-muted-foreground uppercase tracking-wider border-b border-border pb-1 mb-2">Project Details</h3> <h3 class="text-[10px] font-bold text-muted-foreground uppercase tracking-wider border-b border-border pb-1 mb-2">Project Details</h3>
<div class="pl-1 space-y-1"> <div class="pl-1 space-y-1">
<div class="flex gap-2 text-xs">
<span class="font-bold text-muted-foreground/60 w-16 uppercase text-[10px] pt-0.5">Job No.</span>
<span class="font-bold text-foreground">{props.jobInfo.number || '---'}</span>
</div>
<div class="flex gap-2 text-xs"> <div class="flex gap-2 text-xs">
<span class="font-bold text-muted-foreground/60 w-16 uppercase text-[10px] pt-0.5">Project</span> <span class="font-bold text-muted-foreground/60 w-16 uppercase text-[10px] pt-0.5">Project</span>
<span class="font-bold text-foreground">{props.jobInfo.name || '---'}</span> <span class="font-bold text-foreground">{props.jobInfo.name || '---'}</span>
@@ -129,7 +161,7 @@ const PrintEstimate: Component<PrintEstimateProps> = (props) => {
</div> </div>
{/* General Pre-Notes */} {/* General Pre-Notes */}
<Show when={props.generalPreNotes}> <Show when={props.generalPreNotes && !isManagerVariant()}>
<div class="mb-6 p-4 bg-muted/30 rounded-xl border border-border/40 break-inside-avoid shadow-sm outline outline-1 outline-border/20"> <div class="mb-6 p-4 bg-muted/30 rounded-xl border border-border/40 break-inside-avoid shadow-sm outline outline-1 outline-border/20">
<div class="text-[11px] text-foreground/80 leading-relaxed font-medium italic"> <div class="text-[11px] text-foreground/80 leading-relaxed font-medium italic">
{props.generalPreNotes} {props.generalPreNotes}
@@ -138,47 +170,154 @@ const PrintEstimate: Component<PrintEstimateProps> = (props) => {
</Show> </Show>
{/* Scope Details Table */} {/* Scope Details Table */}
<div class="mb-8 border-t-2 border-foreground"> <Show
<div class="grid grid-cols-12 gap-2 py-2 bg-muted/40 px-3 text-[10px] font-bold text-muted-foreground uppercase tracking-wider border-b border-border/60"> when={isManagerVariant()}
<div class="col-span-2">Scope</div> fallback={
<div class="col-span-8 pl-2 border-l border-border/60">Description</div> <div class="mb-8 border-t-2 border-foreground">
<div class="col-span-2 text-right">Subtotal</div> <div class="grid grid-cols-12 gap-2 py-2 bg-muted/40 px-3 text-[10px] font-bold text-muted-foreground uppercase tracking-wider border-b border-border/60">
</div> <div class="col-span-2">Scope</div>
<div class="divide-y divide-border/20"> <div class="col-span-8 pl-2 border-l border-border/60">Description</div>
<For each={props.scopes}> <div class="col-span-2 text-right">Subtotal</div>
{(scope) => ( </div>
<div class="grid grid-cols-12 gap-2 items-start py-4 px-3 hover:bg-muted/10 transition-colors break-inside-avoid"> <div class="divide-y divide-border/20">
<div class="col-span-2"> <For each={props.scopes}>
<h4 class="text-xs font-black text-foreground uppercase leading-snug">{scope.name}</h4> {(scope) => (
</div> <div class="grid grid-cols-12 gap-2 items-start py-4 px-3 hover:bg-muted/10 transition-colors break-inside-avoid">
<div class="col-span-8 pl-2 border-l border-dashed border-border/60 text-foreground/80 text-xs leading-relaxed"> <div class="col-span-2">
<div class="prose prose-sm max-w-none prose-strong:text-foreground prose-em:text-foreground/70"> <h4 class="text-xs font-black text-foreground uppercase leading-snug">{scope.name}</h4>
<For each={(scope.bidDescription || 'See detailed specifications.').split('\n')}> </div>
{(line) => { <div class="col-span-8 pl-2 border-l border-dashed border-border/60 text-foreground/80 text-xs leading-relaxed">
let processed = line; <div class="prose prose-sm max-w-none prose-strong:text-foreground prose-em:text-foreground/70">
processed = processed.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>'); <For each={(scope.bidDescription || 'See detailed specifications.').split('\n')}>
processed = processed.replace(/(\*|_)(.*?)\1/g, '<em>$2</em>'); {(line) => {
if (processed.trim().startsWith('- ')) { let processed = line;
return <li class="ml-3 list-disc pl-1 text-foreground/90" innerHTML={processed.substring(2)} />; processed = processed.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
} processed = processed.replace(/(\*|_)(.*?)\1/g, '<em>$2</em>');
// Handle empty lines more gracefully in compact mode if (processed.trim().startsWith('- ')) {
if (!processed.trim()) return <div class="h-1"></div>; return <li class="ml-3 list-disc pl-1 text-foreground/90" innerHTML={processed.substring(2)} />;
return <p class="mb-0.5 last:mb-0 text-foreground/90" innerHTML={processed} />; }
}} if (!processed.trim()) return <div class="h-1"></div>;
</For> return <p class="mb-0.5 last:mb-0 text-foreground/90" innerHTML={processed} />;
}}
</For>
</div>
</div>
<div class="col-span-2 text-right font-black text-foreground text-sm">
${props.formatNumber(getScopeTotal(scope.id))}
</div>
</div> </div>
</div> )}
<div class="col-span-2 text-right font-black text-foreground text-sm"> </For>
${props.formatNumber(getScopeTotal(scope.id))} </div>
</div> </div>
</div> }
)} >
<div class="mb-8 space-y-6">
<For each={props.scopes}>
{(scope) => {
const scopeItems = () => getScopeItems(scope.id);
return (
<section class={`border border-border/60 rounded-2xl overflow-hidden ${isManagerVariant() ? '' : 'break-inside-avoid'}`}>
<div class="px-4 py-3 bg-muted/30 border-b border-border/60 flex items-start justify-between gap-4">
<div>
<h4 class="text-sm font-black text-foreground uppercase">{scope.name}</h4>
<p class="text-[10px] text-muted-foreground font-bold uppercase tracking-widest mt-1">Scope Total</p>
</div>
<div class="text-right">
<div class="text-lg font-black text-foreground">${props.formatNumber(getManagerScopeVisibleTotal(scope.id))}</div>
</div>
</div>
<div class="p-4 space-y-4">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div class="rounded-xl border border-border/50 p-3 bg-white">
<div class="text-[10px] font-bold text-muted-foreground uppercase tracking-widest mb-2">Scope Description</div>
<div class="prose prose-sm max-w-none prose-strong:text-foreground prose-em:text-foreground/70 text-xs text-foreground/90">
<For each={(scope.bidDescription || 'See detailed specifications.').split('\n')}>
{(line) => {
let processed = line;
processed = processed.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
processed = processed.replace(/(\*|_)(.*?)\1/g, '<em>$2</em>');
if (processed.trim().startsWith('- ')) {
return <li class="ml-3 list-disc pl-1 text-foreground/90" innerHTML={processed.substring(2)} />;
}
if (!processed.trim()) return <div class="h-1"></div>;
return <p class="mb-0.5 last:mb-0 text-foreground/90" innerHTML={processed} />;
}}
</For>
</div>
</div>
<div class="rounded-xl border border-border/50 p-3 bg-white">
<div class="text-[10px] font-bold text-muted-foreground uppercase tracking-widest mb-2">Estimator Notes</div>
<div class="text-xs text-foreground/90 whitespace-pre-wrap leading-relaxed">
{scope.estimatorNotes?.trim() || 'No estimator notes provided.'}
</div>
</div>
</div>
<div class="border border-border/50 rounded-xl overflow-hidden">
<div class="grid grid-cols-12 gap-3 px-3 py-2 bg-muted/20 text-[10px] font-bold text-muted-foreground uppercase tracking-wider border-b border-border/50">
<div class="col-span-2">Section</div>
<div class="col-span-6">Line Item</div>
<div class="col-span-1 text-right">Qty</div>
<div class="col-span-1 text-right">Unit</div>
<div class="col-span-2 text-right">Subtotal</div>
</div>
<Show when={scopeItems().scopeLevelItems.length > 0}>
<div class="border-b border-border/40">
<div class="px-3 py-2 bg-background text-[10px] font-black uppercase tracking-widest text-muted-foreground">Scope Level</div>
<For each={scopeItems().scopeLevelItems}>
{(item) => (
<div class="grid grid-cols-12 gap-3 px-3 py-2 text-xs border-t border-border/20 items-start">
<div class="col-span-2 font-bold text-foreground">Scope Level</div>
<div class="col-span-6 text-foreground/90">{item.description}</div>
<div class="col-span-1 text-right text-foreground">{item.quantity}</div>
<div class="col-span-1 text-right text-foreground">${props.formatNumber(item.unitPrice)}</div>
<div class="col-span-2 text-right font-bold text-foreground">${props.formatNumber(getItemSubtotal(item))}</div>
</div>
)}
</For>
</div>
</Show>
<For each={scopeItems().scopeSubScopes}>
{(entry) => (
<div class="border-b last:border-b-0 border-border/40">
<div class="px-3 py-2 bg-background text-[10px] font-black uppercase tracking-widest text-muted-foreground">
{entry.subScope.name}
</div>
<For each={entry.items}>
{(item) => (
<div class="grid grid-cols-12 gap-3 px-3 py-2 text-xs border-t border-border/20 items-start">
<div class="col-span-2 font-bold text-foreground">{entry.subScope.name}</div>
<div class="col-span-6 text-foreground/90">{item.description}</div>
<div class="col-span-1 text-right text-foreground">{item.quantity}</div>
<div class="col-span-1 text-right text-foreground">${props.formatNumber(item.unitPrice)}</div>
<div class="col-span-2 text-right font-bold text-foreground">${props.formatNumber(getItemSubtotal(item))}</div>
</div>
)}
</For>
</div>
)}
</For>
<Show when={scopeItems().scopeLevelItems.length === 0 && scopeItems().scopeSubScopes.length === 0}>
<div class="px-3 py-4 text-xs text-muted-foreground italic">No line items assigned to this scope.</div>
</Show>
</div>
</div>
</section>
);
}}
</For> </For>
</div> </div>
</div> </Show>
{/* General Estimate Notes */} {/* General Estimate Notes */}
<Show when={props.generalEstimateNotes}> <Show when={props.generalEstimateNotes && !isManagerVariant()}>
<div class="mb-8"> <div class="mb-8">
<h5 class="text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-1 border-b border-border/40 pb-1">General Estimate Notes</h5> <h5 class="text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-1 border-b border-border/40 pb-1">General Estimate Notes</h5>
<div class="text-[11px] text-foreground/80 leading-relaxed whitespace-pre-wrap font-medium"> <div class="text-[11px] text-foreground/80 leading-relaxed whitespace-pre-wrap font-medium">
@@ -188,51 +327,53 @@ const PrintEstimate: Component<PrintEstimateProps> = (props) => {
</Show> </Show>
{/* Final Section: Totals & Signature aligned */} {/* Final Section: Totals & Signature aligned */}
<div class="grid grid-cols-12 gap-8 items-end mb-8 break-inside-avoid"> <Show when={!isManagerVariant()}>
<div class="col-span-7"> <div class="grid grid-cols-12 gap-8 items-end mb-8 break-inside-avoid">
<div class="flex flex-col gap-2"> <div class="col-span-7">
<div class="w-full flex items-end gap-2 pt-2"> <div class="flex flex-col gap-2">
<span class="text-lg font-serif text-muted-foreground/40 translate-y-1">X</span> <div class="w-full flex items-end gap-2 pt-2">
<div class="flex-1 border-b border-foreground h-6"></div> <span class="text-lg font-serif text-muted-foreground/40 translate-y-1">X</span>
<div class="flex-1 border-b border-foreground h-6"></div>
</div>
<div class="flex justify-between items-start px-0.5">
<p class="text-[9px] text-muted-foreground/60 italic max-w-[280px] leading-tight">
A Work Order is then established and Cardoza Construction, LLC will proceed with work.
Valid for 30 days from original date.
</p>
<span class="text-[9px] font-bold text-muted-foreground uppercase tracking-wider">Authorized Signature</span>
</div>
</div> </div>
<div class="flex justify-between items-start px-0.5"> </div>
<p class="text-[9px] text-muted-foreground/60 italic max-w-[280px] leading-tight">
A Work Order is then established and Cardoza Construction, LLC will proceed with work. <div class="col-span-5 text-right space-y-1">
Valid for 30 days from original date. <div class="flex justify-end items-center gap-6 p-4 bg-white text-foreground rounded-2xl mt-3 border-2 border-foreground shadow-sm">
</p> <div class="text-[9px] font-black uppercase tracking-widest text-muted-foreground/60">Grand Total</div>
<span class="text-[9px] font-bold text-muted-foreground uppercase tracking-wider">Authorized Signature</span> <div class="text-3xl font-black tracking-tighter text-foreground">${props.formatNumber(props.grandTotals().grandTotal)}</div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-span-5 text-right space-y-1"> {/* Terms & Legal Notices */}
<div class="flex justify-end items-center gap-6 p-4 bg-white text-foreground rounded-2xl mt-3 border-2 border-foreground shadow-sm"> <div class="mt-8 pt-6 border-t-2 border-foreground space-y-4">
<div class="text-[9px] font-black uppercase tracking-widest text-muted-foreground/60">Grand Total</div> <div class="break-inside-avoid">
<div class="text-3xl font-black tracking-tighter text-foreground">${props.formatNumber(props.grandTotals().grandTotal)}</div> <h5 class="text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-1">Terms & Conditions</h5>
<p class="text-[10px] font-bold text-foreground/70 leading-relaxed">
Unless otherwise agreed, credit approval and 50% down is required prior to scheduling/starting work.
(Credit card authorization may be substituted for jobs under $2,000)
</p>
</div>
<div class="break-inside-avoid">
<h5 class="text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-1">Notice to Owner</h5>
<div class="text-[10px] font-bold text-foreground/60 leading-relaxed whitespace-pre-wrap">
{NOTICE_TO_OWNERS.replace('NOTICE TO OWNER', '').trim()}
</div>
</div> </div>
</div> </div>
</div>
{/* Terms & Legal Notices */} {/* Print Page Counter */}
<div class="mt-8 pt-6 border-t-2 border-foreground space-y-4"> <div class="print-page-number"></div>
<div class="break-inside-avoid"> </Show>
<h5 class="text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-1">Terms & Conditions</h5>
<p class="text-[10px] font-bold text-foreground/70 leading-relaxed">
Unless otherwise agreed, credit approval and 50% down is required prior to scheduling/starting work.
(Credit card authorization may be substituted for jobs under $2,000)
</p>
</div>
<div class="break-inside-avoid">
<h5 class="text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-1">Notice to Owner</h5>
<div class="text-[10px] font-bold text-foreground/60 leading-relaxed whitespace-pre-wrap">
{NOTICE_TO_OWNERS.replace('NOTICE TO OWNER', '').trim()}
</div>
</div>
</div>
{/* Print Page Counter */}
<div class="print-page-number"></div>
</div> </div>
); );
}; };
@@ -11,6 +11,7 @@ interface ExecutiveSummaryProps {
grandTotal: number; grandTotal: number;
}; };
onFinalizeBid: () => void; onFinalizeBid: () => void;
onExportManagerInfo: () => void;
} }
export const ExecutiveSummary: Component<ExecutiveSummaryProps> = (props) => { export const ExecutiveSummary: Component<ExecutiveSummaryProps> = (props) => {
@@ -68,12 +69,20 @@ export const ExecutiveSummary: Component<ExecutiveSummaryProps> = (props) => {
{formatNumber(props.totals.grandTotal)} {formatNumber(props.totals.grandTotal)}
</div> </div>
<div class="mt-8"> <div class="mt-8">
<button <div class="space-y-3">
onClick={props.onFinalizeBid} <button
class="w-full py-5 bg-primary hover:bg-primary/90 text-primary-foreground rounded-2xl font-black text-xs uppercase tracking-[0.2em] transition-all shadow-xl shadow-primary/20 active:scale-95 flex items-center justify-center gap-3" onClick={props.onFinalizeBid}
> class="w-full py-5 bg-primary hover:bg-primary/90 text-primary-foreground rounded-2xl font-black text-xs uppercase tracking-[0.2em] transition-all shadow-xl shadow-primary/20 active:scale-95 flex items-center justify-center gap-3"
<Plus class="w-5 h-5" /> Finalize Bid >
</button> <Plus class="w-5 h-5" /> Finalize Bid
</button>
<button
onClick={props.onExportManagerInfo}
class="w-full py-4 bg-muted hover:bg-muted/80 text-foreground rounded-2xl font-black text-xs uppercase tracking-[0.2em] transition-all border border-border active:scale-95 flex items-center justify-center gap-3"
>
<Info class="w-5 h-5" /> Manager Info
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
+1 -3
View File
@@ -2,8 +2,6 @@
@plugin "@tailwindcss/typography"; @plugin "@tailwindcss/typography";
/* Import Standard Shadcn Font (Inter) */ /* Import Standard Shadcn Font (Inter) */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap');
@theme { @theme {
/* Clean, single-font family for a modern SaaS look */ /* Clean, single-font family for a modern SaaS look */
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif; --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
@@ -130,4 +128,4 @@
.shadow-premium-hover:hover { .shadow-premium-hover:hover {
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.07), 0 8px 10px -6px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(0, 0, 0, 0.04); box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.07), 0 8px 10px -6px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(0, 0, 0, 0.04);
} }