Files
LynkedUpPro_CRM/src/components/leads/LeadJobSection.jsx
T
Satyam d3e2944fa3 fix(leads): portaled dropdowns, flex-wrapper icons, responsive layout, and UI polish
- Replace all native <select> with LeadCustomSelect (ReactDOM.createPortal into body,
  getBoundingClientRect positioning, e.composedPath() scroll detection)
- Add LeadCombobox: fuzzy-search state picker (searches abbr + full name, portal, onMouseDown)
- Convert Phone/MapPin icons from absolute-positioned to flex-wrapper siblings of bg-transparent input
- Fix dark mode invisible text: all inputs converted from isDark JS conditionals to dark: variants
- Fix urgency Standard pill visibility: container bg-zinc-900, selected bg-zinc-700 ring-1 shadow-lg
- Fix City/State/ZIP mobile overlap: flex -> grid-cols-2 (mobile) / grid-cols-[1fr_5.5rem_6rem] (sm+)
- Fix urgency section hidden behind mobile sticky footer: pb-28 -> pb-44 md:pb-10
- Replace all compact type pickers (phone/email) with LeadCustomSelect compact mode
- Update README.md with full lead creation architecture docs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 17:22:58 +05:30

157 lines
6.5 KiB
React

import React from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { LEAD_FORM_OPTIONS } from '../../data/mockStore';
import UrgencyPillSelector from './UrgencyPillSelector';
import LeadCustomSelect from './LeadCustomSelect';
// ---------------------------------------------------------------------------
// Shared field primitives (local to Job section)
// ---------------------------------------------------------------------------
function FieldLabel({ children }) {
return (
<label className="block text-[11px] uppercase tracking-widest font-bold text-zinc-400 dark:text-zinc-500 mb-1.5">
{children}
</label>
);
}
function LeadTextarea({ label, value, onChange, placeholder, rows = 3 }) {
return (
<div>
<FieldLabel>{label}</FieldLabel>
<textarea
value={value}
onChange={e => onChange(e.target.value)}
placeholder={placeholder}
rows={rows}
className="w-full rounded-xl px-4 py-3 text-sm font-medium resize-none
outline-none transition-all duration-150
bg-white dark:bg-zinc-800/60
border border-zinc-200 dark:border-zinc-700
text-zinc-800 dark:text-white
placeholder-zinc-400 dark:placeholder-zinc-500
focus:border-blue-400 dark:focus:border-blue-500
focus:ring-2 focus:ring-blue-100 dark:focus:ring-blue-500/20"
/>
</div>
);
}
// Shared animation config for collapsing rows
const collapseTransition = {
type: 'spring',
stiffness: 300,
damping: 32,
opacity: { duration: 0.15 },
};
// ---------------------------------------------------------------------------
// Job Details Section
// ---------------------------------------------------------------------------
export default function LeadJobSection({ formData, updateField, isQuickCapture }) {
return (
<div>
{/* --- Lead Source — always visible --- */}
<LeadCustomSelect
label="Lead Source"
value={formData.leadSource}
onChange={v => updateField('leadSource', v)}
options={LEAD_FORM_OPTIONS.leadSources}
placeholder="How did you find this lead?"
accent="blue"
/>
{/* --- Lead Type — Full Form only --- */}
<AnimatePresence initial={false}>
{!isQuickCapture && (
<motion.div
key="leadType"
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={collapseTransition}
className="overflow-hidden"
>
<div className="pt-4">
<LeadCustomSelect
label="Lead Type"
value={formData.leadType}
onChange={v => updateField('leadType', v)}
options={LEAD_FORM_OPTIONS.leadTypes}
placeholder="Residential, Commercial…"
accent="blue"
/>
</div>
</motion.div>
)}
</AnimatePresence>
{/* --- Work Type + Trade Type — Full Form only --- */}
<AnimatePresence initial={false}>
{!isQuickCapture && (
<motion.div
key="workTrade"
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={collapseTransition}
className="overflow-hidden"
>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 pt-4">
<LeadCustomSelect
label="Work Type"
value={formData.workType}
onChange={v => updateField('workType', v)}
options={LEAD_FORM_OPTIONS.workTypes}
placeholder="Roof Replacement, Repair…"
accent="blue"
/>
<LeadCustomSelect
label="Trade Type"
value={formData.tradeType}
onChange={v => updateField('tradeType', v)}
options={LEAD_FORM_OPTIONS.tradeTypes}
placeholder="Roofing, Gutter, Siding…"
accent="blue"
/>
</div>
</motion.div>
)}
</AnimatePresence>
{/* --- Urgency — always visible --- */}
<div className="pt-4">
<FieldLabel>Urgency</FieldLabel>
<UrgencyPillSelector
value={formData.urgency}
onChange={v => updateField('urgency', v)}
/>
</div>
{/* --- Notes — Full Form only --- */}
<AnimatePresence initial={false}>
{!isQuickCapture && (
<motion.div
key="notes"
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={collapseTransition}
className="overflow-hidden"
>
<div className="pt-4">
<LeadTextarea
label="Notes"
value={formData.notes}
onChange={v => updateField('notes', v)}
placeholder="First impression, visible damage, special circumstances…"
rows={3}
/>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}