Breakup of EstimateBuilder.tsx
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import { For, Show } from 'solid-js';
|
||||
import { FolderKanban, Plus, FileText, ChevronDown } from 'lucide-solid';
|
||||
import ScopeCard from '../ScopeCard';
|
||||
import NoteEditor from '../NoteEditor';
|
||||
import type { Scope, SubScope, EstimateItem } from '../../types';
|
||||
|
||||
interface ScopesListProps {
|
||||
scopes: Scope[];
|
||||
subScopes: SubScope[];
|
||||
items: EstimateItem[];
|
||||
isNotesExpanded: boolean;
|
||||
setIsNotesExpanded: (expanded: boolean) => void;
|
||||
generalEstimateNotes: string;
|
||||
setGeneralEstimateNotes: (notes: string) => void;
|
||||
|
||||
updateScope: (id: string, updates: Partial<Scope>) => void;
|
||||
deleteScope: (id: string) => void;
|
||||
updateItem: (id: string, updates: Partial<EstimateItem>) => void;
|
||||
deleteItem: (id: string) => void;
|
||||
addSubScope: (scopeId: string) => void;
|
||||
updateSubScope: (id: string, updates: Partial<SubScope>) => void;
|
||||
deleteSubScope: (id: string) => void;
|
||||
handleDragStart: (e: DragEvent, id: string) => void;
|
||||
handleDrop: (e: DragEvent, scopeId?: string, subScopeId?: string) => void;
|
||||
handleBulkUpdate: (scopeId: string, subScopeId: string | undefined, updates: any) => void;
|
||||
duplicateItem: (id: string) => void;
|
||||
copySubScopeItems: (sourceSubScopeId: string, targetSubScopeId: string) => void;
|
||||
setApplyTemplateScopeId: (id: string | null) => void;
|
||||
isSidebarCollapsed: () => boolean;
|
||||
selectedIds: string[];
|
||||
toggleSelection: (id: string, isMulti: boolean, isShift: boolean) => void;
|
||||
clearSelection: (e?: MouseEvent) => void;
|
||||
anyDescriptionIsLong: () => boolean;
|
||||
|
||||
addScope: () => void;
|
||||
}
|
||||
|
||||
export const ScopesList: Component<ScopesListProps> = (props) => {
|
||||
return (
|
||||
<div class="space-y-12">
|
||||
<For each={props.scopes}>
|
||||
{(scope) => (
|
||||
<div id={`scope-${scope.id}`}>
|
||||
<ScopeCard
|
||||
scope={scope}
|
||||
subScopes={props.subScopes.filter(ss => ss.scopeId === scope.id)}
|
||||
items={props.items.filter(i => i.scopeId === scope.id)}
|
||||
onUpdateScope={props.updateScope}
|
||||
onDeleteScope={props.deleteScope}
|
||||
onUpdateItem={props.updateItem}
|
||||
onDeleteItem={props.deleteItem}
|
||||
onAddSubScope={props.addSubScope}
|
||||
onUpdateSubScope={props.updateSubScope}
|
||||
onDeleteSubScope={props.deleteSubScope}
|
||||
onDragStart={props.handleDragStart}
|
||||
onDrop={props.handleDrop}
|
||||
onBulkUpdate={props.handleBulkUpdate}
|
||||
onDuplicateItem={props.duplicateItem}
|
||||
allScopes={props.scopes}
|
||||
allSubScopes={props.subScopes}
|
||||
onCopySubScopeItems={props.copySubScopeItems}
|
||||
onOpenApplyTemplate={props.setApplyTemplateScopeId}
|
||||
isSidebarCollapsed={props.isSidebarCollapsed}
|
||||
selectedIds={props.selectedIds}
|
||||
onToggleSelection={props.toggleSelection}
|
||||
onClearSelection={props.clearSelection}
|
||||
hideColumns={props.anyDescriptionIsLong()}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
|
||||
<Show when={props.scopes.length > 0}>
|
||||
<div class="mt-8 bg-card border border-border rounded-3xl overflow-hidden shadow-premium transition-all duration-300">
|
||||
<button
|
||||
onClick={() => props.setIsNotesExpanded(!props.isNotesExpanded)}
|
||||
class="w-full flex items-center justify-between p-6 hover:bg-muted/30 transition-colors"
|
||||
>
|
||||
<div class="flex items-center gap-3 text-foreground">
|
||||
<div class="p-2 bg-muted rounded-xl shadow-sm">
|
||||
<FileText class="w-5 h-5 text-muted-foreground" />
|
||||
</div>
|
||||
<div class="text-left">
|
||||
<h3 class="font-black text-lg">General Estimate Notes</h3>
|
||||
<p class="text-muted-foreground text-xs font-bold uppercase tracking-wider">Configure standard project terms and exclusions</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class={`transition-transform duration-300 ${props.isNotesExpanded ? 'rotate-180' : ''}`}>
|
||||
<ChevronDown class="w-5 h-5 text-muted-foreground" />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div class={`transition-all duration-300 ease-in-out ${props.isNotesExpanded ? 'max-h-[2000px] border-t border-gray-50' : 'max-h-0'}`}>
|
||||
<div class="p-6 pt-2">
|
||||
<NoteEditor
|
||||
label="Inclusions, Exclusions & Terms"
|
||||
value={props.generalEstimateNotes}
|
||||
onUpdate={props.setGeneralEstimateNotes}
|
||||
icon={FileText}
|
||||
placeholder="Add project-wide notes here..."
|
||||
highlight={props.generalEstimateNotes.includes('#')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={props.scopes.length === 0}>
|
||||
<div class="flex flex-col items-center justify-center py-20 text-center space-y-6">
|
||||
<div class="p-8 bg-muted rounded-full text-muted-foreground/30 shadow-inner">
|
||||
<FolderKanban class="w-16 h-16" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<h3 class="text-2xl font-black text-foreground tracking-tight">No Scopes Defined</h3>
|
||||
<p class="text-muted-foreground max-w-sm mx-auto font-medium">Click "New Scope" in the header to start building your estimate structure.</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={props.addScope}
|
||||
class="flex items-center gap-2 px-8 py-4 bg-primary text-primary-foreground rounded-2xl font-black text-xs uppercase tracking-widest hover:bg-primary/90 transition-all shadow-xl shadow-primary/20 active:scale-95"
|
||||
>
|
||||
<Plus class="w-5 h-5" /> Start First Scope
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user