Session 2026-05-30: task detail page overhaul
- New TaskDetail replaces /tasks/:id (was v2 at /requests/:id/v2) - Overview tab: R00 request info, Requested By/Date/Sign Count inline row, Notes, Sign Family, files, amendments - Revisions tab: R01+ from submissions, newest first, same layout as overview, per-entry Amend button - Comments tab: single-line input, post on Enter, delete own comments, Supabase task_comments table - Folder tab: placeholder for file sharing - Tab badges showing revision/comment counts - Amend Request modal: drag-drop file zone, popupOverlayStyle/Surface, Save+Cancel buttons - Request Revision modal for clients on approved/invoiced/paid tasks - JSZip download-all with progress popup for submission files - Upload progress popup for Add Task and amend file uploads - FileAttachment drop zone: 8px radius, transparent bg, label style fix (no all-caps override) - PageLoader on task detail load - task_comments migration with RLS policies Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,8 @@ export default function CompanyDetail() {
|
||||
const [users, setUsers] = useState([]);
|
||||
const [availableUsers, setAvailableUsers] = useState([]);
|
||||
const [prices, setPrices] = useState([]);
|
||||
const [signFamilies, setSignFamilies] = useState([]);
|
||||
const [savingSignFamilyPrice, setSavingSignFamilyPrice] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [tab, setTab] = useState('users');
|
||||
const [savingPrice, setSavingPrice] = useState(null);
|
||||
@@ -35,6 +37,10 @@ export default function CompanyDetail() {
|
||||
const [editUserVal, setEditUserVal] = useState('');
|
||||
const [deletingUserId, setDeletingUserId] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
supabase.from('sign_families').select('name').order('sort_order').then(({ data }) => setSignFamilies((data || []).map(r => r.name)));
|
||||
}, []);
|
||||
|
||||
async function load() {
|
||||
const [{ data: co }, { data: p }, { data: pr }, { data: memberRows }, { data: allUsers }, { data: t }] = await Promise.all([
|
||||
supabase.from('companies').select('*').eq('id', id).single(),
|
||||
@@ -212,6 +218,36 @@ export default function CompanyDetail() {
|
||||
setSavingPrice(null);
|
||||
};
|
||||
|
||||
const getSignFamilyPrice = (signFamily, priceType) =>
|
||||
prices.find(p => p.sign_family === signFamily && p.price_type === priceType && !p.service_type)?.price ?? '';
|
||||
|
||||
const handleSignFamilyPriceChange = (signFamily, priceType, value) => {
|
||||
setPrices(prev => {
|
||||
const existing = prev.find(p => p.sign_family === signFamily && p.price_type === priceType && !p.service_type);
|
||||
if (existing) return prev.map(p => p.sign_family === signFamily && p.price_type === priceType && !p.service_type ? { ...p, price: value } : p);
|
||||
return [...prev, { sign_family: signFamily, price_type: priceType, price: value, company_id: id }];
|
||||
});
|
||||
};
|
||||
|
||||
const handleSignFamilyPriceSave = async (signFamily) => {
|
||||
setSavingSignFamilyPrice(signFamily);
|
||||
for (const priceType of ['new', 'revision']) {
|
||||
const priceVal = getSignFamilyPrice(signFamily, priceType);
|
||||
const existing = prices.find(p => p.sign_family === signFamily && p.price_type === priceType && !p.service_type && p.id);
|
||||
if (existing) {
|
||||
const { error } = await supabase.from('company_prices').update({ price: Number(priceVal) }).eq('id', existing.id);
|
||||
if (error) { setSavingSignFamilyPrice(null); alert('Failed to save price. Please try again.'); return; }
|
||||
} else if (priceVal !== '') {
|
||||
const { data, error } = await supabase.from('company_prices').insert({
|
||||
company_id: id, sign_family: signFamily, price_type: priceType, price: Number(priceVal),
|
||||
}).select().single();
|
||||
if (error) { setSavingSignFamilyPrice(null); alert('Failed to save price. Please try again.'); return; }
|
||||
if (data) setPrices(prev => [...prev.filter(p => !(p.sign_family === signFamily && p.price_type === priceType && !p.service_type && !p.id)), data]);
|
||||
}
|
||||
}
|
||||
setSavingSignFamilyPrice(null);
|
||||
};
|
||||
|
||||
if (loading) return <Layout><p style={{ padding: 24, color: 'var(--text-muted)' }}>Loading...</p></Layout>;
|
||||
if (!company) return <Layout><p>Company not found.</p></Layout>;
|
||||
|
||||
@@ -538,6 +574,48 @@ export default function CompanyDetail() {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 32 }}>
|
||||
<div className="card-title" style={{ fontSize: 14, marginBottom: 8 }}>Sign Family Prices</div>
|
||||
<p style={{ fontSize: 13, color: 'var(--text-muted)', marginBottom: 16 }}>
|
||||
Set prices per sign family for this company.
|
||||
</p>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 130px 130px 60px', gap: 8, marginBottom: 8, alignItems: 'center' }}>
|
||||
<div />
|
||||
{['New', 'Revision'].map(label => (
|
||||
<div key={label} style={{ fontSize: 11, fontWeight: 400, textTransform: 'uppercase', letterSpacing: '0.5px', color: 'var(--text-muted)', textAlign: 'right' }}>{label}</div>
|
||||
))}
|
||||
<div />
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
{signFamilies.map(sf => (
|
||||
<div key={sf} style={{ display: 'grid', gridTemplateColumns: '1fr 130px 130px 60px', gap: 8, alignItems: 'center' }}>
|
||||
<div style={{ fontSize: 14, fontWeight: 500 }}>{sf}</div>
|
||||
{['new', 'revision'].map(priceType => (
|
||||
<div key={priceType} style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
<span style={{ color: 'var(--text-muted)', fontSize: 14 }}>$</span>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
placeholder="0.00"
|
||||
value={getSignFamilyPrice(sf, priceType)}
|
||||
onChange={e => handleSignFamilyPriceChange(sf, priceType, e.target.value)}
|
||||
style={{ margin: 0, width: '100%', textAlign: 'right' }}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
className="btn btn-outline btn-sm"
|
||||
onClick={() => handleSignFamilyPriceSave(sf)}
|
||||
disabled={savingSignFamilyPrice === sf}
|
||||
>
|
||||
{savingSignFamilyPrice === sf ? '...' : 'Save'}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Layout>
|
||||
|
||||
Reference in New Issue
Block a user