cleanup: remove dead code, drop unused deps, refactor derived-state effects
Lint went from 84 problems to 0. Build passes. Dead code: - TeamInvoices: drop the orphaned Subcontractor PO block (updateSubcontractorPO plus send/ready-to-pay/paid/reopen/cancel/delete/download/CPA-export handlers and ~25 derived vars). That feature moved to the routed TeamCreateSubcontractorPO / TeamSubcontractorPODetail pages in the Layout2 redesign; these were leftovers. 2100 -> 1880 lines. - Tasks: drop unrendered project sort/tab/completion logic in TasksPageShell. - Remove 59 unused vars, imports, and handlers across 12 other files. Removed wasted queries: - TeamInvoices ran a nested subcontractor_payments join (profiles, projects, companies, PO items, tasks) on every page load into state nothing read. - TeamInvoices and ExternalMyInvoices each fetched a task-type map on invoice open and discarded it; the ExternalMyInvoices one blocked the detail popup. - RequestForm queried sign_families on mount into unread state. Dependencies: - Drop heic2any (zero imports). heic-to is the one in use and already lazy-loads. Effects and exports: - RequestForm: remove 4 effects. Sign-row sizing and company-change resets move into their change handlers; single-company selection is derived during render rather than written back into form state. - FilterDropdown: measure position on click instead of in an effect, which also removes a one-frame stale-position flicker. - BrandBook: latest-ref pattern so the 900ms address debounce is not reset by handler identity. - ClientMyInvoices: hoist MONTHS to module scope. - Split non-component exports: POPUP_FIELD_LABEL moves to lib/popupStyles.js; resolveProfileAvatar and useLiveClock are internal-only; getGreeting deleted as nothing referenced it. Companies.jsx keeps one scoped eslint-disable with a reason: load() only setStates after awaiting its queries, so set-state-in-effect is a false positive there, and load() is shared with the create/delete handlers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,6 @@ export default function FilterDropdown({ value, onChange, options }) {
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
place();
|
||||
function onDown(e) {
|
||||
if (btnRef.current?.contains(e.target) || menuRef.current?.contains(e.target)) return;
|
||||
setOpen(false);
|
||||
@@ -48,7 +47,7 @@ export default function FilterDropdown({ value, onChange, options }) {
|
||||
<button
|
||||
ref={btnRef}
|
||||
type="button"
|
||||
onClick={() => setOpen(o => !o)}
|
||||
onClick={() => { if (!open) place(); setOpen(o => !o); }}
|
||||
aria-label="Filter"
|
||||
title="Filter"
|
||||
style={{
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
import { popupOverlayStyle, popupSurfaceStyle } from '../lib/popupStyles';
|
||||
import PageLoader from './PageLoader';
|
||||
|
||||
export const POPUP_FIELD_LABEL = {
|
||||
fontSize: 11,
|
||||
fontWeight: 500,
|
||||
color: 'var(--text-secondary)',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.8,
|
||||
display: 'block',
|
||||
marginBottom: 4,
|
||||
};
|
||||
|
||||
export default function InvoiceDetailPopup({
|
||||
title,
|
||||
subtitle,
|
||||
|
||||
@@ -2,7 +2,7 @@ function normalizeName(value) {
|
||||
return String(value || '').trim().toLowerCase();
|
||||
}
|
||||
|
||||
export function resolveProfileAvatar({
|
||||
function resolveProfileAvatar({
|
||||
avatarUrl = '',
|
||||
profile = null,
|
||||
profileId = '',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { supabase } from '../lib/supabase';
|
||||
import { serviceTypes } from '../data/mockData';
|
||||
import FileAttachment from './FileAttachment';
|
||||
@@ -49,51 +49,23 @@ export default function RequestForm({
|
||||
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)));
|
||||
}, []);
|
||||
// Single-company user: lock selection to their one company (derived, not stored).
|
||||
const companyId = form.companyId || (companies.length === 1 ? companies[0].id : '') || initialCompanyId;
|
||||
|
||||
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; }
|
||||
if (!companyId) return;
|
||||
Promise.all([
|
||||
supabase.from('projects').select('id, name').eq('company_id', companyId).order('name'),
|
||||
showRequester
|
||||
@@ -117,13 +89,6 @@ export default function RequestForm({
|
||||
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) => {
|
||||
@@ -136,9 +101,41 @@ export default function RequestForm({
|
||||
setSigns(prev => prev.map(s => s.id === id ? { ...s, [field]: value } : s));
|
||||
};
|
||||
|
||||
// Sign rows follow the count field; resized on edit rather than in an effect.
|
||||
const handleSignCountChange = (e) => {
|
||||
const raw = e.target.value;
|
||||
setSignCount(raw);
|
||||
const n = Math.max(0, parseInt(raw) || 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;
|
||||
});
|
||||
};
|
||||
|
||||
const handleServiceTypeChange = (e) => {
|
||||
set('serviceType')(e);
|
||||
if (e.target.value !== 'Brand Book') { setSignCount(''); setSigns([]); }
|
||||
};
|
||||
|
||||
// Switching company invalidates the project/requester picked under the old one.
|
||||
const handleCompanyChange = (e) => {
|
||||
setForm(f => ({ ...f, companyId: e.target.value, project: '', requestedBy: '' }));
|
||||
setCustomProjects([]);
|
||||
setIsTypingProject(false);
|
||||
setNewProjectName('');
|
||||
};
|
||||
|
||||
// No company selected yet means nothing loaded applies, so read through as empty.
|
||||
const activeProjects = companyId ? existingProjects : [];
|
||||
const activeCompanyUsers = companyId ? companyUsers : [];
|
||||
|
||||
const allProjectNames = [
|
||||
...existingProjects.map(p => p.name),
|
||||
...customProjects.filter(name => !existingProjects.some(p => p.name === name)),
|
||||
...activeProjects.map(p => p.name),
|
||||
...customProjects.filter(name => !activeProjects.some(p => p.name === name)),
|
||||
];
|
||||
|
||||
const handleProjectSelect = (e) => {
|
||||
@@ -164,7 +161,7 @@ export default function RequestForm({
|
||||
const showCompanySelect = companies.length > 1 || showRequester;
|
||||
const requesterOptions = [
|
||||
...(currentUser ? [{ id: currentUser.id, name: `${currentUser.name} (You)` }] : []),
|
||||
...companyUsers.filter(u => u.id !== currentUser?.id),
|
||||
...activeCompanyUsers.filter(u => u.id !== currentUser?.id),
|
||||
];
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
@@ -230,7 +227,7 @@ export default function RequestForm({
|
||||
) : showCompanySelect ? (
|
||||
<div className="form-group">
|
||||
<label style={modalLabelStyle}>Company *</label>
|
||||
<select value={form.companyId} onChange={e => setForm(f => ({ ...f, companyId: e.target.value }))} required style={modalInputStyle}>
|
||||
<select value={companyId} onChange={handleCompanyChange} required style={modalInputStyle}>
|
||||
<option value="">Select company...</option>
|
||||
{companies.map(co => <option key={co.id} value={co.id}>{co.name}</option>)}
|
||||
</select>
|
||||
@@ -265,7 +262,7 @@ export default function RequestForm({
|
||||
<div className="grid-2">
|
||||
<div className="form-group">
|
||||
<label style={modalLabelStyle}>Service Type *</label>
|
||||
<select value={form.serviceType} onChange={set('serviceType')} required style={modalInputStyle}>
|
||||
<select value={form.serviceType} onChange={handleServiceTypeChange} required style={modalInputStyle}>
|
||||
<option value="">Select service...</option>
|
||||
{serviceTypes.map(s => <option key={s} value={s}>{s}</option>)}
|
||||
</select>
|
||||
@@ -315,7 +312,7 @@ export default function RequestForm({
|
||||
max="50"
|
||||
placeholder="How many signs?"
|
||||
value={signCount}
|
||||
onChange={e => setSignCount(e.target.value)}
|
||||
onChange={handleSignCountChange}
|
||||
required
|
||||
style={{ ...modalInputStyle, width: '100%' }}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user