1import { useEffect, useState } from 'react';
2import { useLocation, useNavigate, useParams } from 'react-router-dom';
3import MainLayout from '../components/Layout/MainLayout';
4// import removed: TenantSelector
5import './PurchaseOrderForm.css';
6import { apiFetch } from '../lib/api';
8export default function PurchaseOrderForm() {
9 const navigate = useNavigate();
10 const { id } = useParams();
11 const location = useLocation();
12 const prefillLines = location.state?.lines || [];
13 const [selectedTenantId, setSelectedTenantId] = useState('');
15 const [header, setHeader] = useState({
22 related_ticket_id: '',
23 related_customer_id: ''
25 const [lines, setLines] = useState(prefillLines.length ? prefillLines.map(l => ({ sku: l.sku || '', description: l.description || l.name || '', qty_ordered: l.needed_qty || 1, unit_price: l.price_retail || 0, product_id: l.product_id })) : [{ sku: '', description: '', qty_ordered: 1, unit_price: 0 }]);
26 const [loading, setLoading] = useState(false);
27 const [loadingData, setLoadingData] = useState(false);
28 const [error, setError] = useState(null);
32 }, [id, selectedTenantId]);
34 const getTenantHeaders = async (baseHeaders = {}) => {
35 const headers = { ...baseHeaders };
36 if (selectedTenantId) {
37 const token = localStorage.getItem('token');
38 const tenantRes = await apiFetch('/tenants', {
40 'Authorization': `Bearer ${token}`,
41 'X-Tenant-Subdomain': 'admin'
45 const data = await tenantRes.json();
46 const tenants = Array.isArray(data) ? data : (data.tenants || []);
47 const tenant = tenants.find(t => t.tenant_id === parseInt(selectedTenantId));
49 headers['X-Tenant-Subdomain'] = tenant.subdomain;
56 async function fetchPO() {
59 const token = localStorage.getItem('token');
60 const url = `/purchase-orders/${id}`;
61 const res = await apiFetch(url, { headers: { Authorization: `Bearer ${token}` } });
62 if (!res.ok) throw new Error('Failed to load');
63 const data = await res.json();
64 // Auto-select the tenant if the PO belongs to a different tenant
65 if (data.purchase_order.tenant_id && !selectedTenantId) {
66 setSelectedTenantId(String(data.purchase_order.tenant_id));
69 po_number: data.purchase_order.po_number || '',
70 supplier: data.purchase_order.supplier || '',
71 bill_to: data.purchase_order.bill_to || '',
72 ship_to: data.purchase_order.ship_to || '',
73 internal_notes: data.purchase_order.internal_notes || '',
74 status: data.purchase_order.status || 'draft',
75 related_ticket_id: data.purchase_order.related_ticket_id || '',
76 related_customer_id: data.purchase_order.related_customer_id || ''
78 setLines((data.lines || []).map(l => ({
79 po_line_id: l.po_line_id,
80 product_id: l.product_id,
82 description: l.description || '',
83 qty_ordered: l.qty_ordered || 1,
84 unit_price: l.unit_price || 0
88 setError(err.message || 'Failed to load');
90 setLoadingData(false);
94 function updateLine(idx, patch) {
95 setLines(prev => prev.map((l, i) => i === idx ? { ...l, ...patch } : l));
99 setLines(prev => [...prev, { sku: '', description: '', qty_ordered: 1, unit_price: 0 }]);
102 function removeLine(idx) {
103 setLines(prev => prev.filter((_, i) => i !== idx));
106 async function save() {
109 const token = localStorage.getItem('token');
110 const headers = await getTenantHeaders({
111 'Content-Type': 'application/json',
112 Authorization: `Bearer ${token}`
114 const method = id ? 'PUT' : 'POST';
115 const url = id ? `/purchase-orders/${id}` : '/purchase-orders';
118 // create header + lines
119 const res = await apiFetch(url, {
122 body: JSON.stringify({ ...header, lines })
124 if (!res.ok) throw new Error('Save failed');
127 const res = await apiFetch(url, {
130 body: JSON.stringify(header)
132 if (!res.ok) throw new Error('Save failed');
135 navigate('/purchase-orders');
137 setError(err.message || 'Save error');
143 const calculateTotal = () => {
144 return lines.reduce((sum, line) => sum + ((line.qty_ordered || 0) * (line.unit_price || 0)), 0);
149 <div className="page-content">
150 <div className="page-header">
151 <h2>{id ? 'Edit Purchase Order' : 'New Purchase Order'}</h2>
152 <div className="header-actions">
153 <button className="btn btn-secondary" onClick={() => navigate('/purchase-orders')}>
154 <span className="material-symbols-outlined">arrow_back</span>
160 {error && <div className="alert alert-error">{error}</div>}
163 <div className="loading-state">
164 <span className="material-symbols-outlined spinning">progress_activity</span>
165 <p>Loading purchase order...</p>
168 <div className="po-form-container">
169 <div className="form-card">
170 <div className="card-header">
172 <span className="material-symbols-outlined">receipt_long</span>
173 Purchase Order Information
176 <div className="card-body">
177 <div className="form-row">
178 <div className="form-group">
179 <label htmlFor="po_number">PO Number</label>
183 value={header.po_number}
184 onChange={e => setHeader(h => ({ ...h, po_number: e.target.value }))}
185 placeholder="Auto-generated or manual"
188 <div className="form-group">
189 <label htmlFor="supplier">Supplier *</label>
193 value={header.supplier}
194 onChange={e => setHeader(h => ({ ...h, supplier: e.target.value }))}
195 placeholder="Supplier name"
201 <div className="form-row">
202 <div className="form-group">
203 <label htmlFor="status">Status</label>
206 value={header.status}
207 onChange={e => setHeader(h => ({ ...h, status: e.target.value }))}
209 <option value="draft">Draft</option>
210 <option value="submitted">Submitted</option>
211 <option value="received">Received</option>
212 <option value="cancelled">Cancelled</option>
215 <div className="form-group">
216 <label htmlFor="related_ticket_id">Related Ticket</label>
218 id="related_ticket_id"
220 value={header.related_ticket_id}
221 onChange={e => setHeader(h => ({ ...h, related_ticket_id: e.target.value }))}
222 placeholder="Ticket ID (optional)"
229 <div className="form-card">
230 <div className="card-header">
232 <span className="material-symbols-outlined">location_on</span>
236 <div className="card-body">
237 <div className="form-row">
238 <div className="form-group">
239 <label htmlFor="bill_to">Bill To Address</label>
242 value={header.bill_to}
243 onChange={e => setHeader(h => ({ ...h, bill_to: e.target.value }))}
244 placeholder="Company Name Street Address City, State ZIP"
248 <div className="form-group">
249 <label htmlFor="ship_to">Ship To Address</label>
252 value={header.ship_to}
253 onChange={e => setHeader(h => ({ ...h, ship_to: e.target.value }))}
254 placeholder="Warehouse/Location Street Address City, State ZIP"
260 <div className="form-group">
261 <label htmlFor="related_customer_id">Related Customer (for dropship)</label>
263 id="related_customer_id"
265 value={header.related_customer_id}
266 onChange={e => setHeader(h => ({ ...h, related_customer_id: e.target.value }))}
267 placeholder="Customer ID (optional)"
271 <div className="form-group">
272 <label htmlFor="internal_notes">Internal Notes</label>
275 value={header.internal_notes}
276 onChange={e => setHeader(h => ({ ...h, internal_notes: e.target.value }))}
277 placeholder="Notes for internal staff only"
284 <div className="form-card">
285 <div className="card-header">
287 <span className="material-symbols-outlined">shopping_cart</span>
290 <button className="btn btn-sm" onClick={addLine} type="button">
291 <span className="material-symbols-outlined">add</span>
295 <div className="card-body">
296 <div className="line-items-table">
297 <table className="data-table">
309 {lines.map((l, idx) => (
315 onChange={e => updateLine(idx, { sku: e.target.value })}
322 value={l.description}
323 onChange={e => updateLine(idx, { description: e.target.value })}
324 placeholder="Item description"
331 value={l.qty_ordered}
332 onChange={e => updateLine(idx, { qty_ordered: parseInt(e.target.value || 0, 10) })}
340 onChange={e => updateLine(idx, { unit_price: parseFloat(e.target.value || 0) })}
344 <td className="line-total">
345 ${Number((l.qty_ordered || 0) * (l.unit_price || 0)).toFixed(2)}
349 className="btn btn-icon btn-danger"
350 onClick={() => removeLine(idx)}
354 <span className="material-symbols-outlined">delete</span>
361 <tr className="total-row">
362 <td colSpan="4" className="text-right"><strong>Total:</strong></td>
363 <td className="total-amount">${calculateTotal().toFixed(2)}</td>
372 <div className="form-actions">
373 <button className="btn btn-primary" onClick={save} disabled={loading || !header.supplier}>
376 <span className="material-symbols-outlined spinning">progress_activity</span>
381 <span className="material-symbols-outlined">save</span>
382 {id ? 'Update Purchase Order' : 'Create Purchase Order'}
386 <button className="btn btn-secondary" onClick={() => navigate('/purchase-orders')} disabled={loading}>