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 modalActionButtonStyle = { width: 132, justifyContent: 'center', flex: '0 0 132px' }; 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), requestedBy: showRequester ? (currentUser?.id || '') : '', ...(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 [localError, setLocalError] = useState(''); const usesSignFields = form.serviceType === 'Brand Book'; useEffect(() => { supabase.from('sign_families').select('name').order('sort_order').then(({ data }) => setSignFamilies((data || []).map(r => r.name))); }, []); useEffect(() => { if (!usesSignFields) { setSignCount(''); setSigns([]); } }, [usesSignFields]); 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; // Single-company user: lock selection to their one company. useEffect(() => { if (companies.length === 1 && !form.companyId) { setForm(f => ({ ...f, companyId: companies[0].id })); } }, [companies, form.companyId]); 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) => { if (localError) setLocalError(''); setForm(f => ({ ...f, [field]: e.target.value })); }; const setSign = (id, field, value) => { if (localError) setLocalError(''); 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 requesterOptions = [ ...(currentUser ? [{ id: currentUser.id, name: `${currentUser.name} (You)` }] : []), ...companyUsers.filter(u => u.id !== currentUser?.id), ]; const handleSubmit = (e) => { e.preventDefault(); setLocalError(''); const selectedRequester = showRequester ? requesterOptions.find(option => option.id === form.requestedBy) || null : null; const requestedBy = showRequester ? (selectedRequester?.id || '') : (currentUser?.id || ''); const requestedByName = showRequester ? ((selectedRequester?.name || '').replace(/\s+\(You\)$/, '') || '') : (currentUser?.name || ''); let normalizedSignCount = null; let normalizedSigns = []; if (usesSignFields) { normalizedSignCount = parseInt(signCount, 10) || null; const formData = new FormData(e.currentTarget); normalizedSigns = Array.from({ length: normalizedSignCount || 0 }, (_, index) => { const stateSign = signs[index] || {}; const domValue = formData.get(`sign_info_${index + 1}`); const signName = typeof domValue === 'string' ? domValue.trim() : String(stateSign.signName || '').trim(); return { id: stateSign.id || `sign-${index + 1}`, signName, signFamily: stateSign.signFamily || '', }; }).filter(sign => sign.signName || sign.signFamily); if ((normalizedSignCount || 0) > 0 && normalizedSigns.length !== normalizedSignCount) { setLocalError('Please fill in the sign information for each sign before submitting.'); return; } } onSubmit( { ...form, companyId, requestedBy, requestedByName, signCount: usesSignFields ? normalizedSignCount : null, signs: usesSignFields ? normalizedSigns : [], }, 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 ? (
) : companies.length === 1 ? (
) :
}
{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 */}
{showRequester && (
)} {/* Row 3: Title/Location + Mark as Hot inline */}
{usesSignFields && (
setSignCount(e.target.value)} required style={{ ...modalInputStyle, width: '100%' }} />
)} {usesSignFields && signs.length > 0 && (
{signs.map((sign, i) => (
Sign {i + 1}
setSign(sign.id, 'signName', e.target.value)} required style={modalInputStyle} />
))}
)}