mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-09-30 07:43:14 +00:00
Extract shared constants and format utils, wire admin dashboard filters
Move STATUS_COLORS and PRIORITY_COLORS to constants/index.ts and formatCurrency/formatDate/formatDateTime to lib/format.ts — previously duplicated across 5 and 4 files respectively. AdminDashboard: Wire Category and Priority filter dropdowns to usePaginatedList extraParams (were no-op onChange handlers). ProposalDetailPage: Add 'Revised' to STATUS_ORDER so the stepper renders correctly for revised proposals.
This commit is contained in:
parent
4d72b63547
commit
5e89e42e6a
5 changed files with 51 additions and 67 deletions
|
|
@ -19,3 +19,17 @@ export const STORAGE_KEY_SIDEBAR = 'sidebarOpen';
|
|||
export const PROPOSAL_STATUSES = ['Draft', 'InReview', 'Approved', 'Sent', 'Revised'] as const;
|
||||
export const SERVICE_CATEGORIES = ['HVAC', 'Plumbing', 'Electrical', 'General', 'Renovation'] as const;
|
||||
export const PRIORITIES = ['Standard', 'Urgent', 'Emergency'] as const;
|
||||
|
||||
export const STATUS_COLORS: Record<string, 'default' | 'info' | 'warning' | 'success' | 'error'> = {
|
||||
Draft: 'default',
|
||||
InReview: 'info',
|
||||
Approved: 'success',
|
||||
Sent: 'success',
|
||||
Revised: 'warning',
|
||||
};
|
||||
|
||||
export const PRIORITY_COLORS: Record<string, 'default' | 'warning' | 'error'> = {
|
||||
Standard: 'default',
|
||||
Urgent: 'warning',
|
||||
Emergency: 'error',
|
||||
};
|
||||
|
|
|
|||
18
web/src/lib/format.ts
Normal file
18
web/src/lib/format.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
export function formatCurrency(amount: number): string {
|
||||
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);
|
||||
}
|
||||
|
||||
export function formatDate(iso: string): string {
|
||||
return new Date(iso).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
}
|
||||
|
||||
export function formatDateTime(iso: string | null): string {
|
||||
if (!iso) return '-';
|
||||
return new Date(iso).toLocaleString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
});
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import {
|
||||
|
|
@ -28,29 +29,8 @@ import { usePaginatedList } from '../../../hooks/usePaginatedList';
|
|||
import { proposalsApi, type ProposalListItem } from '../../../lib/api/proposals';
|
||||
import { adminApi, type DashboardStats } from '../../../lib/api/admin';
|
||||
import { QUERY_KEYS } from '../../../constants/queryKeys';
|
||||
import { SERVICE_CATEGORIES, PRIORITIES } from '../../../constants';
|
||||
|
||||
const STATUS_COLORS: Record<string, 'default' | 'info' | 'warning' | 'success'> = {
|
||||
Draft: 'default',
|
||||
InReview: 'info',
|
||||
Approved: 'success',
|
||||
Sent: 'success',
|
||||
Revised: 'warning',
|
||||
};
|
||||
|
||||
const PRIORITY_COLORS: Record<string, 'default' | 'warning' | 'error'> = {
|
||||
Standard: 'default',
|
||||
Urgent: 'warning',
|
||||
Emergency: 'error',
|
||||
};
|
||||
|
||||
function formatCurrency(amount: number): string {
|
||||
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);
|
||||
}
|
||||
|
||||
function formatDate(iso: string): string {
|
||||
return new Date(iso).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
}
|
||||
import { SERVICE_CATEGORIES, PRIORITIES, STATUS_COLORS, PRIORITY_COLORS } from '../../../constants';
|
||||
import { formatCurrency, formatDate } from '../../../lib/format';
|
||||
|
||||
function StatCard({ icon, label, value, color }: { icon: React.ReactNode; label: string; value: string; color: string }) {
|
||||
return (
|
||||
|
|
@ -76,6 +56,9 @@ export default function AdminDashboard() {
|
|||
queryFn: adminApi.getDashboard,
|
||||
});
|
||||
|
||||
const [categoryFilter, setCategoryFilter] = useState('');
|
||||
const [priorityFilter, setPriorityFilter] = useState('');
|
||||
|
||||
const {
|
||||
rows,
|
||||
search,
|
||||
|
|
@ -87,7 +70,10 @@ export default function AdminDashboard() {
|
|||
totalCount,
|
||||
loading,
|
||||
err,
|
||||
} = usePaginatedList<ProposalListItem>(proposalsApi.getAll);
|
||||
} = usePaginatedList<ProposalListItem>(proposalsApi.getAll, {
|
||||
...(categoryFilter && { serviceCategory: categoryFilter }),
|
||||
...(priorityFilter && { priority: priorityFilter }),
|
||||
});
|
||||
|
||||
return (
|
||||
<Box>
|
||||
|
|
@ -167,9 +153,9 @@ export default function AdminDashboard() {
|
|||
size="small"
|
||||
select
|
||||
label="Category"
|
||||
value=""
|
||||
value={categoryFilter}
|
||||
sx={{ width: 160 }}
|
||||
onChange={() => {}}
|
||||
onChange={(e) => setCategoryFilter(e.target.value)}
|
||||
>
|
||||
<MenuItem value="">All</MenuItem>
|
||||
{SERVICE_CATEGORIES.map((c) => (
|
||||
|
|
@ -180,9 +166,9 @@ export default function AdminDashboard() {
|
|||
size="small"
|
||||
select
|
||||
label="Priority"
|
||||
value=""
|
||||
value={priorityFilter}
|
||||
sx={{ width: 140 }}
|
||||
onChange={() => {}}
|
||||
onChange={(e) => setPriorityFilter(e.target.value)}
|
||||
>
|
||||
<MenuItem value="">All</MenuItem>
|
||||
{PRIORITIES.map((p) => (
|
||||
|
|
|
|||
|
|
@ -16,30 +16,10 @@ import {
|
|||
} from '@mui/material';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import { proposalsApi, type ProposalDetail } from '../../../lib/api/proposals';
|
||||
import { STATUS_COLORS } from '../../../constants';
|
||||
import { formatCurrency, formatDateTime } from '../../../lib/format';
|
||||
|
||||
const STATUS_ORDER = ['Draft', 'InReview', 'Approved', 'Sent'];
|
||||
const STATUS_COLORS: Record<string, 'default' | 'info' | 'warning' | 'success' | 'error'> = {
|
||||
Draft: 'default',
|
||||
InReview: 'info',
|
||||
Approved: 'success',
|
||||
Sent: 'success',
|
||||
Revised: 'warning',
|
||||
};
|
||||
|
||||
function formatCurrency(amount: number): string {
|
||||
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);
|
||||
}
|
||||
|
||||
function formatDateTime(iso: string | null): string {
|
||||
if (!iso) return '-';
|
||||
return new Date(iso).toLocaleString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
});
|
||||
}
|
||||
const STATUS_ORDER = ['Draft', 'InReview', 'Approved', 'Sent', 'Revised'];
|
||||
|
||||
function InfoRow({ label, value }: { label: string; value: React.ReactNode }) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -21,22 +21,8 @@ import SearchIcon from '@mui/icons-material/Search';
|
|||
import AddCircleIcon from '@mui/icons-material/AddCircle';
|
||||
import { usePaginatedList } from '../../../hooks/usePaginatedList';
|
||||
import { proposalsApi, type ProposalListItem } from '../../../lib/api/proposals';
|
||||
|
||||
const STATUS_COLORS: Record<string, 'default' | 'info' | 'warning' | 'success' | 'error'> = {
|
||||
Draft: 'default',
|
||||
InReview: 'info',
|
||||
Approved: 'success',
|
||||
Sent: 'success',
|
||||
Revised: 'warning',
|
||||
};
|
||||
|
||||
function formatCurrency(amount: number): string {
|
||||
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);
|
||||
}
|
||||
|
||||
function formatDate(iso: string): string {
|
||||
return new Date(iso).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
}
|
||||
import { STATUS_COLORS } from '../../../constants';
|
||||
import { formatCurrency, formatDate } from '../../../lib/format';
|
||||
|
||||
export default function ProposalListPage() {
|
||||
const navigate = useNavigate();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue