import { useState, useEffect, useRef } from 'react'; import { supabase } from '../lib/supabase'; import { serviceTypes } from '../data/mockData'; import FileAttachment from './FileAttachment'; import { addDaysToDateOnly, getTodayDateOnlyEST } from '../lib/dates'; const defaultDeadline = () => addDaysToDateOnly(getTodayDateOnlyEST(), 3); const modalLabelStyle = { fontSize: 11, fontWeight: 500, color: 'var(--text-secondary)', textTransform: 'uppercase', letterSpacing: 0.8, marginBottom: 6 }; const modalInputStyle = { fontSize: 13, padding: '0 5px', textAlign: 'left', lineHeight: 1 }; const modalTextAreaStyle = { ...modalInputStyle, minHeight: 100, padding: '8px 5px', lineHeight: 1.4 }; const emptyForm = (companyId = '') => ({ companyId, project: '', serviceType: '', signFamily: '', title: '', deadline: defaultDeadline(), description: '', isHot: false, requestedBy: '', }); // Props: // companies [{id, name}] — all selectable companies // currentUser {id, name} — logged-in team member (added to requester list as "You") // showRequester bool — true for team (submitting on behalf of client) // onSubmit async (formData, files, existingProjects) => void — throw to show error // onCancel () => void — optional; shows Cancel button when provided // saving bool // error string // submitLabel string // initialCompanyId string — pre-selected company (client with 1 company) export default function RequestForm({ companies = [], currentUser = null, showRequester = false, onSubmit, onCancel = null, saving = false, error = '', submitLabel = 'Submit Request', initialCompanyId = '', initialValues = null, lockedFields = [], }) { const [form, setForm] = useState(() => ({ ...emptyForm(initialCompanyId), ...(initialValues || {}) })); const prevCompanyIdRef = useRef(initialValues?.companyId || initialCompanyId || null); const [files, setFiles] = useState([]); const [existingProjects, setExistingProjects] = useState([]); const [customProjects, setCustomProjects] = useState([]); const [isTypingProject, setIsTypingProject] = useState(false); const [newProjectName, setNewProjectName] = useState(''); const [companyUsers, setCompanyUsers] = useState([]); const [signFamilies, setSignFamilies] = useState([]); const [signCount, setSignCount] = useState(''); const [signs, setSigns] = useState([]); const isBrandBook = form.serviceType === 'Brand Book'; useEffect(() => { supabase.from('sign_families').select('name').order('sort_order').then(({ data }) => setSignFamilies((data || []).map(r => r.name))); }, []); useEffect(() => { if (!isBrandBook) { setSignCount(''); setSigns([]); } }, [isBrandBook]); useEffect(() => { const n = Math.max(0, parseInt(signCount) || 0); setSigns(prev => { if (n < prev.length) return prev.slice(0, n); if (n > prev.length) { const extra = Array.from({ length: n - prev.length }, () => ({ id: crypto.randomUUID(), signName: '', signFamily: '' })); return [...prev, ...extra]; } return prev; }); }, [signCount]); const companyId = form.companyId || initialCompanyId; useEffect(() => { if (!companyId) { setExistingProjects([]); setCompanyUsers([]); return; } Promise.all([ supabase.from('projects').select('id, name').eq('company_id', companyId).order('name'), showRequester ? Promise.all([ supabase.from('profiles').select('id, name, email').eq('company_id', companyId).eq('role', 'client'), supabase.from('company_members').select('profile:profiles(id, name, email, role)').eq('company_id', companyId), ]) : Promise.resolve(null), ]).then(([projectsRes, usersData]) => { setExistingProjects(projectsRes.data || []); if (usersData) { const [directRes, membersRes] = usersData; const direct = (directRes.data || []); const fromMembers = (membersRes.data || []).map(m => m.profile).filter(p => p?.role === 'client'); const seen = new Set(); const merged = [...direct, ...fromMembers].filter(u => { if (!u?.id || seen.has(u.id)) return false; seen.add(u.id); return true; }).sort((a, b) => (a.name || '').localeCompare(b.name || '')); setCompanyUsers(merged); } }); if (companyId !== prevCompanyIdRef.current) { setForm(f => ({ ...f, project: '', requestedBy: '' })); setCustomProjects([]); setIsTypingProject(false); setNewProjectName(''); } prevCompanyIdRef.current = companyId; }, [companyId]); // eslint-disable-line react-hooks/exhaustive-deps const set = (field) => (e) => setForm(f => ({ ...f, [field]: e.target.value })); const setSign = (id, field, value) => setSigns(prev => prev.map(s => s.id === id ? { ...s, [field]: value } : s)); const allProjectNames = [ ...existingProjects.map(p => p.name), ...customProjects.filter(name => !existingProjects.some(p => p.name === name)), ]; const handleProjectSelect = (e) => { if (e.target.value === '__new__') { setIsTypingProject(true); setForm(f => ({ ...f, project: '' })); } else { setForm(f => ({ ...f, project: e.target.value })); } }; const handleAddProject = () => { const name = newProjectName.trim(); if (!name) return; if (!customProjects.includes(name) && !existingProjects.some(p => p.name === name)) { setCustomProjects(prev => [...prev, name]); } setForm(f => ({ ...f, project: name })); setIsTypingProject(false); setNewProjectName(''); }; const showCompanySelect = companies.length > 1 || showRequester; const handleSubmit = (e) => { e.preventDefault(); const requestedByName = currentUser?.name || ''; onSubmit( { ...form, companyId, requestedBy: currentUser?.id || '', requestedByName, signCount: isBrandBook ? (parseInt(signCount) || null) : null, signs: isBrandBook ? signs : [], }, files, existingProjects ); }; return (
{/* Row 1: Company + Project */}
{lockedFields.includes('company') ? (
c.id === companyId)?.name || companyId} disabled style={{ ...modalInputStyle, opacity: 0.5, cursor: 'not-allowed' }} />
) : showCompanySelect ? (
) :
}
{lockedFields.includes('project') ? ( ) : isTypingProject ? (
setNewProjectName(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); handleAddProject(); } }} autoFocus style={{ ...modalInputStyle, flex: 1 }} />
) : ( )}
{/* Row 2: Service Type + Desired Deadline */}
{/* Row 3: Title/Location + Mark as Hot inline */}
{isBrandBook && (
setSignCount(e.target.value)} required style={{ ...modalInputStyle, width: '100%' }} />
)} {isBrandBook && signs.length > 0 && (
{signs.map((sign, i) => (
Sign {i + 1}
setSign(sign.id, 'signName', e.target.value)} required style={modalInputStyle} />
))}
)}