diff --git a/src/app/(protected)/workorders/_components/wizard/mini-calendar.tsx b/src/app/(protected)/workorders/_components/wizard/mini-calendar.tsx new file mode 100644 index 00000000..4c4132fd --- /dev/null +++ b/src/app/(protected)/workorders/_components/wizard/mini-calendar.tsx @@ -0,0 +1,115 @@ +import { useState } from "react"; +import { ChevronLeft, ChevronRight } from "lucide-react"; +import { MONTH_NAMES, pad2, todayIso } from "@/domain/work-orders/utils/wizard-date-utils"; + +const WEEKDAY_HEADER = ["S", "M", "T", "W", "T", "F", "S"]; + +type MiniCalendarProps = { + value: string; + onChange: (iso: string) => void; +}; + +export function MiniCalendar({ value, onChange }: MiniCalendarProps) { + const init = /^\d{4}-\d{2}-\d{2}$/.test(value) ? value : todayIso(); + const [iy, im] = init.split("-").map(Number); + const [viewY, setViewY] = useState(iy); + const [viewM, setViewM] = useState(im); + + const firstWeekday = new Date(viewY, viewM - 1, 1).getDay(); + const daysInMonth = new Date(viewY, viewM, 0).getDate(); + const cells: (number | null)[] = [ + ...Array(firstWeekday).fill(null), + ...Array.from({ length: daysInMonth }, (_, i) => i + 1), + ]; + + const step = (delta: number) => { + let m = viewM + delta; + let y = viewY; + if (m < 1) { + m = 12; + y -= 1; + } + if (m > 12) { + m = 1; + y += 1; + } + setViewM(m); + setViewY(y); + }; + + const today = todayIso(); + + return ( +
+
+ + + {MONTH_NAMES[viewM - 1]} {viewY} + + +
+
+ {WEEKDAY_HEADER.map((d, i) => ( + + {d} + + ))} +
+
+ {cells.map((day, i) => { + if (day === null) { + return ; + } + const iso = `${viewY}-${pad2(viewM)}-${pad2(day)}`; + const selected = iso === value; + const isToday = iso === today; + return ( + + ); + })} +
+
+ ); +} diff --git a/src/app/(protected)/workorders/_components/wizard/new-wo-wizard.tsx b/src/app/(protected)/workorders/_components/wizard/new-wo-wizard.tsx new file mode 100644 index 00000000..97e1ec64 --- /dev/null +++ b/src/app/(protected)/workorders/_components/wizard/new-wo-wizard.tsx @@ -0,0 +1,222 @@ +import { useEffect, useMemo, useState } from "react"; +import { Dialog } from "@mui/material"; +import { dialogBlurBackdropSlotProps } from "@/components/ui/dialog-blur-backdrop"; +import { useDropdownOptionsByCategory } from "@/domain/settings/dropdown-options/use-cases/use-dropdown-options-by-category"; +import { mapWizardDraftToFormValues } from "@/domain/work-orders/mappers/wizard-draft-mapper"; +import { isWoWizardMockEnabled } from "@/domain/work-orders/mocks/is-wo-wizard-mock-enabled"; +import { + getMockWoWizardLocationDetail, + MOCK_WO_WIZARD_LOCATIONS, + MOCK_WO_WIZARD_SERVICES, + MOCK_WO_WIZARD_USERS, + MOCK_WO_WIZARD_VENDORS, +} from "@/domain/work-orders/mocks/wo-wizard-mock-data"; +import type { WorkOrderUser } from "@/domain/work-orders/types/work-order"; +import { + EMPTY_WIZARD_DRAFT, + WIZARD_STEPS, + type WorkOrderWizardDraft, +} from "@/domain/work-orders/types/work-order-wizard"; +import { useLocationDetail } from "@/domain/locations/use-cases/use-location-detail"; +import { + useWorkOrderLocations, + useWorkOrderVendors, +} from "@/domain/work-orders/use-cases/use-work-order-detail"; +import { useCreateWorkOrderFromWizard } from "@/domain/work-orders/use-cases/use-create-work-order-from-wizard"; +import { WizardFooter } from "@/app/(protected)/workorders/_components/wizard/wizard-footer"; +import { WizardSidebar } from "@/app/(protected)/workorders/_components/wizard/wizard-sidebar"; +import { WizardStepLocationService } from "@/app/(protected)/workorders/_components/wizard/wizard-step-location-service"; +import { WizardStepTypeSchedule } from "@/app/(protected)/workorders/_components/wizard/wizard-step-type-schedule"; +import { WizardStepVendorTime } from "@/app/(protected)/workorders/_components/wizard/wizard-step-vendor-time"; + +type NewWoWizardProps = { + open: boolean; + onOpenChange: (open: boolean) => void; + users: WorkOrderUser[]; +}; + +export function NewWoWizard({ open, onOpenChange, users }: NewWoWizardProps) { + const mockFallback = isWoWizardMockEnabled(); + const [step, setStep] = useState(1); + const [draft, setDraft] = useState({ + ...EMPTY_WIZARD_DRAFT, + status: "Incomplete", + }); + const [pocAutoFilledFor, setPocAutoFilledFor] = useState(""); + + const { data: apiLocations = [] } = useWorkOrderLocations(); + const { data: apiProblems = [] } = useDropdownOptionsByCategory("Problem"); + const isMockLocation = draft.locationId.startsWith("mock-"); + const { data: apiLocationDetail } = useLocationDetail( + isMockLocation ? undefined : draft.locationId || undefined, + ); + const mockLocationDetail = isMockLocation + ? getMockWoWizardLocationDetail(draft.locationId) + : undefined; + const locationDetail = mockLocationDetail ?? apiLocationDetail; + const { data: apiVendors = [] } = useWorkOrderVendors( + undefined, + isMockLocation ? undefined : locationDetail?.zipCode, + ); + const createMutation = useCreateWorkOrderFromWizard(() => onOpenChange(false)); + + const wizardUsers = useMemo(() => { + if (users.length > 0) { + return users; + } + return mockFallback ? MOCK_WO_WIZARD_USERS : []; + }, [mockFallback, users]); + const locations = + apiLocations.length > 0 ? apiLocations : mockFallback ? MOCK_WO_WIZARD_LOCATIONS : []; + const problems = + apiProblems.length > 0 ? apiProblems : mockFallback ? MOCK_WO_WIZARD_SERVICES : []; + const vendors = apiVendors.length > 0 ? apiVendors : mockFallback ? MOCK_WO_WIZARD_VENDORS : []; + + useEffect(() => { + if (open) { + setStep(1); + setDraft({ ...EMPTY_WIZARD_DRAFT, status: "Incomplete" }); + setPocAutoFilledFor(""); + } + }, [open]); + + useEffect(() => { + if (!draft.locationId || !locationDetail) { + return; + } + if (pocAutoFilledFor === draft.locationId) { + return; + } + setDraft((current) => ({ + ...current, + pocName: locationDetail.contact ?? current.pocName, + pocPhone: locationDetail.phone ?? current.pocPhone, + })); + setPocAutoFilledFor(draft.locationId); + }, [draft.locationId, locationDetail, pocAutoFilledFor]); + + const setField = ( + key: K, + value: WorkOrderWizardDraft[K], + ) => { + setDraft((current) => ({ ...current, [key]: value })); + }; + + const patchDraft = (patch: Partial) => { + setDraft((current) => ({ ...current, ...patch })); + if (patch.locationId !== undefined && patch.locationId !== pocAutoFilledFor) { + setPocAutoFilledFor(""); + } + }; + + const handleCreate = () => { + createMutation.mutate(mapWizardDraftToFormValues(draft)); + }; + + return ( + onOpenChange(false)} + maxWidth={false} + slotProps={{ + ...dialogBlurBackdropSlotProps, + paper: { + className: "flex flex-col overflow-hidden p-0", + style: { + borderRadius: 12, + width: 768, + maxWidth: 768, + height: 680, + maxHeight: 680, + }, + }, + }} + > + Guided setup for creating a new work order + +
+ + +
+
+
+ Step {step} of {WIZARD_STEPS.length} +
+

+ {WIZARD_STEPS[step - 1].title} +

+

+ {WIZARD_STEPS[step - 1].subtitle} +

+
+ + {step === 1 && ( + + )} + + {step === 2 && ( + ({ id: p.id, value: p.value }))} + onUpdate={setField} + onPatch={patchDraft} + /> + )} + + {step === 3 && ( + + )} +
+
+ + onOpenChange(false)} + onBack={() => setStep((s) => s - 1)} + onContinue={() => setStep((s) => s + 1)} + onCreate={handleCreate} + isCreating={createMutation.isPending} + /> +
+ ); +} diff --git a/src/app/(protected)/workorders/_components/wizard/week-picker.tsx b/src/app/(protected)/workorders/_components/wizard/week-picker.tsx new file mode 100644 index 00000000..233939b0 --- /dev/null +++ b/src/app/(protected)/workorders/_components/wizard/week-picker.tsx @@ -0,0 +1,108 @@ +import { useState } from "react"; +import { ChevronLeft, ChevronRight } from "lucide-react"; +import { + MONTH_NAMES, + mondayOf, + todayIso, + weekRangeLabel, + weeksOfMonth, +} from "@/domain/work-orders/utils/wizard-date-utils"; + +type WeekPickerProps = { + selected: string; + onPick: (mondayIso: string) => void; +}; + +export function WeekPicker({ selected, onPick }: WeekPickerProps) { + const init = /^\d{4}-\d{2}-\d{2}$/.test(selected) ? selected : todayIso(); + const [iy, im] = init.split("-").map(Number); + const [viewY, setViewY] = useState(iy); + const [viewM, setViewM] = useState(im); + + const step = (delta: number) => { + let m = viewM + delta; + let y = viewY; + if (m < 1) { + m = 12; + y -= 1; + } + if (m > 12) { + m = 1; + y += 1; + } + setViewM(m); + setViewY(y); + }; + + const weeks = weeksOfMonth(viewY, viewM); + const thisMonday = mondayOf(todayIso()); + + return ( +
+
+ + + {MONTH_NAMES[viewM - 1]} {viewY} + + +
+
+ {weeks.map((mon) => { + const isSel = Boolean(selected) && selected === mon; + const isThis = mon === thisMonday; + return ( + + ); + })} +
+
+ ); +} diff --git a/src/app/(protected)/workorders/_components/wizard/wizard-date-field.tsx b/src/app/(protected)/workorders/_components/wizard/wizard-date-field.tsx new file mode 100644 index 00000000..ad8396d0 --- /dev/null +++ b/src/app/(protected)/workorders/_components/wizard/wizard-date-field.tsx @@ -0,0 +1,145 @@ +import { useState } from "react"; +import { CalendarDays, CalendarIcon, ChevronDown } from "lucide-react"; +import { Popover } from "@mui/material"; +import { MiniCalendar } from "@/app/(protected)/workorders/_components/wizard/mini-calendar"; +import { WeekPicker } from "@/app/(protected)/workorders/_components/wizard/week-picker"; +import { fmtDate, fmtWeekRange } from "@/domain/work-orders/utils/wizard-date-utils"; +import { MenuAnchorButton } from "@/components/ui/menu-anchor-button"; +import { useMenuAnchor } from "@/hooks/use-menu-anchor"; + +type WizardDateFieldProps = { + value: string; + placeholder: string; + onChange: (iso: string) => void; + allowWeekMode?: boolean; + targetWeek?: string; + onChangeWeek?: (weekStart: string) => void; +}; + +export function WizardDateField({ + value, + placeholder, + onChange, + allowWeekMode, + targetWeek = "", + onChangeWeek, +}: WizardDateFieldProps) { + const { anchorEl, open, setAnchor, close } = useMenuAnchor(); + const [cellMode, setCellMode] = useState<"week" | "date">( + allowWeekMode && !value && targetWeek ? "week" : "date", + ); + const showWeek = Boolean(allowWeekMode && !value && targetWeek); + + return ( + <> + { + (e.currentTarget as HTMLButtonElement).style.boxShadow = "0 0 0 2px var(--color-primary)"; + }} + onBlur={(e) => { + (e.currentTarget as HTMLButtonElement).style.boxShadow = "none"; + }} + aria-haspopup="dialog" + > + {showWeek ? ( + + ) : ( + + )} + + {showWeek ? fmtWeekRange(targetWeek) : value ? fmtDate(value) : placeholder} + + + + + e.stopPropagation(), + }, + }} + > + {allowWeekMode && ( +
+ {(["week", "date"] as const).map((m) => { + const activeMode = cellMode === m; + return ( + + ); + })} +
+ )} + + {allowWeekMode && cellMode === "week" ? ( + { + onChange(""); + onChangeWeek?.(mon); + close(); + }} + /> + ) : ( + { + onChange(iso); + close(); + }} + /> + )} +
+ + ); +} diff --git a/src/app/(protected)/workorders/_components/wizard/wizard-field-select.tsx b/src/app/(protected)/workorders/_components/wizard/wizard-field-select.tsx new file mode 100644 index 00000000..d936e85d --- /dev/null +++ b/src/app/(protected)/workorders/_components/wizard/wizard-field-select.tsx @@ -0,0 +1,167 @@ +import { useState, type ReactNode } from "react"; +import { Check, ChevronDown } from "lucide-react"; +import { Popover } from "@mui/material"; +import { MenuAnchorButton } from "@/components/ui/menu-anchor-button"; +import { useMenuAnchor } from "@/hooks/use-menu-anchor"; + +export interface WizardMenuOption { + key: string; + label: string; + left?: ReactNode; +} + +type WizardFieldSelectProps = { + value: string; + placeholder: string; + icon?: ReactNode; + options: WizardMenuOption[]; + onPick: (key: string) => void; + search?: boolean; + searchPlaceholder?: string; + disabled?: boolean; +}; + +export function WizardFieldSelect({ + value, + placeholder, + icon, + options, + onPick, + search = true, + searchPlaceholder = "Search…", + disabled = false, +}: WizardFieldSelectProps) { + const { anchorEl, open, setAnchor, close } = useMenuAnchor(); + const [q, setQ] = useState(""); + const selected = options.find((o) => o.key === value); + const filtered = q + ? options.filter( + (o) => + o.label.toLowerCase().includes(q.toLowerCase()) || + o.key.toLowerCase().includes(q.toLowerCase()), + ) + : options; + + const handleClose = () => { + close(); + setQ(""); + }; + + return ( + <> + { + (e.currentTarget as HTMLButtonElement).style.boxShadow = "0 0 0 2px var(--color-primary)"; + }} + onBlur={(e) => { + (e.currentTarget as HTMLButtonElement).style.boxShadow = "none"; + }} + aria-haspopup="listbox" + > + {icon != null && {icon}} + {icon == null && selected?.left != null && ( + {selected.left} + )} + {selected ? ( + + {selected.label} + + ) : ( + + {placeholder} + + )} + + + + e.stopPropagation(), + }, + }} + > + {search && ( +
+ setQ(e.target.value)} + placeholder={searchPlaceholder} + className="w-full rounded px-2 py-1 text-[12px] focus:outline-none" + style={{ + border: "1px solid var(--color-border)", + color: "var(--color-text-primary)", + background: "var(--color-bg-surface)", + }} + /> +
+ )} +
+ {filtered.length === 0 ? ( +

+ No matches +

+ ) : ( + filtered.map((o) => ( + + )) + )} +
+
+ + ); +} diff --git a/src/app/(protected)/workorders/_components/wizard/wizard-footer.tsx b/src/app/(protected)/workorders/_components/wizard/wizard-footer.tsx new file mode 100644 index 00000000..4240f19a --- /dev/null +++ b/src/app/(protected)/workorders/_components/wizard/wizard-footer.tsx @@ -0,0 +1,110 @@ +import { Check, ChevronLeft, ChevronRight, Lightbulb } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { + WIZARD_STEPS, + canContinueWizardStep1, + canCreateWorkOrderFromWizard, + isWizardStepValid, + type WorkOrderWizardDraft, +} from "@/domain/work-orders/types/work-order-wizard"; + +type WizardFooterProps = { + step: number; + draft: WorkOrderWizardDraft; + onCancel: () => void; + onBack: () => void; + onContinue: () => void; + onCreate: () => void; + isCreating: boolean; +}; + +export function WizardFooter({ + step, + draft, + onCancel, + onBack, + onContinue, + onCreate, + isCreating, +}: WizardFooterProps) { + const stepValid = isWizardStepValid(step, draft); + const canCreate = canCreateWorkOrderFromWizard(draft); + const canContinue1 = canContinueWizardStep1(draft); + + return ( +
+ {step === 1 ? ( + + ) : ( + + )} + +
+ {step === 1 && !canContinue1 && ( + + Pick a type to continue + + )} +
+ + {step < WIZARD_STEPS.length ? ( + + ) : ( + + )} +
+ ); +} diff --git a/src/app/(protected)/workorders/_components/wizard/wizard-input-styles.ts b/src/app/(protected)/workorders/_components/wizard/wizard-input-styles.ts new file mode 100644 index 00000000..708de8d3 --- /dev/null +++ b/src/app/(protected)/workorders/_components/wizard/wizard-input-styles.ts @@ -0,0 +1,9 @@ +export const DLG_INPUT_CLS = + "w-full rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-primary"; + +export const DLG_INPUT_STYLE: React.CSSProperties = { + fontSize: 13, + border: "1px solid var(--color-border)", + color: "var(--color-text-primary)", + background: "var(--color-bg-surface)", +}; diff --git a/src/app/(protected)/workorders/_components/wizard/wizard-labels.tsx b/src/app/(protected)/workorders/_components/wizard/wizard-labels.tsx new file mode 100644 index 00000000..0d75b5be --- /dev/null +++ b/src/app/(protected)/workorders/_components/wizard/wizard-labels.tsx @@ -0,0 +1,180 @@ +import type { CSSProperties, ElementType, ReactNode } from "react"; +import { Check } from "lucide-react"; + +export function StepCircle({ n, done, active }: { n: number; done: boolean; active: boolean }) { + const base: React.CSSProperties = { + width: 28, + height: 28, + borderRadius: 9999, + fontFamily: "var(--font-sans)", + fontSize: 13, + fontWeight: 600, + }; + + if (done) { + return ( + + + + ); + } + + if (active) { + return ( + + {n} + + ); + } + + return ( + + {n} + + ); +} + +const WIZ_LABEL_STYLE: CSSProperties = { + fontFamily: "var(--font-mono)", + fontSize: 10, + fontWeight: 600, + letterSpacing: "0.08em", + textTransform: "uppercase", + color: "var(--muted-foreground)", +}; + +function WizOptionalTag() { + return ( + + optional + + ); +} + +export function WizLabel({ + children, + optional, + required, + leading, +}: { + children: ReactNode; + optional?: boolean; + required?: boolean; + leading?: ReactNode; +}) { + return ( + + ); +} + +export function WizSubLabel({ children }: { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +export function WizLabelRow({ + children, + optional, + onClear, +}: { + children: ReactNode; + optional?: boolean; + onClear?: () => void; +}) { + return ( +
+ + {children} + {optional && } + + {onClear && ( + + )} +
+ ); +} + +export function Detail({ icon: Icon, text }: { icon: ElementType; text: string }) { + return ( +
+ + {text} +
+ ); +} + +export function ColorDot({ color }: { color: string }) { + return ( + + ); +} diff --git a/src/app/(protected)/workorders/_components/wizard/wizard-sidebar.tsx b/src/app/(protected)/workorders/_components/wizard/wizard-sidebar.tsx new file mode 100644 index 00000000..3db0de5b --- /dev/null +++ b/src/app/(protected)/workorders/_components/wizard/wizard-sidebar.tsx @@ -0,0 +1,219 @@ +import { ClipboardList } from "lucide-react"; +import { + WIZARD_STATUS_DOTS, + WIZARD_STEPS, + type WizardWOStatus, + type WorkOrderWizardDraft, +} from "@/domain/work-orders/types/work-order-wizard"; +import { + fmtDate, + fmtWeekRange, + formatApptRange, +} from "@/domain/work-orders/utils/wizard-date-utils"; +import { + ColorDot, + StepCircle, +} from "@/app/(protected)/workorders/_components/wizard/wizard-labels"; + +type WizardSidebarProps = { + step: number; + draft: WorkOrderWizardDraft; +}; + +export function WizardSidebar({ step, draft }: WizardSidebarProps) { + const summary: [string, string][] = [ + ["WO #", draft.woNumber ? `#${draft.woNumber}` : "—"], + ["Type", draft.type || "—"], + [ + "Schedule on", + draft.scheduledOn + ? draft.scheduleWeekOnly + ? fmtWeekRange(draft.scheduledOn) + : fmtDate(draft.scheduledOn) + : "—", + ], + ["Due date", fmtDate(draft.dueDate) || "—"], + ["Assigned to", draft.assigneeName || "—"], + ["Site", draft.locationName || "—"], + [ + "POC", + draft.pocName + ? draft.pocPhone + ? `${draft.pocName} · ${draft.pocPhone}` + : draft.pocName + : "—", + ], + ["Service", draft.pm || "—"], + ["Technician", draft.vendorName || "—"], + ["Company", draft.vendorName || "—"], + ["Appt time", draft.apptStart ? formatApptRange(draft.apptStart, draft.apptEnd) : "—"], + ["Status", draft.status || "Incomplete"], + ]; + + const statusLabel = draft.status || "Incomplete"; + const statusValue = statusLabel as WizardWOStatus; + const statusDotColor = WIZARD_STATUS_DOTS[statusValue] ?? WIZARD_STATUS_DOTS.Incomplete; + + return ( +
+
+ + + +
+
+ Operations +
+
+ New work order +
+
+
+ +
+ {WIZARD_STEPS.map((s, i) => { + const n = i + 1; + const done = n < step; + const active = n === step; + const last = i === WIZARD_STEPS.length - 1; + return ( +
+
+ + {!last && ( +
+ )} +
+
+
+ {s.title} +
+
+ {s.nav} +
+
+
+ ); + })} +
+ +
+
+ This work order +
+ {summary.map(([label, val], i) => ( +
+ + {label} + + {label === "Status" ? ( + + + + {statusLabel} + + + ) : ( + + {val} + + )} +
+ ))} +
+
+ ); +} diff --git a/src/app/(protected)/workorders/_components/wizard/wizard-step-location-service.tsx b/src/app/(protected)/workorders/_components/wizard/wizard-step-location-service.tsx new file mode 100644 index 00000000..8d602150 --- /dev/null +++ b/src/app/(protected)/workorders/_components/wizard/wizard-step-location-service.tsx @@ -0,0 +1,167 @@ +import { ExternalLink, Info, Mail, MapPin, Phone } from "lucide-react"; +import type { LocationOption } from "@/domain/work-orders/types/work-order"; +import type { Location } from "@/domain/locations/types/location"; +import type { WorkOrderWizardDraft } from "@/domain/work-orders/types/work-order-wizard"; +import { WizardFieldSelect } from "@/app/(protected)/workorders/_components/wizard/wizard-field-select"; +import { + DLG_INPUT_CLS, + DLG_INPUT_STYLE, +} from "@/app/(protected)/workorders/_components/wizard/wizard-input-styles"; +import { + Detail, + WizLabel, + WizSubLabel, +} from "@/app/(protected)/workorders/_components/wizard/wizard-labels"; + +type ServiceOption = { id: string | number; value: string }; + +type WizardStepLocationServiceProps = { + draft: WorkOrderWizardDraft; + locations: LocationOption[]; + locationDetail: Location | undefined; + services: ServiceOption[]; + onUpdate: (key: K, value: WorkOrderWizardDraft[K]) => void; + onPatch: (patch: Partial) => void; +}; + +function buildMapsUrl(location: Location): string { + const parts = [location.address, location.city, location.state, location.zipCode] + .filter(Boolean) + .join(", "); + return `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(parts)}`; +} + +export function WizardStepLocationService({ + draft, + locations, + locationDetail, + services, + onUpdate, + onPatch, +}: WizardStepLocationServiceProps) { + return ( +
+
+ Site + + } + searchPlaceholder="Search site code or city…" + options={locations.map((loc) => ({ + key: String(loc.id), + label: loc.name, + }))} + onPick={(id) => { + const loc = locations.find((l) => String(l.id) === id); + onPatch({ + locationId: id, + locationName: loc?.name ?? "", + }); + }} + /> + {!draft.locationId && ( +

+ Site is required to continue. +

+ )} +
+ + {locationDetail && draft.locationId && ( + <> +
+ + {locationDetail.phone && } + {locationDetail.contactEmail && ( + + )} + {locationDetail.address && ( + + Open in Google Maps + + )} +
+
+ Point of contact +

+ Auto-filled from records — edit if outdated +

+
+ onUpdate("pocName", e.target.value)} + placeholder="POC name" + className={DLG_INPUT_CLS} + style={DLG_INPUT_STYLE} + /> + onUpdate("pocPhone", e.target.value)} + placeholder="POC phone" + className={DLG_INPUT_CLS} + style={DLG_INPUT_STYLE} + /> +
+