@@ -412,7 +412,7 @@ function TeamCompanies() {
}).map(user => {
const companyNames = getProfileCompanyIds(user).map(id => companies.find(c => c.id === id)?.name).filter(Boolean);
return (
-
{editingUserId === user.id ? (
@@ -460,7 +460,7 @@ function TeamCompanies() {
{subSort(subcontractors, (u, key) => u[key] || '').map(user => (
-
+
{editingUserId === user.id ? (
diff --git a/src/pages/CompanyDetail.jsx b/src/pages/CompanyDetail.jsx
index d4c8ba5..b357ae4 100644
--- a/src/pages/CompanyDetail.jsx
+++ b/src/pages/CompanyDetail.jsx
@@ -512,7 +512,7 @@ export default function CompanyDetail() {
const active = projectTasks.filter(t => t.status !== 'client_approved').length;
const done = projectTasks.filter(t => t.status === 'client_approved').length;
return (
-
+
{project.name}
@@ -528,7 +528,7 @@ export default function CompanyDetail() {
{isTeam && }
diff --git a/src/pages/Converters.jsx b/src/pages/Converters.jsx
index f68ccff..4dbe6c8 100644
--- a/src/pages/Converters.jsx
+++ b/src/pages/Converters.jsx
@@ -408,7 +408,7 @@ export default function Converters() {
{result.error}
)}
{result?.status === 'done' && (
-
+
Ready as {outputName}
)}
diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx
index c00c855..4d3c254 100644
--- a/src/pages/Login.jsx
+++ b/src/pages/Login.jsx
@@ -59,14 +59,14 @@ export default function Login() {
required
/>
- {successMessage && {successMessage} }
- {error && {error} }
+ {successMessage && {successMessage} }
+ {error && {error} }
-
+
Contact Fourge Branding to get access.
diff --git a/src/pages/PayInvoice.jsx b/src/pages/PayInvoice.jsx
index a5b0691..26c8ae3 100644
--- a/src/pages/PayInvoice.jsx
+++ b/src/pages/PayInvoice.jsx
@@ -83,21 +83,21 @@ export default function PayInvoice() {
{!invoice ? (
-
+
Invoice not found
This payment link may be invalid or expired.
) : success || invoice.status === 'paid' ? (
-
+
✓
Payment received
{invoice.invoice_number}
- {totalLabel}
+ {totalLabel}
Charged in USD
Thank you for your payment. We'll be in touch!
) : (
-
+
Invoice
{invoice.invoice_number}
{invoice.bill_to || company?.name}
@@ -105,27 +105,27 @@ export default function PayInvoice() {
Invoice Date
- {new Date(invoice.invoice_date).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })}
+ {new Date(invoice.invoice_date).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })}
Due Date
- {new Date(invoice.due_date).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })}
+ {new Date(invoice.due_date).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })}
Total Due
- {totalLabel}
+ {totalLabel}
{cancelled && (
-
+
Payment was cancelled. You can try again below.
)}
{error && (
-
+
{error}
)}
@@ -135,7 +135,7 @@ export default function PayInvoice() {
disabled={paying}
style={{
width: '100%', padding: '14px', borderRadius: 4, border: 'none',
- background: paying ? '#999' : '#141414', color: '#fff',
+ background: paying ? '#999' : 'var(--surface-sunken)', color: '#fff',
fontSize: 16, fontWeight: 400, cursor: paying ? 'not-allowed' : 'pointer',
}}
>
diff --git a/src/pages/Profile.jsx b/src/pages/Profile.jsx
index 1fb8a37..3d7374a 100644
--- a/src/pages/Profile.jsx
+++ b/src/pages/Profile.jsx
@@ -18,7 +18,7 @@ function smoothCurve(pts) {
return d;
}
-function MiniAreaChart({ data, color = '#F5A523', gradId = 'ag1' }) {
+function MiniAreaChart({ data, color = 'var(--accent)', gradId = 'ag1' }) {
const W = 90, H = 42;
if (!data || data.length < 2) return null;
const max = Math.max(...data, 1);
@@ -370,15 +370,15 @@ export default function ProfilePage() {
const ACTION_ICON = {
- task_started: { bg: 'rgba(96,165,250,0.15)', color: '#60a5fa', path: },
- task_resumed: { bg: 'rgba(96,165,250,0.15)', color: '#60a5fa', path: },
- task_submitted: { bg: 'rgba(74,222,128,0.15)', color: '#4ade80', path: <> > },
- task_approved: { bg: 'rgba(74,222,128,0.15)', color: '#4ade80', path: },
- task_on_hold: { bg: 'rgba(239,68,68,0.15)', color: '#ef4444', path: <> > },
- task_created: { bg: 'rgba(167,139,250,0.15)', color: '#a78bfa', path: <> > },
- project_created: { bg: 'rgba(245,165,35,0.15)', color: '#F5A523', path: <> > },
- request_submitted: { bg: 'rgba(167,139,250,0.15)', color: '#a78bfa', path: <> > },
- revision_requested: { bg: 'rgba(245,158,11,0.15)', color: '#f59e0b', path: <> > },
+ task_started: { bg: 'color-mix(in srgb, var(--info) 15%, transparent)', color: 'var(--info)', path: },
+ task_resumed: { bg: 'color-mix(in srgb, var(--info) 15%, transparent)', color: 'var(--info)', path: },
+ task_submitted: { bg: 'color-mix(in srgb, var(--positive) 15%, transparent)', color: 'var(--positive)', path: <> > },
+ task_approved: { bg: 'color-mix(in srgb, var(--positive) 15%, transparent)', color: 'var(--positive)', path: },
+ task_on_hold: { bg: 'color-mix(in srgb, var(--danger) 15%, transparent)', color: 'var(--danger)', path: <> > },
+ task_created: { bg: 'color-mix(in srgb, var(--violet) 15%, transparent)', color: 'var(--violet)', path: <> > },
+ project_created: { bg: 'color-mix(in srgb, var(--accent) 15%, transparent)', color: 'var(--accent)', path: <> > },
+ request_submitted: { bg: 'color-mix(in srgb, var(--violet) 15%, transparent)', color: 'var(--violet)', path: <> > },
+ revision_requested: { bg: 'color-mix(in srgb, var(--warning) 15%, transparent)', color: 'var(--warning)', path: <> > },
};
const ActionIcon = ({ actionKey, size = 27 }) => {
const cfg = ACTION_ICON[actionKey] || { bg: 'rgba(255,255,255,0.08)', color: 'rgba(255,255,255,0.4)', path: };
@@ -605,12 +605,12 @@ export default function ProfilePage() {
-
+
{/* Active Projects — monthly */}
@@ -622,12 +622,12 @@ export default function ProfilePage() {
-
+
)}
diff --git a/src/pages/ProjectDetail.jsx b/src/pages/ProjectDetail.jsx
index 1d1cd87..9ee287d 100644
--- a/src/pages/ProjectDetail.jsx
+++ b/src/pages/ProjectDetail.jsx
@@ -418,7 +418,7 @@ export default function ProjectDetailPage() {
:
}
|
-
+ |
{row.isHot ? 'HOT' : 'NO'}
|
{row.serviceType} |
@@ -517,7 +517,7 @@ export default function ProjectDetailPage() {
Activity
{(() => {
- const SHOW = { task_started: { label: 'started', color: '#60a5fa', bg: 'rgba(96,165,250,0.15)', path: 'M6 4l14 8-14 8V4z' }, task_on_hold: { label: 'placed on hold', color: '#ef4444', bg: 'rgba(239,68,68,0.15)', path: 'M6 4h4v16H6zM14 4h4v16h-4z' }, task_approved: { label: 'approved', color: '#4ade80', bg: 'rgba(74,222,128,0.15)', path: 'M4 13l5 5L20 7' } };
+ const SHOW = { task_started: { label: 'started', color: 'var(--info)', bg: 'color-mix(in srgb, var(--info) 15%, transparent)', path: 'M6 4l14 8-14 8V4z' }, task_on_hold: { label: 'placed on hold', color: 'var(--danger)', bg: 'color-mix(in srgb, var(--danger) 15%, transparent)', path: 'M6 4h4v16H6zM14 4h4v16h-4z' }, task_approved: { label: 'approved', color: 'var(--positive)', bg: 'color-mix(in srgb, var(--positive) 15%, transparent)', path: 'M4 13l5 5L20 7' } };
const filtered = activityLog;
if (filtered.length === 0) return No activity ;
return (
@@ -613,7 +613,7 @@ export default function ProjectDetailPage() {
{p.email || '—'} |
- {checked && }
+ {checked && }
|
diff --git a/src/pages/SurveyMaker.jsx b/src/pages/SurveyMaker.jsx
index 07e9d0b..e2b0fbf 100644
--- a/src/pages/SurveyMaker.jsx
+++ b/src/pages/SurveyMaker.jsx
@@ -90,7 +90,7 @@ export default function SurveyMaker() {
{notification && (
-
+
{notification.msg}
)}
@@ -279,7 +279,7 @@ function PhotoPicker({ inputRef, preview, label, onPick, small = false }) {
borderRadius: 4,
minHeight: small ? 110 : 120,
cursor: 'pointer',
- background: dragging ? 'rgba(245,165,35,0.05)' : 'var(--card-bg-2)',
+ background: dragging ? 'color-mix(in srgb, var(--accent) 5%, transparent)' : 'var(--card-bg-2)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
diff --git a/src/pages/TaskDetail.jsx b/src/pages/TaskDetail.jsx
index bed9159..cf2243d 100644
--- a/src/pages/TaskDetail.jsx
+++ b/src/pages/TaskDetail.jsx
@@ -25,15 +25,15 @@ const ACTION_LABEL = {
};
const ACTION_ICON = {
- task_started: { bg: 'rgba(96,165,250,0.15)', color: '#60a5fa', path: 'M6 4l14 8-14 8V4z' },
- task_resumed: { bg: 'rgba(96,165,250,0.15)', color: '#60a5fa', path: 'M6 4l14 8-14 8V4z' },
- task_submitted: { bg: 'rgba(74,222,128,0.15)', color: '#4ade80', path: 'M12 19V5M5 12l7-7 7 7' },
- task_approved: { bg: 'rgba(74,222,128,0.15)', color: '#4ade80', path: 'M4 13l5 5L20 7' },
- task_on_hold: { bg: 'rgba(239,68,68,0.15)', color: '#ef4444', path: 'M6 4h4v16H6zM14 4h4v16h-4z' },
- task_released: { bg: 'rgba(239,68,68,0.15)', color: '#ef4444', path: 'M12 5v14M5 12h14' },
- task_created: { bg: 'rgba(167,139,250,0.15)', color: '#a78bfa', path: 'M12 5v14M5 12h14' },
- revision_requested: { bg: 'rgba(245,158,11,0.15)', color: '#f59e0b', path: 'M4 12a8 8 0 018-8v0a8 8 0 018 8' },
- request_submitted: { bg: 'rgba(167,139,250,0.15)', color: '#a78bfa', path: 'M9 12h6M9 8h6M5 3h14a2 2 0 012 2v14a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2z' },
+ task_started: { bg: 'color-mix(in srgb, var(--info) 15%, transparent)', color: 'var(--info)', path: 'M6 4l14 8-14 8V4z' },
+ task_resumed: { bg: 'color-mix(in srgb, var(--info) 15%, transparent)', color: 'var(--info)', path: 'M6 4l14 8-14 8V4z' },
+ task_submitted: { bg: 'color-mix(in srgb, var(--positive) 15%, transparent)', color: 'var(--positive)', path: 'M12 19V5M5 12l7-7 7 7' },
+ task_approved: { bg: 'color-mix(in srgb, var(--positive) 15%, transparent)', color: 'var(--positive)', path: 'M4 13l5 5L20 7' },
+ task_on_hold: { bg: 'color-mix(in srgb, var(--danger) 15%, transparent)', color: 'var(--danger)', path: 'M6 4h4v16H6zM14 4h4v16h-4z' },
+ task_released: { bg: 'color-mix(in srgb, var(--danger) 15%, transparent)', color: 'var(--danger)', path: 'M12 5v14M5 12h14' },
+ task_created: { bg: 'color-mix(in srgb, var(--violet) 15%, transparent)', color: 'var(--violet)', path: 'M12 5v14M5 12h14' },
+ revision_requested: { bg: 'color-mix(in srgb, var(--warning) 15%, transparent)', color: 'var(--warning)', path: 'M4 12a8 8 0 018-8v0a8 8 0 018 8' },
+ request_submitted: { bg: 'color-mix(in srgb, var(--violet) 15%, transparent)', color: 'var(--violet)', path: 'M9 12h6M9 8h6M5 3h14a2 2 0 012 2v14a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2z' },
};
function ActivityItem({ e }) {
@@ -120,7 +120,7 @@ function TimelineCard({ task, activityLog, currentSubmission }) {
if (isDone) {
circle = (
);
} else if (isCurrent) {
@@ -162,7 +162,7 @@ const fmtSize = (bytes) => {
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
};
-const FILE_EXT_COLOR = { pdf: '#ef4444', jpg: '#f59e0b', jpeg: '#f59e0b', png: '#3b82f6', gif: '#8b5cf6', svg: '#10b981', ai: '#f97316', psd: '#3b82f6', zip: '#6b7280', rar: '#6b7280', doc: '#2563eb', docx: '#2563eb', xls: '#16a34a', xlsx: '#16a34a', mp4: '#ec4899', mov: '#ec4899' };
+const FILE_EXT_COLOR = { pdf: 'var(--danger)', jpg: 'var(--warning)', jpeg: 'var(--warning)', png: 'var(--data-blue)', gif: 'var(--data-purple)', svg: 'var(--data-emerald)', ai: 'var(--data-orange)', psd: 'var(--data-blue)', zip: 'var(--data-gray)', rar: 'var(--data-gray)', doc: 'var(--data-indigo)', docx: 'var(--data-indigo)', xls: 'var(--success-strong)', xlsx: 'var(--success-strong)', mp4: 'var(--data-pink)', mov: 'var(--data-pink)' };
const getExt = (name = '') => (name.split('.').pop() || '').toLowerCase();
function SubmissionFiles({ files, downloading, onDownloadAll }) {
@@ -699,7 +699,7 @@ export default function TaskDetail() {
-
+
{task.title}
@@ -1036,7 +1036,7 @@ export default function TaskDetail() {
{reviewModal && (
{ if (!reviewSaving) { setReviewModal(false); setReviewFiles([]); setReviewNotes(''); } }}>
- e.stopPropagation()}>
+ e.stopPropagation()}>
Place in Review
Notes (optional)
@@ -1091,7 +1091,7 @@ export default function TaskDetail() {
{rejectModal && (
{ if (!rejectSaving) { setRejectModal(false); setRejectNote(''); } }}>
- e.stopPropagation()}>
+ e.stopPropagation()}>
Reject Task
Rejected Notes
@@ -1115,7 +1115,7 @@ export default function TaskDetail() {
{amendModal && (
{ setAmendModal(false); setAmendForm({ description: '' }); setAmendFiles([]); }}>
- e.stopPropagation()}>
+ e.stopPropagation()}>
Amend Request
Notes
diff --git a/src/pages/Tasks.jsx b/src/pages/Tasks.jsx
index e9b0664..df27760 100644
--- a/src/pages/Tasks.jsx
+++ b/src/pages/Tasks.jsx
@@ -16,10 +16,10 @@ import { createInitialSubmissionForRequest, createTaskForRequest, findOrCreatePr
import { ensureProjectFolder, ensureRequestFolder, uploadRequestFileToSurvey } from '../lib/folderSync';
import { sendEmail } from '../lib/email';
import SortTh from '../components/SortTh';
+import FilterDropdown from '../components/FilterDropdown';
import { useSortable } from '../hooks/useSortable';
import { popupOverlayStyle } from '../lib/popupStyles';
import { useLiveRefresh } from '../hooks/useLiveRefresh';
-import { resolveScopedWorkIds } from '../lib/workScope';
import { mergeSubmissionDisplayNames } from '../lib/submissionDisplay';
import {
TASK_TABLE_TH_STYLE,
@@ -45,7 +45,7 @@ const TASK_STATUS_SORT_RANK = { not_started: 0, in_progress: 1, on_hold: 2, clie
function TaskStatCard({ label, value, sub, iconBg, iconColor, iconPath }) {
return (
-
+
{label}
@@ -69,12 +69,12 @@ function TasksStatsRow({ tasks = [] }) {
const pct = (count) => total > 0 ? `${Math.round((count / total) * 100)}% of total` : '0% of total';
return (
-
-
-
-
-
-
+
+
+
+
+
+
);
}
@@ -126,66 +126,10 @@ function TasksPageShell({ statsTasks = [], projects = [], onAddProject = null, c
return (
-
-
+
+
{children}
-
-
- {projectTabs.map(t => (
-
- ))}
- {onAddProject && (
-
-
-
- )}
-
-
- {sortedProjects.length === 0 ? (
- No projects
- ) : (
-
-
-
-
-
-
-
-
- Name
- Status
-
-
-
- {sortedProjects.map(p => (
-
- |
- {p.name}
- |
-
-
-
-
-
- {projectCompletionPct(p)}%
-
- |
-
- ))}
-
-
-
- )}
-
-
);
@@ -214,8 +158,11 @@ export default function RequestsPage() {
});
const [error, setError] = useState('');
const [loadError, setLoadError] = useState(false);
- const [activeTab, setActiveTab] = useState('new_requests');
+ const [activeTab, setActiveTab] = useState('tasks');
const [filterCompany, setFilterCompany] = useState('');
+ const [filterStatus, setFilterStatus] = useState('all');
+ const [filterRevision, setFilterRevision] = useState('all');
+ const [filterType, setFilterType] = useState('all');
const [filterProject, setFilterProject] = useState('');
const { sortKey, sortDir, toggle, sort } = useSortable('status', 'asc');
@@ -259,33 +206,18 @@ export default function RequestsPage() {
try {
setError(''); setLoadError(false);
- const { scopedProjectIds, scopedTaskIds } = await withTimeout(
- resolveScopedWorkIds(currentUser, { isClient, isExternal }),
- 10000,
- 'Tasks scope'
- );
-
+ // Scope is enforced server-side by RLS (team sees all; client = has_company_access;
+ // external = project_members). No need to pre-fetch scoped IDs — that was a
+ // redundant serial round trip. Query directly and let RLS filter.
const tasksQ = supabase.from('tasks')
.select('id, title, status, current_version, project_id, assigned_name, assigned_to, invoiced, completed_at, submitted_at, assignee:profiles!assigned_to(avatar_url)')
.order('submitted_at', { ascending: false });
- if (!isTeam) {
- if ((scopedProjectIds || []).length > 0) tasksQ.in('project_id', scopedProjectIds);
- else tasksQ.eq('id', '__none__');
- }
const projectsQ = supabase.from('projects').select('id, name, status, company_id, company:companies(id, name)');
- if (!isTeam) {
- if ((scopedProjectIds || []).length > 0) projectsQ.in('id', scopedProjectIds);
- else projectsQ.eq('id', '__none__');
- }
const subsQ = supabase.from('submissions')
.select('id, task_id, submitted_at, submitted_by, submitted_by_name, is_hot, service_type, deadline, version_number, type')
.order('submitted_at', { ascending: false });
- if (!isTeam) {
- if ((scopedTaskIds || []).length > 0) subsQ.in('task_id', scopedTaskIds);
- else subsQ.eq('id', '__none__');
- }
const [{ data: subs }, { data: t }, { data: p }, { data: co }] = await withTimeout(
Promise.all([subsQ, tasksQ, projectsQ, supabase.from('companies').select('id, name')]),
@@ -294,26 +226,22 @@ export default function RequestsPage() {
if (cancelled) return;
const allSubs = subs || [];
- const submitterIds = [...new Set(allSubs.map((submission) => submission.submitted_by).filter(Boolean))];
- let submitterProfiles = [];
- if (submitterIds.length > 0) {
- const { data: profileRows } = await withTimeout(
- supabase.from('profiles').select('id, name').in('id', submitterIds),
- 10000,
- 'Tasks submitter profiles load'
- );
- submitterProfiles = profileRows || [];
- }
- const hydratedSubs = mergeSubmissionDisplayNames(allSubs, submitterProfiles);
- let deliveryRows = [];
- if (hydratedSubs.length > 0) {
- const { data: delRows } = await withTimeout(
- supabase.from('deliveries').select('id, submission_id, version_number, sent_at').in('submission_id', hydratedSubs.map(sub => sub.id)),
- 10000,
- 'Tasks deliveries load'
- );
- deliveryRows = delRows || [];
- }
+ // Resolve submitter names + deliveries with light follow-up queries (faster than
+ // a nested PostgREST embed, which is very slow on this compute tier).
+ const submitterIds = [...new Set(allSubs.map(s => s.submitted_by).filter(Boolean))];
+ const [{ data: profileRows }, { data: delRows }] = await withTimeout(
+ Promise.all([
+ submitterIds.length > 0
+ ? supabase.from('profiles').select('id, name').in('id', submitterIds)
+ : Promise.resolve({ data: [] }),
+ allSubs.length > 0
+ ? supabase.from('deliveries').select('id, submission_id, version_number, sent_at').in('submission_id', allSubs.map(s => s.id))
+ : Promise.resolve({ data: [] }),
+ ]),
+ 15000, 'Tasks page details'
+ );
+ const hydratedSubs = mergeSubmissionDisplayNames(allSubs, profileRows || []);
+ const deliveryRows = delRows || [];
setProjects(p || []);
setTasks(t || []);
@@ -572,34 +500,70 @@ export default function RequestsPage() {
const doneStatuses = new Set(['client_approved', 'invoiced', 'paid']);
const tabs = [
- { id: 'all', label: 'All' },
- { id: 'new_requests', label: 'New Requests' },
- { id: 'revisions', label: 'Revisions' },
- { id: 'in_progress', label: 'In Progress' },
- { id: 'on_hold', label: 'On Hold' },
- { id: 'client_review', label: 'In Review' },
- { id: 'completed', label: 'Completed' },
+ { id: 'tasks', label: 'Tasks' },
+ { id: 'completed', label: 'Completed' },
];
const tabCounts = {
- all: filteredRows.length,
- new_requests: filteredRows.filter(r => r.status === 'not_started' && Number(r.version || 0) === 0).length,
- revisions: filteredRows.filter(r => r.status === 'not_started' && Number(r.version || 0) >= 1).length,
- in_progress: filteredRows.filter(r => r.status === 'in_progress').length,
- on_hold: filteredRows.filter(r => r.status === 'on_hold').length,
- client_review: filteredRows.filter(r => r.status === 'client_review').length,
+ tasks: filteredRows.filter(r => !doneStatuses.has(r.status)).length,
completed: filteredRows.filter(r => doneStatuses.has(r.status)).length,
};
- const tabRows = activeTab === 'all' ? filteredRows
- : activeTab === 'completed' ? filteredRows.filter(r => doneStatuses.has(r.status))
- : activeTab === 'new_requests' ? filteredRows.filter(r => r.status === 'not_started' && Number(r.version || 0) === 0)
- : activeTab === 'revisions' ? filteredRows.filter(r => r.status === 'not_started' && Number(r.version || 0) >= 1)
- : filteredRows.filter(r => r.status === activeTab);
+ const tabRowsBase = activeTab === 'completed'
+ ? filteredRows.filter(r => doneStatuses.has(r.status))
+ : filteredRows.filter(r => !doneStatuses.has(r.status));
+ let tabRows = tabRowsBase;
+ if (filterStatus !== 'all') tabRows = tabRows.filter(r => r.status === filterStatus);
+ if (filterRevision === 'new') tabRows = tabRows.filter(r => Number(r.version || 0) === 0);
+ else if (filterRevision === 'revision') tabRows = tabRows.filter(r => Number(r.version || 0) >= 1);
+ if (filterType !== 'all') tabRows = tabRows.filter(r => (r.serviceType || '') === filterType);
+
+ const REVISION_FILTER_OPTIONS = [
+ { value: 'all', label: 'All' },
+ { value: 'new', label: 'New (R00)' },
+ { value: 'revision', label: 'Revisions (R1+)' },
+ ];
+
+ const PROJECT_FILTER_OPTIONS = [
+ { value: '', label: 'All Projects' },
+ ...projects
+ .filter(p => !filterCompany || p.company_id === filterCompany)
+ .slice()
+ .sort((a, b) => (a.name || '').localeCompare(b.name || ''))
+ .map(p => ({ value: p.id, label: p.name })),
+ ];
+
+ const TYPE_FILTER_OPTIONS = [
+ { value: 'all', label: 'All Types' },
+ ...[...new Set(tabRowsBase.map(r => r.serviceType).filter(Boolean))]
+ .sort((a, b) => a.localeCompare(b))
+ .map(t => ({ value: t, label: t })),
+ ];
+
+ const COMPANY_FILTER_OPTIONS = [
+ { value: '', label: 'All Companies' },
+ ...filterableCompanies.map(c => ({ value: c.id, label: c.name })),
+ ];
+
+ const STATUS_FILTER_OPTIONS = activeTab === 'completed'
+ ? [
+ { value: 'all', label: 'All Statuses' },
+ { value: 'client_approved', label: 'Approved' },
+ { value: 'invoiced', label: 'Invoiced' },
+ { value: 'paid', label: 'Paid' },
+ ]
+ : [
+ { value: 'all', label: 'All Statuses' },
+ { value: 'not_started', label: 'Not Started' },
+ { value: 'in_progress', label: 'In Progress' },
+ { value: 'on_hold', label: 'On Hold' },
+ { value: 'client_review', label: 'In Review' },
+ ];
const sortedRows = sort(tabRows, (row, key) => {
if (key === 'title') return row.title || '';
if (key === 'project') return projects.find(p => p.id === row.projectId)?.name || '';
+ if (key === 'company') return projects.find(p => p.id === row.projectId)?.company?.name || '';
if (key === 'serviceType') return row.serviceType || '';
if (key === 'revision') return row.version ?? 0;
if (key === 'deadline') return row.deadline || '';
@@ -622,6 +586,9 @@ export default function RequestsPage() {
const project = projects.find(p => p.id === row.projectId);
return (
+ |
+
+ |
{`R${String(row.version).padStart(2, '0')}`}
|
@@ -640,15 +607,15 @@ export default function RequestsPage() {
return ;
})()}
-
- {row.isHot ? 'HOT' : 'NO'}
- |
{row.serviceType}
|
{fmtShortDate(row.deadline, 'Not specified')}
|
+
+ {project?.company?.name || '—'}
+ |
);
};
@@ -724,7 +691,7 @@ export default function RequestsPage() {
{/* Controls bar: tabs left, filters + actions right */}
{tabs.map(t => (
- |