129 lines
6.7 KiB
TypeScript
129 lines
6.7 KiB
TypeScript
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>
|
|
);
|
|
};
|