1import { useEffect, useState } from 'react';
2import { useNavigate } from 'react-router-dom';
3import { apiFetch } from '../lib/api';
5import MainLayout from '../components/Layout/MainLayout';
6// import removed: TenantSelector
8export default function PurchaseOrders() {
9 const token = localStorage.getItem('token');
13 const payload = token.split('.')[1];
14 const decoded = JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/')));
15 tenantId = decoded.tenant_id || decoded.tenantId || '';
18 const navigate = useNavigate();
19 const [items, setItems] = useState([]);
20 const [total, setTotal] = useState(0);
21 const [page, setPage] = useState(1);
22 const [limit] = useState(25);
23 const [status, setStatus] = useState('');
24 const [search, setSearch] = useState('');
25 const [loading, setLoading] = useState(true);
26 const [error, setError] = useState(null);
27 const [isRootTenant, setIsRootTenant] = useState(false);
28 const [tenantMap, setTenantMap] = useState({});
29 const [isAdmin, setIsAdmin] = useState(false);
31 // Detect if user is admin
33 const token = localStorage.getItem('token');
36 const payload = token.split('.')[1];
37 const decoded = JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/')));
38 setIsAdmin(decoded.role === 'admin');
43 // Detect if user is root tenant (MSP)
45 const detectRootTenant = async () => {
47 const token = localStorage.getItem('token');
48 const res = await apiFetch('/tenants', {
49 headers: { Authorization: `Bearer ${token}` }
52 setIsRootTenant(true);
53 const data = await res.json();
55 data.tenants?.forEach(t => {
56 map[t.tenant_id] = t.name;
61 // Silently fail - 403 is expected for non-MSP users
69 }, [page, status, search, tenantId]);
72 }, [page, status, search, tenantId]);
74 async function fetchList(fetchTenantId = tenantId) {
77 const token = localStorage.getItem('token');
78 let url = '/purchase-orders';
79 const params = new URLSearchParams();
80 params.set('page', page);
81 params.set('limit', limit);
82 if (status) params.set('status', status);
83 if (search) params.set('search', search);
84 const queryString = params.toString();
85 if (queryString) url += `?${queryString}`;
86 const res = await apiFetch(url, { headers: { Authorization: `Bearer ${token}` } });
87 if (!res.ok) throw new Error('Failed to load');
88 const data = await res.json();
89 setItems(data.purchase_orders || []);
90 setTotal(data.total || 0);
93 setError(err.message || 'Failed to load');
99 async function handleDelete(po) {
100 if (!confirm(`Are you sure you want to delete purchase order ${po.po_number || 'this item'}? This action cannot be undone.`)) {
105 const token = localStorage.getItem('token');
106 const res = await apiFetch(`/purchase-orders/${po.purchase_order_id}`, {
108 headers: { Authorization: `Bearer ${token}` }
112 const data = await res.json().catch(() => ({}));
113 throw new Error(data.error || 'Failed to delete');
119 alert(`Delete failed: ${err.message}`);
126 <div className="page-content">
127 <div className="page-header">
128 <div className="header-content">
129 <h2>Purchase Orders</h2>
130 <div className="header-actions">
131 <div className="search-box">
132 <span className="material-symbols-outlined">search</span>
133 <input placeholder="Search by PO number or supplier" value={search} onChange={e => { setSearch(e.target.value); setPage(1); }} />
135 <select value={status} onChange={e => { setStatus(e.target.value); setPage(1); }}>
136 <option value="">All Statuses</option>
137 <option value="draft">Draft</option>
138 <option value="submitted">Submitted</option>
139 <option value="received">Received</option>
140 <option value="cancelled">Cancelled</option>
142 <button className="btn primary" onClick={() => navigate('/purchase-orders/new')}>
143 <span className="material-symbols-outlined">add</span>
150 {error && <div className="error-message">{error}</div>}
151 {loading ? <div className="loading">Loading...</div> : (
153 <table className="data-table">
157 {isRootTenant && <th>Tenant</th>}
160 <th>Related Customer</th>
167 <tr key={po.purchase_order_id}>
168 <td>{po.po_number || '-'}</td>
169 {isRootTenant && <td>{po.tenant_id === 1 ? 'Root Tenant' : (po.tenant_name || tenantMap[po.tenant_id] || '-')}</td>}
170 <td>{po.supplier || '-'}</td>
171 <td><span className={`status-badge status-${po.status}`}>{po.status}</span></td>
172 <td>{po.related_customer_id || '-'}</td>
173 <td>{new Date(po.created_at).toLocaleString()}</td>
175 <button className="btn" onClick={() => navigate(`/purchase-orders/${po.purchase_order_id}`)}>
176 <span className="material-symbols-outlined">visibility</span>
178 <button className="btn" onClick={() => navigate(`/purchase-orders/${po.purchase_order_id}/edit`)}>
179 <span className="material-symbols-outlined">edit</span>
184 onClick={() => handleDelete(po)}
185 style={{ color: '#f44336' }}
186 title="Delete (Admin Only)"
188 <span className="material-symbols-outlined">delete</span>
197 <div className="pagination">
198 <button className="btn" disabled={page <= 1} onClick={() => setPage(p => Math.max(1, p - 1))}>
199 <span className="material-symbols-outlined">navigate_before</span>
201 <span>Page {page} of {Math.ceil(total / limit) || 1}</span>
202 <button className="btn" disabled={items.length < limit} onClick={() => setPage(p => p + 1)}>
203 <span className="material-symbols-outlined">navigate_next</span>