feat(estimates): add EstimatesPage hub with templates tab, estimate detail drawer, and template editor

- EstimatesPage: tabbed hub with Estimates and Templates tabs, KPI strip, search + filter
- EstimateDetailModal: right-side drawer showing scope of work, computed line items, and financial summary from template + roof area
- TemplateEditorModal: create/edit/duplicate templates with segmented mode toggle
- Owner-only template management (create, edit, duplicate, delete); Admin/FA read-only view
- Duplicate mode: toggle between 'Save as new copy' (default, editable name) and 'Replace original'
- MASTER_TEMPLATES and MOCK_ESTIMATES moved into mockStore with full CRUD helpers
- TemplateSelectionModal now reads from mockStore instead of local stub
- Sidebar nav updated to point to /estimates hub for all roles
- Routes added: /emp/fa/estimates, /admin/estimates, /owner/estimates
This commit is contained in:
Satyam-Rastogi
2026-04-01 16:13:36 +05:30
parent 9be8ec911f
commit 8f9cb9dd48
7 changed files with 1690 additions and 5 deletions
+447
View File
@@ -3197,6 +3197,426 @@ export const DISPATCH_WEEKLY_STATS = [
},
];
// ---------------------------------------------------------------------------
// MASTER TEMPLATES
// ---------------------------------------------------------------------------
export const TEMPLATE_CATEGORIES = ['Roofing', 'Siding', 'Gutters', 'Windows', 'Insulation', 'Other'];
const MASTER_TEMPLATES_INITIAL = [
{
id: 'atlas_glassmaster',
name: 'Atlas GlassMaster',
category: 'Roofing',
brand: 'Atlas',
description: [
"Remove existing shingles down to deck.",
"Re-nail any loose wood. If bad or rotten wood is discovered, it will be replaced at a price of $50 per sheet.",
"Install 3' of Atlas WeatherMaster ice and water shield at all gutter lines, rakes edges, and valleys.",
"Install Atlas Summit 60 Synthetic underlayment.",
"Install Atlas Glassmaster AR Shingles."
],
materials: [
{ desc: 'Atlas Glassmaster AR', baseUnitCost: 37.67, uom: 'SQ' },
{ desc: 'Atlas Pro-Cut Hip & Ridge', baseUnitCost: 78.50, uom: 'BD' },
{ desc: 'Atlas 6X Pro-Cut Starter', baseUnitCost: 70.80, uom: 'BD' },
{ desc: 'Atlas Summit 60 Synthetic Underlayment', baseUnitCost: 116.00, uom: 'RL' },
{ desc: 'Atlas WeatherMaster Ice & Water', baseUnitCost: 65.00, uom: 'RL' },
{ desc: 'Coil Nails 1 1/4"', baseUnitCost: 35.00, uom: 'BX' },
{ desc: 'Plastic Cap Nails', baseUnitCost: 28.50, uom: 'BX' },
{ desc: 'Geocel 2300 Clear Sealant', baseUnitCost: 8.95, uom: 'EA' }
]
},
{
id: 'atlas_glassmaster_srs',
name: 'Atlas GlassMaster - SRS',
category: 'Roofing',
brand: 'SRS',
description: [
"Remove existing shingles down to deck.",
"Re-nail any loose wood.",
"Install 3' of Atlas WeatherMaster ice and water shield at all gutter lines, rakes edges, and valleys.",
"Install Atlas Summit 60 Synthetic underlayment.",
"Install Atlas Glassmaster AR Shingles (SRS distribution)."
],
materials: [
{ desc: 'Atlas Glassmaster AR', baseUnitCost: 37.67, uom: 'SQ' },
{ desc: 'Atlas Pro-Cut Hip & Ridge', baseUnitCost: 78.50, uom: 'BD' },
{ desc: 'Atlas 6X Pro-Cut Starter', baseUnitCost: 70.80, uom: 'BD' },
{ desc: 'Atlas Summit 60 Synthetic Underlayment', baseUnitCost: 116.00, uom: 'RL' },
{ desc: 'Atlas WeatherMaster Ice & Water', baseUnitCost: 65.00, uom: 'RL' },
{ desc: 'Coil Nails 1 1/4"', baseUnitCost: 35.00, uom: 'BX' },
{ desc: 'Plastic Cap Nails', baseUnitCost: 28.50, uom: 'BX' },
{ desc: 'Geocel 2300 Clear Sealant', baseUnitCost: 8.95, uom: 'EA' }
]
},
{
id: 'atlas_pinnacle_pristine',
name: 'Atlas Pinnacle Pristine',
category: 'Roofing',
brand: 'Atlas',
description: [
"Remove existing shingles down to deck.",
"Re-nail any loose wood.",
"Install 3' of Atlas WeatherMaster ice and water shield.",
"Install Atlas Summit 60 Synthetic underlayment.",
"Install Atlas Pinnacle Pristine Lifetime Dimensional Shingles."
],
materials: [
{ desc: 'Atlas Pinnacle Pristine', baseUnitCost: 42.50, uom: 'SQ' },
{ desc: 'Atlas Pro-Cut High Profile Hip & Ridge', baseUnitCost: 85.00, uom: 'BD' },
{ desc: 'Atlas Pro-Cut Starter', baseUnitCost: 72.00, uom: 'BD' },
{ desc: 'Atlas Summit 60 Synthetic Underlayment', baseUnitCost: 116.00, uom: 'RL' },
{ desc: 'Atlas WeatherMaster Ice & Water', baseUnitCost: 65.00, uom: 'RL' },
{ desc: 'Coil Nails 1 1/4"', baseUnitCost: 35.00, uom: 'BX' },
{ desc: 'Plastic Cap Nails', baseUnitCost: 28.50, uom: 'BX' },
{ desc: 'Geocel 2300 Clear Sealant', baseUnitCost: 8.95, uom: 'EA' }
]
},
{
id: 'atlas_stormmaster_shake',
name: 'Atlas StormMaster Shake',
category: 'Roofing',
brand: 'Atlas',
description: [
"Remove existing shingles down to deck.",
"Re-nail any loose wood.",
"Install 3' of Atlas WeatherMaster ice and water shield at all eaves and valleys.",
"Install Atlas Summit 60 Synthetic underlayment.",
"Install Atlas StormMaster Shake Impact Resistant Shingles."
],
materials: [
{ desc: 'Atlas StormMaster Shake', baseUnitCost: 55.00, uom: 'SQ' },
{ desc: 'Atlas Pro-Cut Hip & Ridge', baseUnitCost: 78.50, uom: 'BD' },
{ desc: 'Atlas 6X Pro-Cut Starter', baseUnitCost: 70.80, uom: 'BD' },
{ desc: 'Atlas Summit 60 Synthetic Underlayment', baseUnitCost: 116.00, uom: 'RL' },
{ desc: 'Atlas WeatherMaster Ice & Water', baseUnitCost: 65.00, uom: 'RL' },
{ desc: 'Coil Nails 1 1/4"', baseUnitCost: 35.00, uom: 'BX' },
{ desc: 'Plastic Cap Nails', baseUnitCost: 28.50, uom: 'BX' },
{ desc: 'Geocel 2300 Clear Sealant', baseUnitCost: 8.95, uom: 'EA' }
]
},
{
id: 'certainteed_landmark',
name: 'CertainTeed Landmark AR',
category: 'Roofing',
brand: 'CertainTeed',
description: [
"Remove existing shingles down to deck.",
"Re-nail any loose wood.",
"Install CertainTeed WinterGuard ice and water shield.",
"Install CertainTeed RoofRunner Synthetic underlayment.",
"Install CertainTeed Landmark AR Shingles."
],
materials: [
{ desc: 'CertainTeed Landmark AR', baseUnitCost: 44.00, uom: 'SQ' },
{ desc: 'CertainTeed ShadowRidge', baseUnitCost: 82.00, uom: 'BD' },
{ desc: 'CertainTeed SwiftStart', baseUnitCost: 75.00, uom: 'BD' },
{ desc: 'CertainTeed RoofRunner', baseUnitCost: 125.00, uom: 'RL' },
{ desc: 'CertainTeed WinterGuard', baseUnitCost: 70.00, uom: 'RL' },
{ desc: 'Coil Nails 1 1/4"', baseUnitCost: 35.00, uom: 'BX' },
{ desc: 'Plastic Cap Nails', baseUnitCost: 28.50, uom: 'BX' },
{ desc: 'Geocel 2300 Clear Sealant', baseUnitCost: 8.95, uom: 'EA' }
]
},
{
id: 'certainteed_presidential_shake',
name: 'CertainTeed Presidential Shake TL',
category: 'Roofing',
brand: 'CertainTeed',
description: [
"Remove existing shingles down to deck.",
"Re-nail any loose wood.",
"Install CertainTeed WinterGuard ice and water shield at all eaves and valleys.",
"Install CertainTeed RoofRunner Synthetic underlayment.",
"Install CertainTeed Presidential Shake TL triple-layer shingles."
],
materials: [
{ desc: 'CertainTeed Presidential Shake TL', baseUnitCost: 72.00, uom: 'SQ' },
{ desc: 'CertainTeed Presidential Ridge', baseUnitCost: 92.00, uom: 'BD' },
{ desc: 'CertainTeed SwiftStart', baseUnitCost: 75.00, uom: 'BD' },
{ desc: 'CertainTeed RoofRunner', baseUnitCost: 125.00, uom: 'RL' },
{ desc: 'CertainTeed WinterGuard', baseUnitCost: 70.00, uom: 'RL' },
{ desc: 'Coil Nails 1 1/4"', baseUnitCost: 35.00, uom: 'BX' },
{ desc: 'Plastic Cap Nails', baseUnitCost: 28.50, uom: 'BX' },
{ desc: 'Geocel 2300 Clear Sealant', baseUnitCost: 8.95, uom: 'EA' }
]
},
{
id: 'gaf_timberline_hdz',
name: 'GAF Timberline HDZ',
category: 'Roofing',
brand: 'GAF',
description: [
"Remove existing shingles down to deck.",
"Re-nail any loose wood.",
"Install GAF WeatherWatch ice and water shield at eaves, rakes, and valleys.",
"Install GAF FeltBuster Synthetic underlayment.",
"Install GAF Timberline HDZ Laminated Architectural Shingles."
],
materials: [
{ desc: 'GAF Timberline HDZ', baseUnitCost: 45.00, uom: 'SQ' },
{ desc: 'GAF TimberTex Premium Ridge Cap', baseUnitCost: 85.00, uom: 'BD' },
{ desc: 'GAF Pro-Start Starter Strip', baseUnitCost: 68.00, uom: 'BD' },
{ desc: 'GAF FeltBuster Synthetic Underlayment', baseUnitCost: 110.00, uom: 'RL' },
{ desc: 'GAF WeatherWatch Ice & Water', baseUnitCost: 68.00, uom: 'RL' },
{ desc: 'Coil Nails 1 1/4"', baseUnitCost: 35.00, uom: 'BX' },
{ desc: 'Plastic Cap Nails', baseUnitCost: 28.50, uom: 'BX' },
{ desc: 'Geocel 2300 Clear Sealant', baseUnitCost: 8.95, uom: 'EA' }
]
},
{
id: 'gaf_timberline_ultra_hd',
name: 'GAF Timberline Ultra HD',
category: 'Roofing',
brand: 'GAF',
description: [
"Remove existing shingles down to deck.",
"Re-nail any loose wood.",
"Install GAF WeatherWatch ice and water shield at eaves, rakes, and valleys.",
"Install GAF FeltBuster Synthetic underlayment.",
"Install GAF Timberline Ultra HD extra-thick dimensional shingles."
],
materials: [
{ desc: 'GAF Timberline Ultra HD', baseUnitCost: 52.00, uom: 'SQ' },
{ desc: 'GAF TimberTex Premium Ridge Cap', baseUnitCost: 85.00, uom: 'BD' },
{ desc: 'GAF Pro-Start Starter Strip', baseUnitCost: 68.00, uom: 'BD' },
{ desc: 'GAF FeltBuster Synthetic Underlayment', baseUnitCost: 110.00, uom: 'RL' },
{ desc: 'GAF WeatherWatch Ice & Water', baseUnitCost: 68.00, uom: 'RL' },
{ desc: 'Coil Nails 1 1/4"', baseUnitCost: 35.00, uom: 'BX' },
{ desc: 'Plastic Cap Nails', baseUnitCost: 28.50, uom: 'BX' },
{ desc: 'Geocel 2300 Clear Sealant', baseUnitCost: 8.95, uom: 'EA' }
]
},
{
id: 'iko_dynasty',
name: 'IKO Dynasty',
category: 'Roofing',
brand: 'IKO',
description: [
"Remove existing shingles down to deck.",
"Re-nail any loose wood.",
"Install IKO ArmourGard ice and water shield at eaves, rakes, and valleys.",
"Install IKO RoofGard-SB Synthetic underlayment.",
"Install IKO Dynasty Impact Resistant shingles."
],
materials: [
{ desc: 'IKO Dynasty', baseUnitCost: 54.00, uom: 'SQ' },
{ desc: 'IKO Hip & Ridge 12', baseUnitCost: 80.00, uom: 'BD' },
{ desc: 'IKO Leading Edge Plus Starter Strip', baseUnitCost: 71.00, uom: 'BD' },
{ desc: 'IKO RoofGard-SB Synthetic Underlayment', baseUnitCost: 118.00, uom: 'RL' },
{ desc: 'IKO ArmourGard Ice & Water', baseUnitCost: 67.00, uom: 'RL' },
{ desc: 'Coil Nails 1 1/4"', baseUnitCost: 35.00, uom: 'BX' },
{ desc: 'Plastic Cap Nails', baseUnitCost: 28.50, uom: 'BX' },
{ desc: 'Geocel 2300 Clear Sealant', baseUnitCost: 8.95, uom: 'EA' }
]
},
{
id: 'malarkey_vista_ar',
name: 'Malarkey Vista AR',
category: 'Roofing',
brand: 'Malarkey',
description: [
"Remove existing shingles down to deck.",
"Re-nail any loose wood.",
"Install Malarkey EverGuard ice and water shield at eaves and valleys.",
"Install Malarkey UDL 40 Synthetic underlayment.",
"Install Malarkey Vista AR Architectural Shingles."
],
materials: [
{ desc: 'Malarkey Vista AR', baseUnitCost: 46.00, uom: 'SQ' },
{ desc: 'Malarkey Ridglass Hip & Ridge', baseUnitCost: 83.00, uom: 'BD' },
{ desc: 'Malarkey Windsor Starter Strip', baseUnitCost: 73.00, uom: 'BD' },
{ desc: 'Malarkey UDL 40 Synthetic Underlayment', baseUnitCost: 122.00, uom: 'RL' },
{ desc: 'Malarkey EverGuard Ice & Water', baseUnitCost: 69.00, uom: 'RL' },
{ desc: 'Coil Nails 1 1/4"', baseUnitCost: 35.00, uom: 'BX' },
{ desc: 'Plastic Cap Nails', baseUnitCost: 28.50, uom: 'BX' },
{ desc: 'Geocel 2300 Clear Sealant', baseUnitCost: 8.95, uom: 'EA' }
]
},
{
id: 'owens_corning_duration',
name: 'Owens Corning Duration',
category: 'Roofing',
brand: 'Owens Corning',
description: [
"Remove existing shingles down to deck.",
"Inspect and re-nail decking.",
"Install Owens Corning WeatherLock G ice and water shield.",
"Install Owens Corning ProArmor Synthetic underlayment.",
"Install Owens Corning TruDefinition Duration Shingles."
],
materials: [
{ desc: 'OC TruDefinition Duration', baseUnitCost: 46.50, uom: 'SQ' },
{ desc: 'OC ProEdge Hip & Ridge', baseUnitCost: 88.00, uom: 'BD' },
{ desc: 'OC Starter Strip Plus', baseUnitCost: 78.00, uom: 'BD' },
{ desc: 'OC ProArmor Underlayment', baseUnitCost: 130.00, uom: 'RL' },
{ desc: 'OC WeatherLock G', baseUnitCost: 75.00, uom: 'RL' },
{ desc: 'Coil Nails 1 1/4"', baseUnitCost: 35.00, uom: 'BX' },
{ desc: 'Plastic Cap Nails', baseUnitCost: 28.50, uom: 'BX' },
{ desc: 'Geocel 2300 Clear Sealant', baseUnitCost: 8.95, uom: 'EA' }
]
},
{
id: 'mulehide_epdm',
name: 'Mulehide .060 EPDM System',
category: 'Roofing',
brand: 'Mulehide',
description: [
"Remove existing flat roof membrane down to deck.",
"Inspect and repair decking as needed.",
"Install Mulehide .060 EPDM membrane fully adhered system.",
"Install Mulehide bonding adhesive.",
"Install drip edge and termination bars at perimeter."
],
materials: [
{ desc: 'Mulehide .060 EPDM Membrane', baseUnitCost: 85.00, uom: 'SQ' },
{ desc: 'Mulehide Bonding Adhesive', baseUnitCost: 48.00, uom: 'EA' },
{ desc: 'Mulehide EPDM Lap Sealant', baseUnitCost: 12.00, uom: 'EA' },
{ desc: 'Mulehide Termination Bar', baseUnitCost: 4.50, uom: 'LF' },
{ desc: 'Coil Nails 1 1/4"', baseUnitCost: 35.00, uom: 'BX' },
{ desc: 'Geocel 2300 Clear Sealant', baseUnitCost: 8.95, uom: 'EA' }
]
},
];
// ---------------------------------------------------------------------------
// MOCK ESTIMATES
// ---------------------------------------------------------------------------
const MOCK_ESTIMATES_INITIAL = [
{
id: 'est_001',
clientName: 'Robert Chambers',
address: '2612 Dunwick Dr, Plano, TX 75023',
date: '2026-03-28',
status: 'Approved',
total: 18450.00,
templateId: 'certainteed_landmark',
templateName: 'CertainTeed Landmark AR',
estimatedBy: 'Marcus Webb',
roofArea: 28.4,
notes: 'Hail damage from March 15 storm. Insurance claim approved.',
},
{
id: 'est_002',
clientName: 'Sandra Kim',
address: '6613 Phoenix Pl, Plano, TX 75023',
date: '2026-03-25',
status: 'Sent',
total: 22100.00,
templateId: 'gaf_timberline_hdz',
templateName: 'GAF Timberline HDZ',
estimatedBy: 'Kenji Flores',
roofArea: 32.1,
notes: 'Full replacement. Customer wants upgraded ridge cap.',
},
{
id: 'est_003',
clientName: 'David Okonkwo',
address: '3913 Arizona Pl, Plano, TX 75023',
date: '2026-03-22',
status: 'Draft',
total: 14800.00,
templateId: 'atlas_glassmaster',
templateName: 'Atlas GlassMaster',
estimatedBy: 'Marcus Webb',
roofArea: 24.6,
notes: null,
},
{
id: 'est_004',
clientName: 'Patricia Hollis',
address: '3905 Sailmaker Ln, Plano, TX 75023',
date: '2026-03-18',
status: 'Approved',
total: 31200.00,
templateId: 'certainteed_presidential_shake',
templateName: 'CertainTeed Presidential Shake TL',
estimatedBy: 'Aisha Patel',
roofArea: 38.7,
notes: 'Premium upgrade. Customer requested Presidential Shake for curb appeal.',
},
{
id: 'est_005',
clientName: 'James Thornton',
address: '6909 Custer Rd, Plano, TX 75023',
date: '2026-03-14',
status: 'Rejected',
total: 28750.00,
templateId: 'gaf_timberline_ultra_hd',
templateName: 'GAF Timberline Ultra HD',
estimatedBy: 'Kenji Flores',
roofArea: 35.2,
notes: 'Customer rejected — going with competitor. Follow up in 30 days.',
},
{
id: 'est_006',
clientName: 'Maria Gonzalez',
address: '2608 Dunwick Dr, Plano, TX 75023',
date: '2026-03-10',
status: 'Approved',
total: 16900.00,
templateId: 'atlas_stormmaster_shake',
templateName: 'Atlas StormMaster Shake',
estimatedBy: 'Marcus Webb',
roofArea: 26.8,
notes: 'Impact resistant upgrade. HOA approved color: Weathered Wood.',
},
{
id: 'est_007',
clientName: 'Thomas Reeves',
address: '7224 Independence Pkwy, Plano, TX 75025',
date: '2026-03-05',
status: 'Sent',
total: 54200.00,
templateId: 'mulehide_epdm',
templateName: 'Mulehide .060 EPDM System',
estimatedBy: 'Aisha Patel',
roofArea: 62.0,
notes: 'Commercial flat roof. 3 HVAC curb flashings included.',
},
{
id: 'est_008',
clientName: 'Denise Watkins',
address: '3901 Sailmaker Ln, Plano, TX 75023',
date: '2026-02-28',
status: 'Expired',
total: 19350.00,
templateId: 'iko_dynasty',
templateName: 'IKO Dynasty',
estimatedBy: 'Kenji Flores',
roofArea: 29.1,
notes: 'Estimate expired — no response after 3 follow-ups. Reactivate on request.',
},
{
id: 'est_009',
clientName: 'Calvin Monroe',
address: '2100 Legacy Dr, Plano, TX 75023',
date: '2026-02-20',
status: 'Approved',
total: 11600.00,
templateId: 'owens_corning_duration',
templateName: 'Owens Corning Duration',
estimatedBy: 'Marcus Webb',
roofArea: 18.9,
notes: 'Apartment unit — common area roof section only.',
},
{
id: 'est_010',
clientName: 'Brenda Castillo',
address: '6601 Phoenix Pl, Plano, TX 75023',
date: '2026-02-14',
status: 'Draft',
total: 17250.00,
templateId: 'malarkey_vista_ar',
templateName: 'Malarkey Vista AR',
estimatedBy: 'Aisha Patel',
roofArea: 27.3,
notes: null,
},
];
// --- CONTEXT SETUP ---
const MockStoreContext = createContext();
@@ -3217,6 +3637,8 @@ export const MockStoreProvider = ({ children }) => {
const [vendorInvoices, setVendorInvoices] = useState(MOCK_VENDOR_INVOICES);
const [leads, setLeads] = useState([]);
const [dispatchLeads, setDispatchLeads] = useState(DISPATCH_LEADS_INITIAL);
const [templates, setTemplates] = useState(MASTER_TEMPLATES_INITIAL);
const [estimates, setEstimates] = useState(MOCK_ESTIMATES_INITIAL);
// Initialize properties once
useEffect(() => {
@@ -3317,6 +3739,31 @@ export const MockStoreProvider = ({ children }) => {
return newLead;
},
// Templates
templates,
addTemplate: (tpl) => {
const newTpl = { ...tpl, id: `tpl_${Date.now()}` };
setTemplates(prev => [...prev, newTpl]);
return newTpl;
},
updateTemplate: (id, data) => {
setTemplates(prev => prev.map(t => t.id === id ? { ...t, ...data } : t));
},
deleteTemplate: (id) => {
setTemplates(prev => prev.filter(t => t.id !== id));
},
// Estimates
estimates,
addEstimate: (est) => {
const newEst = { ...est, id: `est_${Date.now()}`, date: new Date().toISOString().slice(0, 10) };
setEstimates(prev => [newEst, ...prev]);
return newEst;
},
updateEstimate: (id, data) => {
setEstimates(prev => prev.map(e => e.id === id ? { ...e, ...data } : e));
},
// LynkDispatch
dispatchLeads,
assignDispatchLead: (leadId, repId) => {