File size: 13,187 Bytes
f4158d4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
'use client'
import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from 'react'
import type { Vendor } from '@/lib/vendors'
import { vendorData } from '@/lib/vendors'

// ── Types ──

export interface ShortlistEntry {
  vendorId: string
  notes: string
  addedAt: string
}

export type ContractStatus = 'draft' | 'pending' | 'active' | 'amended' | 'disputed' | 'completed'

export interface Contract {
  id: string
  vendorId: string
  clientName: string
  type: string
  status: ContractStatus
  amount: number
  date: string
  version: number
  sections: { title: string; content: string; items?: string[] }[]
  deliverables: { id: string; desc: string; due: string; qty: number; criteria: string; completed: boolean }[]
}

export interface AuditEntry {
  time: string
  action: string
  actor: string
  detail: string
  type: 'create'|'send'|'view'|'amend'|'update'|'sign'|'decline'|'system'
}

export interface PlannerTask {
  id: string
  task: string
  status: 'completed'|'active'|'upcoming'
  date: string
  category: string
}

export interface BudgetItem {
  category: string
  budgeted: number
  spent: number
}

// ── Context ──

interface AppState {
  shortlist: ShortlistEntry[]
  contracts: Contract[]
  plannerTasks: PlannerTask[]
  budget: BudgetItem[]
  guestCount: number
  // Auth mock
  userRole: 'client'|'vendor'|'admin'|null
  userName: string|null

  // Actions
  toggleShortlist: (vendorId: string) => void
  removeFromShortlist: (vendorId: string) => void
  updateShortlistNotes: (vendorId: string, notes: string) => void
  getShortlistVendors: () => Vendor[]
  isShortlisted: (vendorId: string) => boolean

  updateContractStatus: (id: string, status: ContractStatus, auditAction: string) => void
  getContractById: (id: string) => Contract|undefined
  getContractAuditLog: (id: string) => AuditEntry[]

  addTask: (task: string, category: string) => void
  toggleTask: (id: string) => void
  updateGuestCount: (n: number) => void
  updateBudgetItem: (category: string, spent: number) => void

  setRole: (role: 'client'|'vendor'|'admin'|null) => void
  setUserName: (name: string) => void
}

const AppContext = createContext<AppState|null>(null)

// ── Default data ──

const defaultContracts: Contract[] = [
  {
    id:'CTR-001', vendorId:'v1', clientName:'Amaya & Ruwan', type:'Full Day Venue',
    status:'active', amount:550000, date:'2026-11-01', version:1,
    sections:[
      { title:'1. Venue Access', content:'Client has access to the Grand Atrium, garden, and bridal suite from 10:00 AM to 8:00 PM on the wedding date. Setup crew may enter at 8:00 AM.' },
      { title:'2. Guest Count', content:'Maximum capacity is 350 guests. Final headcount must be confirmed 14 days before the event.' },
      { title:'3. Payment Terms', content:'Total: Rs. 550,000. Deposit of Rs. 200,000 paid. Remaining Rs. 350,000 due 7 days before the event.' },
    ],
    deliverables: [
      { id:'d1', desc:'Grand Atrium access', due:'2027-01-15', qty:1, criteria:'Clean, setup complete by 10:00 AM', completed:false },
      { id:'d2', desc:'Bridal suite access', due:'2027-01-15', qty:1, criteria:'Private room with AC, mirrors, refreshments', completed:false },
    ],
  },
  {
    id:'CTR-002', vendorId:'v2', clientName:'Amaya & Ruwan', type:'Full Day Photography',
    status:'pending', amount:150000, date:'2026-11-08', version:2,
    sections:[
      { title:'1. Scope', content:'Full-day wedding photography coverage. Two photographers. Minimum 400 edited high-resolution images delivered within 30 days post-event.' },
      { title:'2. Deliverables', items:['400+ edited images','Online gallery (12 months)','USB drive','30-page premium layflat album'] },
      { title:'3. Payment', content:'Total: Rs. 150,000. Rs. 50,000 deposit due on signing. Rs. 100,000 balance due 7 days before event.' },
    ],
    deliverables: [
      { id:'d3', desc:'Edited high-resolution images', due:'2027-02-15', qty:400, criteria:'Color-corrected, professionally edited', completed:false },
      { id:'d4', desc:'Online gallery', due:'2027-02-20', qty:1, criteria:'Password protected, accessible 12 months', completed:false },
      { id:'d5', desc:'Premium layflat album', due:'2027-03-15', qty:1, criteria:'30 pages, premium paper, leather cover', completed:false },
    ],
  },
  {
    id:'CTR-003', vendorId:'v3', clientName:'Amaya & Ruwan', type:'Classic Florals',
    status:'draft', amount:120000, date:'2026-12-01', version:1,
    sections:[
      { title:'1. Arrangements', content:'Bridal bouquet, 3 bridesmaid bouquets, ceremony arch, 15 table centerpieces, aisle petal decor.' },
      { title:'2. Payment', content:'Total: Rs. 120,000. Rs. 40,000 deposit on signing. Balance due 14 days before event.' },
    ],
    deliverables:[
      { id:'d6', desc:'Bridal bouquet', due:'2027-01-15', qty:1, criteria:'Premium seasonal blooms, hand-tied', completed:false },
      { id:'d7', desc:'Ceremony arch', due:'2027-01-15', qty:1, criteria:'Full floral coverage, stable structure', completed:false },
    ],
  },
]

const defaultTasks: PlannerTask[] = [
  { id:'t1', task:'Book venue', status:'completed', date:'2026-11-01', category:'Venue' },
  { id:'t2', task:'Finalize guest list', status:'completed', date:'2026-11-05', category:'Planning' },
  { id:'t3', task:'Sign photographer contract', status:'active', date:'2026-11-15', category:'Contract' },
  { id:'t4', task:'Choose floral arrangements', status:'active', date:'2026-11-20', category:'DΓ©cor' },
  { id:'t5', task:'Book caterer tasting', status:'upcoming', date:'2026-12-01', category:'Catering' },
  { id:'t6', task:'Order wedding invitations', status:'upcoming', date:'2026-12-10', category:'Stationery' },
  { id:'t7', task:'Final dress fitting', status:'upcoming', date:'2026-12-15', category:'Attire' },
  { id:'t8', task:'Confirm DJ playlist', status:'upcoming', date:'2026-12-20', category:'Music' },
]

const defaultBudget: BudgetItem[] = [
  { category:'Venue', budgeted:550000, spent:400000 },
  { category:'Photography', budgeted:150000, spent:50000 },
  { category:'Catering', budgeted:250000, spent:25000 },
  { category:'Florals', budgeted:120000, spent:0 },
  { category:'Music & DJ', budgeted:75000, spent:75000 },
  { category:'Attire', budgeted:120000, spent:45000 },
]

// ── Audit log generator ──

function generateAudit(id: string): AuditEntry[] {
  const contract = defaultContracts.find(c=>c.id===id)
  if (!contract) return []
  const base: AuditEntry[] = [
    { time:'2026-11-08 14:32', action:'Contract created', actor:'Evermore', detail:'Initial contract generated', type:'create' },
    { time:'2026-11-08 14:33', action:'Sent to client', actor:'Evermore', detail:'Sent via platform', type:'send' },
    { time:'2026-11-08 15:10', action:'Viewed by client', actor:'You', detail:'Viewed on web app', type:'view' },
  ]
  if (contract.version > 1) {
    base.push({ time:'2026-11-09 09:45', action:'Amendment requested', actor:'You', detail:'Added 100 images to deliverable', type:'amend' })
    base.push({ time:'2026-11-09 11:20', action:'Vendor responded', actor:'Evermore', detail:'Accepted amendment. Version updated to v2', type:'update' })
    base.push({ time:'2026-11-09 11:20', action:'Version updated', actor:'System', detail:'Previous version archived', type:'system' })
  }
  if (contract.status === 'active') {
    base.push({ time:'2026-11-15 10:00', action:'Contract signed', actor:'You', detail:'E-signed. IP: 192.168.1.1, UA: Chrome/120', type:'sign' })
    base.push({ time:'2026-11-15 10:05', action:'Countersigned', actor:'Evermore', detail:'Vendor signed. Contract now active', type:'sign' })
  }
  return base
}

const auditCache: Record<string, AuditEntry[]> = {}
defaultContracts.forEach(c => { auditCache[c.id] = generateAudit(c.id) })

// ── Provider ──

function loadFromStorage<T>(key: string, fallback: T): T {
  if (typeof window === 'undefined') return fallback
  try {
    const stored = localStorage.getItem(key)
    return stored ? JSON.parse(stored) : fallback
  } catch { return fallback }
}

export function AppProvider({ children }: { children: ReactNode }) {
  const [shortlist, setShortlist] = useState<ShortlistEntry[]>([])
  const [contracts, setContracts] = useState<Contract[]>(defaultContracts)
  const [plannerTasks, setPlannerTasks] = useState<PlannerTask[]>(defaultTasks)
  const [budget, setBudget] = useState<BudgetItem[]>(defaultBudget)
  const [guestCount, setGuestCount] = useState(180)
  const [userRole, setUserRole] = useState<'client'|'vendor'|'admin'|null>(null)
  const [userName, setUserNameState] = useState<string|null>(null)

  // Load from localStorage on mount
  useEffect(() => {
    setShortlist(loadFromStorage<ShortlistEntry[]>('evermore_shortlist', []))
    setPlannerTasks(loadFromStorage<PlannerTask[]>('evermore_tasks', defaultTasks))
    setBudget(loadFromStorage<BudgetItem[]>('evermore_budget', defaultBudget))
    setGuestCount(loadFromStorage<number>('evermore_guests', 180))
    setUserRole(loadFromStorage<'client'|'vendor'|'admin'|null>('evermore_role', null))
    setUserNameState(loadFromStorage<string|null>('evermore_name', null))
  }, [])

  // Persist
  useEffect(() => { localStorage.setItem('evermore_shortlist', JSON.stringify(shortlist)) }, [shortlist])
  useEffect(() => { localStorage.setItem('evermore_tasks', JSON.stringify(plannerTasks)) }, [plannerTasks])
  useEffect(() => { localStorage.setItem('evermore_budget', JSON.stringify(budget)) }, [budget])
  useEffect(() => { localStorage.setItem('evermore_guests', JSON.stringify(guestCount)) }, [guestCount])
  useEffect(() => { if (userRole !== null) localStorage.setItem('evermore_role', userRole); else localStorage.removeItem('evermore_role') }, [userRole])
  useEffect(() => { if (userName) localStorage.setItem('evermore_name', userName); else localStorage.removeItem('evermore_name') }, [userName])

  const toggleShortlist = useCallback((vendorId: string) => {
    setShortlist(prev => {
      if (prev.find(s => s.vendorId === vendorId)) {
        return prev.filter(s => s.vendorId !== vendorId)
      }
      return [...prev, { vendorId, notes: '', addedAt: new Date().toISOString() }]
    })
  }, [])

  const removeFromShortlist = useCallback((vendorId: string) => {
    setShortlist(prev => prev.filter(s => s.vendorId !== vendorId))
  }, [])

  const updateShortlistNotes = useCallback((vendorId: string, notes: string) => {
    setShortlist(prev => prev.map(s => s.vendorId === vendorId ? { ...s, notes } : s))
  }, [])

  const getShortlistVendors = useCallback(() => {
    return shortlist.map(s => vendorData.find(v => v.id === s.vendorId)).filter(Boolean) as Vendor[]
  }, [shortlist])

  const isShortlisted = useCallback((vendorId: string) => {
    return shortlist.some(s => s.vendorId === vendorId)
  }, [shortlist])

  const updateContractStatus = useCallback((id: string, status: ContractStatus, auditAction: string) => {
    setContracts(prev => prev.map(c => c.id === id ? { ...c, status } : c))
    if (!auditCache[id]) auditCache[id] = []
    auditCache[id].push({
      time: new Date().toLocaleString('en-US', { month:'short', day:'numeric', year:'numeric', hour:'2-digit', minute:'2-digit' }),
      action: auditAction,
      actor: 'You',
      detail: `Contract status changed to: ${status}`,
      type: status === 'active' ? 'sign' : status === 'disputed' ? 'amend' : 'update',
    })
  }, [])

  const getContractById = useCallback((id: string) => contracts.find(c => c.id === id), [contracts])
  const getContractAuditLog = useCallback((id: string) => auditCache[id] || [], [])

  const addTask = useCallback((task: string, category: string) => {
    setPlannerTasks(prev => [...prev, {
      id: 't' + Date.now().toString(36),
      task, category, status: 'upcoming',
      date: new Date().toLocaleDateString('en-US', { month:'short', day:'numeric' }),
    }])
  }, [])

  const toggleTask = useCallback((id: string) => {
    setPlannerTasks(prev => prev.map(t => t.id === id ? {
      ...t,
      status: t.status === 'completed' ? 'active' : 'completed',
    } : t))
  }, [])

  const updateGuestCount = useCallback((n: number) => setGuestCount(n), [])
  const updateBudgetItem = useCallback((category: string, spent: number) => {
    setBudget(prev => prev.map(b => b.category === category ? { ...b, spent } : b))
  }, [])

  const setRole = useCallback((role: 'client'|'vendor'|'admin'|null) => setUserRole(role), [])
  const setUserName = useCallback((name: string) => setUserNameState(name), [])

  return (
    <AppContext.Provider value={{
      shortlist, contracts, plannerTasks, budget, guestCount, userRole, userName,
      toggleShortlist, removeFromShortlist, updateShortlistNotes, getShortlistVendors, isShortlisted,
      updateContractStatus, getContractById, getContractAuditLog,
      addTask, toggleTask, updateGuestCount, updateBudgetItem,
      setRole, setUserName,
    }}>
      {children}
    </AppContext.Provider>
  )
}

export function useApp(): AppState {
  const ctx = useContext(AppContext)
  if (!ctx) throw new Error('useApp must be used inside AppProvider')
  return ctx
}