Skip to content

Commit c8d0bb5

Browse files
committed
feat: add pre-populated Delivery submission
1 parent f2cf387 commit c8d0bb5

3 files changed

Lines changed: 169 additions & 27 deletions

File tree

src/components/DeliveryLead/DeliveryLeadSubmission.stories.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,29 @@ const exampleCustomerProjectPairs = [
2929
{ customer: { id: 3, name: 'Initech' }, project: { id: 301, name: 'API Integration' } },
3030
]
3131

32+
const prefilledSubmission = {
33+
customer: 1,
34+
project: 101,
35+
projectSummary: 'Existing project summary',
36+
projectUpdate: 'Latest updates go here',
37+
projectConcerns: 'Escalate dependency on vendor',
38+
commercialOpportunities: 'Upsell managed services',
39+
commercialRisks: 'Budget freeze risk',
40+
milestones: [
41+
{
42+
name: 'milestone 1',
43+
commentary: 'Kick-off completed',
44+
dueDate: '2026-01-01T12:00:00.000Z',
45+
rag: 'At Risk',
46+
},
47+
{
48+
name: 'milestone 2',
49+
commentary: 'Launch planned',
50+
rag: 'Complete',
51+
},
52+
],
53+
}
54+
3255
export const Default: Story = {
3356
args: {
3457
customerProjectPairs: exampleCustomerProjectPairs,
@@ -40,4 +63,11 @@ export const NoCustomers: Story = {
4063
args: {
4164
// customerProjectPairs: exampleCustomerProjectPairs,
4265
},
66+
}
67+
68+
export const PrefilledValues: Story = {
69+
args: {
70+
customerProjectPairs: exampleCustomerProjectPairs,
71+
initialData: prefilledSubmission,
72+
},
4373
}

src/components/DeliveryLead/DeliveryLeadSubmission.tsx

Lines changed: 132 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
'use client'
2-
import React, { useState } from 'react'
2+
import React, { useEffect, useState } from 'react'
33
import { Button } from '@/components/ui/button'
44
import { Label } from '@/components/ui/label'
55
import {
@@ -12,13 +12,89 @@ import {
1212
PlusCircle,
1313
Trash2,
1414
} from 'lucide-react'
15-
import { DeliveryLeadSubmissionData, Milestone } from './types'
15+
import { DeliveryLeadSubmissionData, DeliveryLeadSubmissionPrefill, Milestone } from './types'
1616
import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from '@/components/ui/select'
1717

18-
const initialMilestones: Milestone[] = [{ name: '', commentary: '', dueDate: '', rag: 'On-Track' }]
18+
const createEmptyMilestone = (): Milestone => ({
19+
name: '',
20+
commentary: '',
21+
dueDate: '',
22+
rag: 'On-Track',
23+
id: null,
24+
})
1925

2026
const ragOptions = ['On-Track', 'Off-Track', 'At Risk', 'Complete']
2127

28+
const formatDateInputValue = (value?: string | null) => {
29+
if (!value) return ''
30+
const parsed = new Date(value)
31+
if (!Number.isNaN(parsed.getTime())) {
32+
return parsed.toISOString().slice(0, 10)
33+
}
34+
const [dateOnly] = value.split('T')
35+
return dateOnly ?? ''
36+
}
37+
38+
const getNumericId = (value: number | { id?: number | string | null } | null | undefined): number | null => {
39+
if (typeof value === 'number') return value
40+
if (value && typeof value === 'object') {
41+
const numericId = value.id
42+
if (typeof numericId === 'number') return numericId
43+
if (typeof numericId === 'string') {
44+
const parsed = Number(numericId)
45+
return Number.isNaN(parsed) ? null : parsed
46+
}
47+
}
48+
return null
49+
}
50+
51+
const getCustomerOption = (customer: DeliveryLeadSubmissionPrefill['customer']) => {
52+
const id = getNumericId(customer)
53+
if (id === null) return null
54+
if (customer && typeof customer === 'object') {
55+
const name = (customer as { name?: string | null }).name ?? `Customer ${id}`
56+
return { id, name }
57+
}
58+
return { id, name: `Customer ${id}` }
59+
}
60+
61+
const getProjectOption = (
62+
project: DeliveryLeadSubmissionPrefill['project'],
63+
fallbackCustomerId: number | null,
64+
) => {
65+
const id = getNumericId(project)
66+
if (id === null) return null
67+
68+
if (project && typeof project === 'object') {
69+
const name =
70+
(project as { name?: string | null; projectName?: string | null }).name ??
71+
(project as { projectName?: string | null }).projectName ??
72+
`Project ${id}`
73+
const customerId =
74+
getNumericId((project as { customer?: number | { id?: number | string | null } }).customer) ??
75+
fallbackCustomerId
76+
return { id, name, customerId }
77+
}
78+
79+
return { id, name: `Project ${id}`, customerId: fallbackCustomerId }
80+
}
81+
82+
const buildMilestonesFromPrefill = (
83+
prefilled: DeliveryLeadSubmissionPrefill['milestones'],
84+
): Milestone[] => {
85+
if (!prefilled || prefilled.length === 0) return [createEmptyMilestone()]
86+
87+
return prefilled
88+
.filter(Boolean)
89+
.map((milestone) => ({
90+
name: milestone?.name ?? '',
91+
commentary: milestone?.commentary ?? '',
92+
dueDate: formatDateInputValue(milestone?.dueDate ?? ''),
93+
rag: milestone?.rag ?? 'On-Track',
94+
id: milestone?.id ?? null,
95+
}))
96+
}
97+
2298
const tabConfig = [
2399
{ name: 'Project Summary', icon: FileText },
24100
{ name: 'Milestones', icon: ListChecks },
@@ -38,28 +114,52 @@ export interface DeliveryLeadSubmissionProps {
38114
formData: DeliveryLeadSubmissionData,
39115
) => Promise<{ success: boolean; message: string }>
40116
customerProjectPairs?: CustomerProjectPair[]
117+
initialData?: DeliveryLeadSubmissionPrefill
41118
}
42119

43-
export function DeliveryLeadSubmissionComponent({ onSubmit, customerProjectPairs }: DeliveryLeadSubmissionProps) {
120+
export function DeliveryLeadSubmissionComponent({ onSubmit, customerProjectPairs, initialData }: DeliveryLeadSubmissionProps) {
44121
const [currentTab, setCurrentTab] = useState(tabConfig[0].name)
45-
const [clientId, setClientId] = useState<number | null>(null)
46-
const [projectId, setProjectId] = useState<number | null>(null)
47-
const [deliveryLead, setDeliveryLead] = useState('')
48-
const [projectSummary, setProjectSummary] = useState('')
49-
const [milestones, setMilestones] = useState(initialMilestones)
50-
const [projectUpdate, setProjectUpdate] = useState('')
51-
const [projectConcerns, setProjectConcerns] = useState('')
52-
const [commercialOpportunities, setCommercialOpportunities] = useState('')
53-
const [commercialRisks, setCommercialRisks] = useState('')
122+
const [clientId, setClientId] = useState<number | null>(() => getNumericId(initialData?.customer))
123+
const [projectId, setProjectId] = useState<number | null>(() => getNumericId(initialData?.project))
124+
const [projectSummary, setProjectSummary] = useState(initialData?.projectSummary ?? '')
125+
const [milestones, setMilestones] = useState<Milestone[]>(() => buildMilestonesFromPrefill(initialData?.milestones))
126+
const [projectUpdate, setProjectUpdate] = useState(initialData?.projectUpdate ?? '')
127+
const [projectConcerns, setProjectConcerns] = useState(initialData?.projectConcerns ?? '')
128+
const [commercialOpportunities, setCommercialOpportunities] = useState(initialData?.commercialOpportunities ?? '')
129+
const [commercialRisks, setCommercialRisks] = useState(initialData?.commercialRisks ?? '')
54130

55-
// Derive unique customers
56-
const customers = Array.from(
57-
new Map((customerProjectPairs ?? []).map(cp => [cp.customer.id, cp.customer])).values()
131+
useEffect(() => {
132+
setClientId(getNumericId(initialData?.customer))
133+
setProjectId(getNumericId(initialData?.project))
134+
setProjectSummary(initialData?.projectSummary ?? '')
135+
setProjectUpdate(initialData?.projectUpdate ?? '')
136+
setProjectConcerns(initialData?.projectConcerns ?? '')
137+
setCommercialOpportunities(initialData?.commercialOpportunities ?? '')
138+
setCommercialRisks(initialData?.commercialRisks ?? '')
139+
setMilestones(buildMilestonesFromPrefill(initialData?.milestones))
140+
}, [initialData])
141+
142+
const prefilledCustomer = getCustomerOption(initialData?.customer)
143+
const customersFromPairs = Array.from(
144+
new Map((customerProjectPairs ?? []).map((cp) => [cp.customer.id, cp.customer])).values(),
58145
)
59-
// Filter projects by selected customer
60-
const filteredProjects = (customerProjectPairs ?? [])
61-
.filter(cp => cp.customer.id === clientId)
62-
.map(cp => cp.project)
146+
const customers =
147+
prefilledCustomer && !customersFromPairs.some((customer) => customer.id === prefilledCustomer.id)
148+
? [...customersFromPairs, prefilledCustomer]
149+
: customersFromPairs
150+
151+
const prefilledProject = getProjectOption(initialData?.project, getNumericId(initialData?.customer))
152+
const projectsForCustomer = (customerProjectPairs ?? [])
153+
.filter((cp) => cp.customer.id === clientId)
154+
.map((cp) => cp.project)
155+
156+
const filteredProjects =
157+
clientId &&
158+
prefilledProject &&
159+
prefilledProject.customerId === clientId &&
160+
!projectsForCustomer.some((project) => project.id === prefilledProject.id)
161+
? [...projectsForCustomer, { id: prefilledProject.id, name: prefilledProject.name }]
162+
: projectsForCustomer
63163

64164
const handleMilestoneChange = (idx: number, field: string, value: string) => {
65165
setMilestones((prev) => {
@@ -73,7 +173,7 @@ export function DeliveryLeadSubmissionComponent({ onSubmit, customerProjectPairs
73173
}
74174

75175
const addMilestone = () => {
76-
setMilestones((prev) => [...prev, { name: '', commentary: '', dueDate: '', rag: 'On-Track' }])
176+
setMilestones((prev) => [...prev, createEmptyMilestone()])
77177
}
78178

79179
const removeMilestone = (idx: number) => {
@@ -82,11 +182,21 @@ export function DeliveryLeadSubmissionComponent({ onSubmit, customerProjectPairs
82182

83183
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
84184
e.preventDefault()
185+
186+
const normalizedMilestones =
187+
milestones.length > 0
188+
? milestones.map((milestone) => ({
189+
...milestone,
190+
commentary: milestone.commentary || null,
191+
dueDate: milestone.dueDate || null,
192+
}))
193+
: null
194+
85195
const data: DeliveryLeadSubmissionData = {
86196
customer: clientId as number, // must be number
87197
project: projectId as number, // must be number
88198
projectSummary,
89-
milestones: milestones.length > 0 ? milestones : null,
199+
milestones: normalizedMilestones,
90200
projectUpdate,
91201
projectConcerns: projectConcerns || null,
92202
commercialOpportunities: commercialOpportunities || null,
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
import { DeliveryReport as PayloadDeliveryLeadSubmission } from '../../payload-types'
22

33
export interface DeliveryLeadSubmissionProps {
4-
onSubmit: (
4+
onSubmit?: (
55
formData: Omit<PayloadDeliveryLeadSubmission, 'id' | 'user' | 'updatedAt' | 'createdAt'>,
66
) => Promise<{ success: boolean; message: string }>
77
isSubmitting?: boolean
8+
initialData?: DeliveryLeadSubmissionPrefill
89
}
910

10-
// Re-export the payload type for convenience
11+
export type DeliveryLeadSubmissionPrefill = Partial<PayloadDeliveryLeadSubmission>
12+
1113
export type DeliveryLeadSubmissionData = Omit<
1214
PayloadDeliveryLeadSubmission,
1315
'id' | 'user' | 'updatedAt' | 'createdAt'
1416
>
1517

16-
// Keep the Milestone type for backward compatibility
1718
export interface Milestone {
1819
name: string
19-
commentary?: string
20-
dueDate?: string
20+
commentary?: string | null
21+
dueDate?: string | null
2122
rag: 'On-Track' | 'Off-Track' | 'At Risk' | 'Complete'
23+
id?: string | null
2224
}

0 commit comments

Comments
 (0)