declare global {interface Window {PRESS_BASE:string;PRESS_CSRF:string}}
'use client';
import {useState,useEffect} from 'react';
import {receiveOrder,useMaterials,websiteUrl} from './inventory';
import {catalogOptions,changeCatalog,namesForCategory,isTransferItemCategory} from './catalog';
import {categoriesOf,UNCATALOGUED,itemCategories} from './access';
import {Package,Layers,Scissors,Check,Plus,Search,ArrowUpRight,AlertTriangle,X,ImagePlus,Download,History,Menu,Shirt,ShoppingBag,Truck,Images,Trash2,RotateCcw,Printer,Folder,Users} from 'lucide-react';
type Item={id:string;name:string;kind:string;category:string;size:string;color:string;qty:number;cost:number;low:number;supplier:string;photo:string;notes:string;onOrder?:number;used?:number;supplierPartNumber?:string;myPartNumber?:string;transferCategoryId?:string;transferCategoryIds?:string[];sourceTransferIds?:string[]};
type Supplier={id:string;company:string;contact:string;address:string;phone:string;email:string;website:string};
const blankSupplier:Supplier={id:'',company:'',contact:'',address:'',phone:'',email:'',website:''};
type Photo={id:string;url:string;name:string;uploaded:string};
type Category={id:string;name:string};
type Member={password?:string;email:string;name:string;categoryIds:string[];disabled:number};
type Data={purgedPhotos?:string[];itemCategories?:string[];itemNames?:string[];transferCategories?:Category[];supplierProfiles?:Supplier[];suppliers?:string[];archivedPhotos?:string[];items:Item[];history:{id:string;text:string;date:string}[]};
const empty:Item={id:'',name:'',kind:'Blanks',category:'Shirts',size:'',color:'',qty:0,cost:0,low:5,supplier:'',photo:'',notes:'',onOrder:0,used:0,supplierPartNumber:'',myPartNumber:'',transferCategoryId:UNCATALOGUED,transferCategoryIds:[UNCATALOGUED]};
const uuid=()=>globalThis.crypto?.randomUUID?.()||Date.now().toString(36)+'-'+Math.random().toString(36).slice(2);
const money=(n:number)=>n.toLocaleString('en-US',{style:'currency',currency:'USD'});
function ReuseField({label,value,options,onChange,required=false,wide=false}:{label:string;value:string;options:string[];onChange:(v:string)=>void;required?:boolean;wide?:boolean}){
const [entering,setEntering]=useState(Boolean(value&&!options.includes(value)));
return <div className={wide?'reusefield wide':'reusefield'}><label>{label}<select aria-label={label} value={entering?'__new':value} required={required&&!entering} onChange={e=>{if(e.target.value==='__new'){setEntering(true);onChange('')}else{setEntering(false);onChange(e.target.value)}}}><option value="">{required?'Choose a saved name or add new…':'Select or add new…'}</option>{Array.from(new Set([...options,...value?[value]:[]])).filter(Boolean).map(v=><option key={v} value={v}>{v}</option>)}<option value="__new">＋ Enter new {label.toLowerCase()}…</option></select></label>{entering&&<input aria-label={'New '+label.toLowerCase()} autoFocus required={required} placeholder={'Enter '+label.toLowerCase()} value={value} onChange={e=>onChange(e.target.value)}/>}</div>
}
const unique=(values:string[])=>Array.from(new Map(values.map(v=>v.trim()).filter(Boolean).map(v=>[v.toLowerCase(),v])).values()).sort((a,b)=>a.localeCompare(b));
export default function Stockroom(){
const [data,setData]=useState<Data>({items:[],history:[]}),[revision,setRevision]=useState(0),[tab,setTab]=useState('Overview'),[search,setSearch]=useState(''),[filter,setFilter]=useState('All'),[lowOnly,setLowOnly]=useState(false),[edit,setEdit]=useState<Item|null>(null),[make,setMake]=useState(false),[busy,setBusy]=useState(false),[loaded,setLoaded]=useState(false),[message,setMessage]=useState(''),[activity,setActivity]=useState(false),[blank,setBlank]=useState(''),[transfer,setTransfer]=useState(''),[qty,setQty]=useState(1),[output,setOutput]=useState(''),[ready,setReady]=useState(true),[adjust,setAdjust]=useState<Item|null>(null),[delta,setDelta]=useState(1),[reason,setReason]=useState('Stock received');
const [makeCategory,setMakeCategory]=useState('All');
const [actor,setActor]=useState<{admin:boolean;email:string;name:string}>({admin:false,email:'',name:''});
const [categoriesOpen,setCategoriesOpen]=useState(false),[categoryDraft,setCategoryDraft]=useState<Category>({id:'',name:''}),[deleteCategory,setDeleteCategory]=useState<Category|null>(null),[categoryFilter,setCategoryFilter]=useState('All'),[usersOpen,setUsersOpen]=useState(false),[members,setMembers]=useState<Member[]>([]),[memberDraft,setMemberDraft]=useState<Member|null>(null),[memberBusy,setMemberBusy]=useState(false),[customOrder,setCustomOrder]=useState<{id:string;qty:number}|null>(null);
const [catalog,setCatalog]=useState<'category'|'name'|null>(null),[catalogOriginal,setCatalogOriginal]=useState(''),[catalogDraft,setCatalogDraft]=useState(''),[catalogDelete,setCatalogDelete]=useState<string|null>(null);
const [printView,setPrintView]=useState<{items:Item[];summary:string;date:string}|null>(null);
const [photoDetail,setPhotoDetail]=useState<Photo|null>(null),[emptyTrashConfirm,setEmptyTrashConfirm]=useState(false),[photoRefresh,setPhotoRefresh]=useState(0);
const [suppliersOpen,setSuppliersOpen]=useState(false),[supplierDraft,setSupplierDraft]=useState<Supplier|null>(null),[originalCompany,setOriginalCompany]=useState(''),[photosOpen,setPhotosOpen]=useState(false),[photoPicker,setPhotoPicker]=useState(false),[photos,setPhotos]=useState<Photo[]>([]),[photoLoading,setPhotoLoading]=useState(false),[photoSearch,setPhotoSearch]=useState(''),[showTrash,setShowTrash]=useState(false);
async function replacePhoto(file:File){if(!photoDetail)return;setBusy(true);try{const f=new FormData();f.append('file',file);const r=await fetch(photoDetail.url,{method:'PUT',body:f});if(!r.ok)throw Error(await r.text());setPhotoDetail({...photoDetail,name:file.name});setPhotoRefresh(Date.now());await loadPhotos();setMessage('Photo replaced everywhere it is used.')}catch(e:any){setMessage(e.message)}finally{setBusy(false)}}
async function emptyPhotoTrash(){setBusy(true);try{const r=await fetch('/api/photos',{method:'DELETE',headers:{'Content-Type':'application/json'},body:JSON.stringify({revision})});if(!r.ok){if(r.status===409)await load();throw Error(await r.text())}const j:any=await r.json();setData(j.data);setRevision(j.revision);setEmptyTrashConfirm(false);await loadPhotos();setMessage(j.pending?'Photos removed; some storage cleanup is pending. Empty Trash again to retry.':`Permanently deleted ${j.deleted} photos.`)}catch(e:any){setMessage(e.message)}finally{setBusy(false)}}
async function loadPhotos(){setPhotoLoading(true);try{const r=await fetch('/api/photos');if(!r.ok)throw Error('Could not load your photos.');const j:any=await r.json();setPhotos(j.photos)}catch(e:any){setMessage(e.message)}finally{setPhotoLoading(false)}}
function openPhotos(pick=false){setPhotoPicker(pick);setPhotosOpen(true);setShowTrash(false);setPhotoSearch('');setPhotoDetail(null);setEmptyTrashConfirm(false);loadPhotos()}
function openSupplier(company=''){const existing=data.supplierProfiles?.find(s=>s.company.toLowerCase()===company.toLowerCase());setOriginalCompany(company);setSupplierDraft(existing?{...existing}:{...blankSupplier,company});setSuppliersOpen(true)}
async function addSupplier(e:any){e.preventDefault();if(!supplierDraft||busy)return;const profile={...supplierDraft,id:supplierDraft.id||uuid(),company:supplierDraft.company.trim(),contact:supplierDraft.contact.trim(),address:supplierDraft.address.trim(),phone:supplierDraft.phone.trim(),email:supplierDraft.email.trim(),website:websiteUrl(supplierDraft.website)};if(!profile.company){setMessage('Company name is required.');return}if(supplierDraft.website.trim()&&!profile.website){setMessage('Please enter a valid website address.');return}if(supplierOptions.some(n=>n.toLowerCase()===profile.company.toLowerCase()&&n.toLowerCase()!==originalCompany.toLowerCase())){setMessage('That supplier already exists. Open its profile to edit it.');return}const profiles=(data.supplierProfiles||[]).filter(s=>s.id!==profile.id&&s.company.toLowerCase()!==originalCompany.toLowerCase());const next={...data,supplierProfiles:[...profiles,profile],suppliers:unique([...supplierOptions.filter(n=>n.toLowerCase()!==originalCompany.toLowerCase()),profile.company]),items:items.map(i=>originalCompany&&i.supplier.toLowerCase()===originalCompany.toLowerCase()?{...i,supplier:profile.company}:i)};if(await save(next,`${originalCompany?'Updated':'Added'} supplier: ${profile.company}`)){if(edit&&(!originalCompany||edit.supplier.toLowerCase()===originalCompany.toLowerCase()))setEdit({...edit,supplier:profile.company});setSupplierDraft(null);setOriginalCompany('')}}
async function trashPhoto(photo:Photo,restore=false){if(!restore&&(items.some(i=>i.photo===photo.url)||edit?.photo===photo.url)){setMessage('This photo is in use. Remove or replace it on those items first.');return}const archived=new Set(data.archivedPhotos||[]);restore?archived.delete(photo.id):archived.add(photo.id);await save({...data,archivedPhotos:Array.from(archived)},restore?'Restored photo: '+photo.name:'Moved photo to Trash: '+photo.name)}
async function load(){try{const r=await fetch('/api/stock');if(!r.ok)throw Error(await r.text());const j:any=await r.json();setData(j.data);if(j.actor)setActor(j.actor);setRevision(j.revision);setLoaded(true)}catch(e:any){setMessage(e.message)}}
useEffect(()=>{load()},[]);

async function save(next:Data,text:string){setBusy(true);try{next={...next,history:[{id:uuid(),text,date:new Date().toISOString()},...next.history].slice(0,1000)};const r=await fetch('/api/stock',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({data:next,revision})});if(!r.ok){const t=await r.text();if(r.status===409)await load();throw Error(t)}const j:any=await r.json();setData(next);setRevision(j.revision);setMessage('Saved · '+text);return true}catch(e:any){setMessage(e.message);return false}finally{setBusy(false)}}
const categories=data.transferCategories||[{id:UNCATALOGUED,name:'Uncatalogued'}];
const items=data.items,supplierOptions=unique([...(data.suppliers||[]),...(data.supplierProfiles||[]).map(s=>s.company),...data.items.map(i=>i.supplier)]),itemCategoryOptions=catalogOptions(data,'category'),nameOptions=catalogOptions(data,'name'),colorOptions=unique(data.items.map(i=>i.color)),sizeOptions=unique(data.items.map(i=>i.size)),low=items.filter(i=>i.qty<=i.low),total=items.reduce((s,i)=>s+i.qty,0),value=items.reduce((s,i)=>s+i.qty*i.cost,0);
const availableTransfers=items.filter(i=>i.kind==='Transfers'&&(makeCategory==='All'||(i.transferCategoryId||UNCATALOGUED)===makeCategory));
const shown=items.filter(i=>(tab==='Overview'||i.kind===tab)&&(!lowOnly||i.qty<=i.low)&&(filter==='All'||i.category===filter)&&(categoryFilter==='All'||itemCategories(i,items).includes(categoryFilter))&&[i.name,i.color,i.size,i.supplier,i.category,i.myPartNumber,i.supplierPartNumber].join(' ').toLowerCase().includes(search.toLowerCase()));
async function commitItem(draft:Item,received=false){if(busy)return;const exists=items.some(i=>i.id===draft.id);const item={...draft,name:draft.name.trim(),size:draft.size.trim(),color:draft.color.trim(),supplier:draft.supplier.trim(),id:draft.id||uuid()};if(!item.name){setMessage('Please enter an item name.');return}if(await save({...data,itemNames:unique([...nameOptions,item.name]),itemCategories:unique([...itemCategoryOptions,item.category]),items:exists?items.map(i=>i.id===item.id?item:i):[...items,item]},received?`Received ${draft.qty-(edit?.qty||0)} × ${item.name} · ${item.size} ${item.color}`:`${exists?'Updated':'Added'} ${item.name} · ${item.size||'One size'} ${item.color}`))setEdit(null)}
async function submit(e:any){e.preventDefault();if(edit)await commitItem(edit)}
async function receive(e:any){if(!edit||busy||!e.currentTarget.form.reportValidity())return;try{await commitItem(receiveOrder(edit),true)}catch(error:any){setMessage(error.message)}}
async function upload(file:File,assign=true){setBusy(true);try{const f=new FormData();f.append('file',file);const r=await fetch('/api/photos',{method:'POST',body:f});if(!r.ok)throw Error(await r.text());const j:any=await r.json();if(assign)setEdit(v=>v?{...v,photo:j.url}:v);await loadPhotos();setMessage('Photo uploaded and saved to your photo library.')}catch(e:any){setMessage(e.message)}finally{setBusy(false)}}
async function produce(e:any){e.preventDefault();const b=items.find(i=>i.id===blank),t=availableTransfers.find(i=>i.id===transfer);if(!b||!t||qty<1||!Number.isInteger(qty)||qty>b.qty||qty>t.qty){setMessage('Select a blank and transfer with enough stock.');return}let next=useMaterials(items,b.id,t.id,qty);if(ready){const existing=next.find(i=>i.kind==='Premade'&&i.name===output.trim()&&i.size===b.size&&i.color===b.color&&i.sourceTransferIds?.length===1&&i.sourceTransferIds[0]===t.id);next=existing?next.map(i=>i.id===existing.id?{...i,qty:i.qty+qty}:i):[...next,{...b,id:uuid(),kind:'Premade',name:output.trim(),qty,onOrder:0,used:0,sourceTransferIds:[t.id],transferCategoryIds:[t.transferCategoryId||UNCATALOGUED],supplierPartNumber:'',myPartNumber:'',cost:b.cost+t.cost,photo:'',supplier:'Made in-house',notes:`Blank: ${b.name}; transfer: ${t.name} (${t.size})`}]}if(await save({...data,itemNames:ready?unique([...nameOptions,output.trim()]):nameOptions,items:next},`Made ${qty} × ${output||b.name+' + '+t.name}${ready?' · added to premade stock':' · materials deducted'}`))setMake(false)}
async function setOnOrder(id:string,value:number){if(!Number.isSafeInteger(value)||value<0){setMessage('Enter a whole number of zero or more.');return}if(await save({...data,items:items.map(i=>i.id===id?{...i,onOrder:value}:i)},`Updated on order: ${items.find(i=>i.id===id)?.name} → ${value}`))setCustomOrder(null)}
async function receiveSupplier(company:string,id?:string){if(busy)return;const selected=items.filter(i=>i.supplier===company&&(!id||i.id===id)&&(i.onOrder??0)>0);if(!selected.length)return;const ids=new Set(selected.map(i=>i.id));await save({...data,items:items.map(i=>ids.has(i.id)?receiveOrder(i):i)},`Received ${selected.reduce((n,i)=>n+(i.onOrder??0),0)} items from ${company}${id?' · '+selected[0].name:' · all on order'}`)}
function openCatalog(field:'category'|'name'){setCatalog(field);setCatalogOriginal('');setCatalogDraft('');setCatalogDelete(null)}
async function saveCatalog(e:any,remove=false){e.preventDefault();if(!catalog||busy)return;try{const original=remove?catalogDelete!:catalogOriginal;const next=changeCatalog(data,catalog,original,catalogDraft,remove);if(await save(next,`${remove?'Removed':original?'Renamed':'Added'} ${catalog==='category'?'item category':'item / design name'}: ${remove?original:catalogDraft.trim()}`)){if(edit&&original&&edit[catalog].toLowerCase()===original.toLowerCase()&&!(remove&&catalog==='name'))setEdit({...edit,[catalog]:remove?'Other':catalogDraft.trim()});if(catalog==='category')setFilter('All');setCatalogOriginal('');setCatalogDraft('');setCatalogDelete(null)}}catch(e:any){setMessage(e.message)}}
async function saveCategory(e:any){e.preventDefault();const name=categoryDraft.name.trim();if(!name||categories.some(c=>c.name.toLowerCase()===name.toLowerCase()&&c.id!==categoryDraft.id)){setMessage('Choose a unique category name.');return}const category={id:categoryDraft.id||uuid(),name};if(await save({...data,transferCategories:categoryDraft.id?categories.map(c=>c.id===category.id?category:c):[...categories,category]},`${categoryDraft.id?'Renamed':'Added'} transfer category: ${name}`))setCategoryDraft({id:'',name:''})}
async function removeCategory(){if(!deleteCategory)return;const id=deleteCategory.id;if(await save({...data,transferCategories:categories.filter(c=>c.id!==id),items:items.map(i=>({...i,...i.transferCategoryId===id?{transferCategoryId:UNCATALOGUED}:{},...i.transferCategoryIds?.includes(id)?{transferCategoryIds:[...new Set(i.transferCategoryIds.map(c=>c===id?UNCATALOGUED:c))]}:{}}))},`Deleted category ${deleteCategory.name}; moved items to Uncatalogued`)){setDeleteCategory(null);setCategoryFilter('All');setCategoryDraft({id:'',name:''})}}
async function openUsers(){setUsersOpen(true);setMemberBusy(true);try{const r=await fetch('/api/users');if(!r.ok)throw Error(await r.text());const j:any=await r.json();setMembers(j.users)}catch(e:any){setMessage(e.message)}finally{setMemberBusy(false)}}
async function saveMember(e:any){e.preventDefault();if(!memberDraft)return;setMemberBusy(true);try{const r=await fetch('/api/users',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(memberDraft)});if(!r.ok)throw Error(await r.text());setMessage('User access saved. The user can now sign in with their username and password.');setMemberDraft(null);await openUsers()}catch(e:any){setMessage(e.message)}finally{setMemberBusy(false)}}
function supplierOrders(company:string){const supplierItems=items.filter(i=>i.supplier===company),ordered=supplierItems.reduce((n,i)=>n+(i.onOrder??0),0);return <section className="supplierorders"><div className="supplierordershead"><h3>Items from this supplier</h3>{actor.admin&&<button type="button" className="secondary" disabled={busy||!ordered} onClick={()=>receiveSupplier(company)}><Truck size={15}/> Receive all ({ordered})</button>}</div>{!supplierItems.length?<p className="hint">No items assigned yet. Choose this supplier on an item to add it here.</p>:<div className="tablewrap"><table><thead><tr><th>ITEM / VARIANT</th><th>ON HAND</th><th>ON ORDER</th>{actor.admin&&<><th>CHANGE ON ORDER</th><th>RECEIVE</th></>}</tr></thead><tbody>{supplierItems.map(i=><tr key={i.id}><td><strong>{i.name}</strong><small>{[i.size,i.color].filter(Boolean).join(' · ')}</small>{(i.myPartNumber||i.supplierPartNumber)&&<small>My #: {i.myPartNumber||'—'} · Supplier #: {i.supplierPartNumber||'—'}</small>}</td><td>{i.qty}</td><td>{i.onOrder??0}</td>{actor.admin&&<><td><select aria-label={'On order for '+i.name+' '+i.size+' '+i.color} disabled={busy} value={i.onOrder??0} onChange={e=>{if(e.target.value==='custom')setCustomOrder({id:i.id,qty:i.onOrder??0});else setOnOrder(i.id,Number(e.target.value))}}>{[...new Set([...Array.from({length:101},(_,n)=>n),250,500,1000,i.onOrder??0])].sort((a,b)=>a-b).map(n=><option key={n} value={n}>{n}</option>)}<option value="custom">Other amount…</option></select>{customOrder?.id===i.id&&<div className="customorder"><input aria-label="Custom order amount" type="number" min="0" step="1" value={customOrder.qty} onChange={e=>setCustomOrder({id:i.id,qty:Number(e.target.value)})}/><button className="textbutton" type="button" disabled={busy} onClick={()=>setOnOrder(i.id,customOrder.qty)}>Save amount</button></div>}</td><td><button type="button" className="secondary" disabled={busy||!(i.onOrder??0)} onClick={()=>receiveSupplier(company,i.id)}>Receive {i.onOrder??0}</button></td></>}</tr>)}</tbody></table></div>}</section>}
function openPrintView(){setPrintView({items:shown.map(i=>({...i})),summary:[tab,filter!=='All'?'Category: '+filter:'',search.trim()?'Search: '+search.trim():'',lowOnly?'Low stock only':'',categoryFilter!=='All'?'Transfer category: '+(categories.find(c=>c.id===categoryFilter)?.name||categoryFilter):''].filter(Boolean).join(' · '),date:new Date().toLocaleString()})}
function exportStock(){const cols=['kind','category','name','size','color','qty','onOrder','cost','inventoryValue','used','low','supplier','supplierPartNumber','myPartNumber','notes'];const csv=[cols.join(','),...items.map(i=>cols.map(c=>'"'+String(c==='inventoryValue'?(i.qty*i.cost).toFixed(2):(i as any)[c]??'').replaceAll('"','""')+'"').join(','))].join('\r\n');const url=URL.createObjectURL(new Blob([csv],{type:'text/csv'}));const a=document.createElement('a');a.href=url;a.download='press-tracker-inventory.csv';a.click();URL.revokeObjectURL(url)}
return <div className="app"><aside><a className="brand" href={window.PRESS_BASE+"/"}><span className="brandmark">pt</span><span>PRESS TRACKER<small>INVENTORY & MATERIALS</small></span></a><div className="navlabel">WORKSPACE</div><nav>{[['Overview',Package],['Blanks',Shirt],['Transfers',Layers],['Premade',ShoppingBag]].map(([label,Icon]:any)=><button key={label} className={tab===label?'active':''} onClick={()=>{setTab(label);setFilter('All');setCategoryFilter('All');setLowOnly(false)}}><Icon size={19}/>{label}<span>{label==='Overview'?items.length:items.filter(i=>i.kind===label).length}</span></button>)}</nav><div className="navlabel toolslabel">YOUR RESOURCES</div><div className="resourcebuttons"><button disabled={!loaded||busy} onClick={()=>setSuppliersOpen(true)}><Truck size={19}/> Suppliers</button><button disabled={!loaded||busy} onClick={()=>openPhotos()}><Images size={19}/> Photo library</button>{actor.admin&&<><button disabled={!loaded||busy} onClick={()=>openCatalog("category")}><Folder size={19}/> Item categories</button><button disabled={!loaded||busy} onClick={()=>openCatalog("name")}><Layers size={19}/> Item / design names</button><button disabled={!loaded||busy} onClick={()=>setCategoriesOpen(true)}><Folder size={19}/> Transfer categories</button><button disabled={!loaded||busy} onClick={openUsers}><Users size={19}/> Users</button></>}</div><div className="asidebottom"><span className="dot"/> One shared stockroom<p>Made for Jamie & Tim</p><small>Every design. Every storefront.</small><small className="release">Self-hosted 1.0.0 · Press Tracker</small></div></aside>
<div className="main"><header><span>YOUR WORKSPACE <span className="slash">/</span> <b>{tab}</b></span><div><span className="cloud">● {loaded?'Cloud inventory':'Connecting…'}</span><button className="icon" aria-label="Activity history" onClick={()=>setActivity(true)}><History size={20}/></button><a className="textbutton" href={window.PRESS_BASE+"/account.php"}>Account</a>{actor.admin&&<a className="textbutton" href={window.PRESS_BASE+"/backup.php"}>Backups</a>}<form action={window.PRESS_BASE+"/logout.php"} method="post"><input type="hidden" name="csrf" value={window.PRESS_CSRF}/><button className="textbutton">Sign out</button></form></div></header><main><div className="heading"><div><div className="eyebrow">A LITTLE ORDER BEHIND THE CREATIVITY</div><h1>{tab==='Overview'?'Your stock, at a glance.':tab==='Blanks'?'Ready for your next idea.':tab==='Transfers'?'Designs ready to press.':'Made. Pressed. Ready.'}</h1><p>{tab==='Overview'?'Everything you have. Everything you need to make.':'Keep every size, color, and quantity in its place.'}</p></div><div className="actions">{actor.admin&&<><button className="secondary" disabled={!loaded||busy} onClick={()=>{setMakeCategory('All');setTransfer('');setMake(true)}}><Scissors size={17}/> Make an item</button><button className="primary" disabled={!loaded||busy} onClick={()=>setEdit({...empty,kind:tab==='Overview'?'Blanks':tab,category:itemCategoryOptions.includes(tab==='Transfers'?'DTF transfers':'Shirts')?(tab==='Transfers'?'DTF transfers':'Shirts'):(itemCategoryOptions[0]||'Other')})}><Plus size={18}/> Add item</button></>}{!actor.admin&&<span className="viewonly">View-only access · assigned categories</span>}</div></div>
<div className="stats">{[[Package,'Items in stock',total,'Across all materials & finished goods'],[Layers,'Inventory value',money(value),'Based on your unit costs'],[AlertTriangle,'Running low',low.length,'Variants at or below their stock limit'],[Check,'Ready to sell',items.filter(i=>i.kind==='Premade').reduce((s,i)=>s+i.qty,0),'Finished items on hand']].map(([Icon,label,v,desc]:any)=><div className="stat" key={label}><div><span>{label}</span><Icon size={19}/></div><strong>{v}</strong><small>{desc}</small></div>)}</div>
{low.length>0&&<button className="alert" onClick={()=>{setLowOnly(!lowOnly);setTab('Overview');setFilter('All')}}><AlertTriangle size={18}/><span><b>{low.length} {low.length===1?'variant needs':'variants need'} a little attention.</b> Check your low-stock list before your next supply order.</span><ArrowUpRight size={19}/></button>}
<div className="mobiletools"><button className="secondary" disabled={!loaded||busy} onClick={()=>setSuppliersOpen(true)}><Truck size={16}/> Suppliers</button><button className="secondary" disabled={!loaded||busy} onClick={()=>openPhotos()}><Images size={16}/> Photo library</button>{actor.admin&&<><button className="secondary" onClick={()=>openCatalog("category")}>Item categories</button><button className="secondary" onClick={()=>openCatalog("name")}>Item / design names</button><button className="secondary" onClick={()=>setCategoriesOpen(true)}><Folder size={16}/> Categories</button><button className="secondary" onClick={openUsers}><Users size={16}/> Users</button></>}</div><section className="inventory"><div className="sectionhead"><div><h2>{lowOnly?'Low-stock inventory':tab==='Overview'?'Your inventory':tab}</h2><span>{shown.length} variants <i>·</i> Blank, transfer, and premade quantities stay separate</span><p className="filteredvalue" aria-live="polite">Filtered inventory value: <strong>{money(shown.reduce((total,item)=>total+item.qty*item.cost,0))}</strong></p></div><div className="inventoryactions"><button className="textbutton" disabled={!loaded} onClick={openPrintView}><Printer size={16}/> Print inventory</button><button className="textbutton" onClick={exportStock}><Download size={16}/> Export CSV</button></div></div><div className="toolbar"><label className="search"><Search size={18}/><input placeholder="Search items, colors, sizes, suppliers…" value={search} onChange={e=>setSearch(e.target.value)}/></label><select aria-label="Category filter" value={filter} onChange={e=>setFilter(e.target.value)}><option>All</option>{itemCategoryOptions.map(c=><option key={c}>{c}</option>)}</select><select aria-label="Transfer category filter" value={categoryFilter} onChange={e=>setCategoryFilter(e.target.value)}><option value="All">All transfer categories</option>{categories.map(c=><option key={c.id} value={c.id}>{c.name}</option>)}</select><button className={'filter '+(lowOnly?'selected':'')} onClick={()=>setLowOnly(!lowOnly)}>Low stock {low.length>0&&<b>{low.length}</b>}</button></div>
{!loaded?<div className="empty">Loading your stockroom…</div>:shown.length===0?<div className="empty"><div className="emptyicon"><Package size={32}/></div><h3>{items.length?'No matching items':'A fresh start for your stockroom.'}</h3><p>{items.length?'Try another search or filter.':'Add your first blank, transfer, or premade item. Your real inventory starts here.'}</p>{!items.length&&actor.admin&&<button className="primary" onClick={()=>setEdit({...empty,category:itemCategoryOptions.includes("Shirts")?"Shirts":(itemCategoryOptions[0]||"Other")})}><Plus size={17}/> Add your first item</button>}<small>No sample quantities to clear out.</small></div>:<div className="tablewrap"><table><thead><tr><th>ITEM / VARIANT</th><th>TYPE</th><th>SUPPLIER</th><th>UNIT COST</th><th>INVENTORY VALUE</th><th>ON HAND</th><th>ON ORDER</th><th></th></tr></thead><tbody>{shown.map(i=><tr key={i.id}><td><button className="itemname" onClick={()=>actor.admin&&setEdit({...i})}>{i.photo?<img src={window.PRESS_BASE+i.photo+"?v="+photoRefresh} alt={i.name}/>:<span className="placeholder">{i.kind==='Transfers'?<Layers size={23}/>:<Shirt size={23}/>}</span>}<span><b>{i.name}</b><small>{[i.category,i.size||'One size',i.color,i.kind==='Transfers'?(categories.find(c=>c.id===(i.transferCategoryId||UNCATALOGUED))?.name||'Uncatalogued'):''].filter(Boolean).join(' · ')}</small>{(i.myPartNumber||i.supplierPartNumber)&&<small>My #: {i.myPartNumber||'—'} · Supplier #: {i.supplierPartNumber||'—'}</small>}</span></button></td><td><span className={'badge '+i.kind.toLowerCase()}>{i.kind}</span>{i.kind==='Transfers'&&<small className="usedcount">Used: {i.used??0}</small>}</td><td>{i.supplier?<button className="textbutton supplierlink" onClick={()=>openSupplier(i.supplier)}>{i.supplier}</button>:'—'}</td><td>{money(i.cost)}</td><td className="itemvalue">{money(i.cost*i.qty)}</td><td><button disabled={!actor.admin} className={'quantity '+(i.qty<=i.low?'low':'')} onClick={()=>{setAdjust(i);setDelta(1);setReason('Stock received')}}>{i.qty} <small>{i.qty<=i.low?'Low stock':'in stock'}</small></button></td><td><span className="orderedcount">{i.onOrder??0}</span>{actor.admin&&(i.onOrder??0)>0&&<button className="textbutton" onClick={()=>setEdit({...i})}>Receive</button>}</td><td>{actor.admin&&<button className="textbutton" onClick={()=>setEdit({...i})}>Edit</button>}</td></tr>)}</tbody></table></div>}
<div className="tablefooter"><span><span className="dot"/> Changes saved securely online</span><button className="textbutton" onClick={load}>Refresh stock ↻</button></div></section><div className="bottomnote"><Scissors size={17}/><span>From blank to beautiful.<br/><small>Use “Make an item” to keep your materials in step with your creativity.</small></span></div></main></div>
{message&&<div className="toast" role="status">{message}<button aria-label="Dismiss notification" onClick={()=>setMessage('')}><X size={18}/></button></div>}
{edit&&<div className="overlay"><form className="modal" onSubmit={submit}><div className="modalhead"><div><span className="eyebrow">YOUR INVENTORY</span><h2>{edit.id?'Edit item':'Add an item'}</h2></div><button type="button" className="icon" onClick={()=>setEdit(null)} aria-label="Close"><X/></button></div><div className="formgrid"><label>Inventory type<select value={edit.kind} onChange={e=>setEdit({...edit,kind:e.target.value})}>{['Blanks','Transfers','Premade'].map(v=><option key={v}>{v}</option>)}</select></label><div><ReuseField key={edit.id+"category"} label="Category" required value={edit.category} options={itemCategoryOptions} onChange={category=>setEdit({...edit,category,name:edit.id?edit.name:""})}/><button type="button" className="textbutton" onClick={()=>openCatalog("category")}>Manage categories</button></div>{(edit.kind==='Transfers'||isTransferItemCategory(edit.category))&&<label className="wide">Transfer category<select value={edit.transferCategoryId||UNCATALOGUED} onChange={e=>setEdit({...edit,transferCategoryId:e.target.value,name:edit.id?edit.name:""})}>{categories.map(c=><option value={c.id} key={c.id}>{c.name}</option>)}</select></label>}{edit.kind==='Premade'&&<div className="wide"><label>Transfer categories for this finished item</label>{edit.sourceTransferIds?.length?<p className="hint">Linked to the original transfer(s): {edit.sourceTransferIds.map(id=>items.find(i=>i.id===id)?.name||'Uncatalogued').join(', ')}. Category access follows those transfers.</p>:<><p className="hint">Choose every transfer category used. Leave unassigned items in Uncatalogued.</p><div className="categorychecks">{categories.map(c=><label className="check" key={c.id}><input type="checkbox" checked={(edit.transferCategoryIds?.length?edit.transferCategoryIds:[UNCATALOGUED]).includes(c.id)} onChange={e=>{const ids=new Set(edit.transferCategoryIds?.length?edit.transferCategoryIds:[UNCATALOGUED]);e.target.checked?ids.add(c.id):ids.delete(c.id);setEdit({...edit,transferCategoryIds:ids.size?[...ids]:[UNCATALOGUED]})}}/>{c.name}</label>)}</div></>}</div>}<label>Supplier part number (optional)<input maxLength={200} value={edit.supplierPartNumber||''} onChange={e=>setEdit({...edit,supplierPartNumber:e.target.value})}/></label><label>My part number (optional)<input maxLength={200} value={edit.myPartNumber||''} onChange={e=>setEdit({...edit,myPartNumber:e.target.value})}/></label><div className="wide"><ReuseField key={edit.id+"name"+edit.category+(isTransferItemCategory(edit.category)?edit.transferCategoryId:"")} label="Item / design name" required value={edit.name} options={namesForCategory(data,edit.category,isTransferItemCategory(edit.category)?(edit.transferCategoryId||UNCATALOGUED):undefined)} onChange={name=>setEdit({...edit,name})}/><button type="button" className="textbutton" onClick={()=>openCatalog("name")}>Manage item / design names</button></div><ReuseField key={edit.id+"size"} label="Size" value={edit.size} options={sizeOptions} onChange={size=>setEdit({...edit,size})}/><ReuseField key={edit.id+"color"} label="Color" value={edit.color} options={colorOptions} onChange={color=>setEdit({...edit,color})}/><label>Quantity on hand<input type="number" min="0" step="1" required value={edit.qty} onChange={e=>setEdit({...edit,qty:Number(e.target.value)})}/></label><div className="orderfield"><label>Quantity on order<input type="number" min="0" step="1" required value={edit.onOrder??0} onChange={e=>setEdit({...edit,onOrder:Number(e.target.value)})}/></label><button type="button" className="secondary receivebutton" disabled={busy||!edit.id||!(edit.onOrder??0)} onClick={receive}><Truck size={15}/> Receive all on order</button><small>{!edit.id?'Save the item before receiving an order.':'Adds to on hand and clears on order.'}</small></div>{edit.kind==='Transfers'&&<label>Transfers used to date<input type="number" min="0" step="1" required value={edit.used??0} onChange={e=>setEdit({...edit,used:Number(e.target.value)})}/><small>Increases with “Make an item.” Set a starting total here if needed.</small></label>}<label>Unit cost ($)<input type="number" min="0" step="0.01" required value={edit.cost} onChange={e=>setEdit({...edit,cost:Number(e.target.value)})}/></label><label>Low-stock limit<input type="number" min="0" step="1" required value={edit.low} onChange={e=>setEdit({...edit,low:Number(e.target.value)})}/></label><div><label>Supplier<select aria-label="Supplier" value={edit.supplier} onChange={e=>setEdit({...edit,supplier:e.target.value})}><option value="">Select supplier…</option>{unique([...supplierOptions,edit.supplier]).map(v=><option value={v} key={v}>{v}</option>)}</select></label><button type="button" className="textbutton" onClick={()=>setSuppliersOpen(true)}><Plus size={14}/> Add supplier</button></div><label className="wide">Notes<textarea value={edit.notes} onChange={e=>setEdit({...edit,notes:e.target.value})} placeholder="Brand, style number, storage location…"/></label><div className="wide photofield">{edit.photo&&<div className="currentphoto"><img src={window.PRESS_BASE+edit.photo+"?v="+photoRefresh} alt="Selected item photo"/><button className="textbutton" type="button" onClick={()=>setEdit({...edit,photo:''})}>Remove from item</button></div>}<div className="photobuttons"><label className="upload"><ImagePlus size={22}/><span>Upload photo<small>JPG, PNG, WebP · up to 6 MB</small></span><input aria-label="Upload item photo" type="file" accept="image/jpeg,image/png,image/webp" disabled={busy} onChange={e=>{if(e.target.files?.[0])upload(e.target.files[0]);e.target.value=''}}/></label><button className="secondary" type="button" disabled={busy} onClick={()=>openPhotos(true)}><Images size={18}/> Choose saved photo</button></div></div></div><p className="hint">Add a separate item for each size and color combination.</p><div className="modalfoot">{edit.id&&<button type="button" className="textbutton" onClick={()=>setEdit({...edit,id:'',qty:0,onOrder:0,used:0})}>Duplicate variant</button>}<button type="submit" className="primary" disabled={busy}>{busy?'Saving…':'Save item'}</button></div></form></div>}
{suppliersOpen&&<div className="overlay resourceoverlay"><div className="modal suppliermodal" role="dialog" aria-label="Suppliers"><div className="modalhead"><div><span className="eyebrow">YOUR RESOURCES</span><h2>{supplierDraft?(originalCompany?'Supplier profile':'Add supplier'):'Suppliers'}</h2></div><button type="button" className="icon" aria-label="Close suppliers" onClick={()=>{setSuppliersOpen(false);setSupplierDraft(null)}}><X/></button></div>{supplierDraft?<form onSubmit={addSupplier}><fieldset className="profilefields" disabled={!actor.admin}><div className="formgrid"><label className="wide">Company name<input autoFocus required value={supplierDraft.company} onChange={e=>setSupplierDraft({...supplierDraft,company:e.target.value})}/></label><label>Contact name<input value={supplierDraft.contact} onChange={e=>setSupplierDraft({...supplierDraft,contact:e.target.value})}/></label><label>Phone number<input type="tel" value={supplierDraft.phone} onChange={e=>setSupplierDraft({...supplierDraft,phone:e.target.value})}/></label><label className="wide">Address<textarea value={supplierDraft.address} onChange={e=>setSupplierDraft({...supplierDraft,address:e.target.value})}/></label><label>Email address<input type="email" value={supplierDraft.email} onChange={e=>setSupplierDraft({...supplierDraft,email:e.target.value})}/></label><label>Website address<input placeholder="example.com" value={supplierDraft.website} onChange={e=>setSupplierDraft({...supplierDraft,website:e.target.value})}/></label></div></fieldset>{websiteUrl(supplierDraft.website)&&<a className="supplierwebsite" href={websiteUrl(supplierDraft.website)} target="_blank" rel="noopener noreferrer">Open supplier website <ArrowUpRight size={15}/></a>}<div className="modalfoot"><button type="button" className="textbutton" disabled={busy} onClick={()=>setSupplierDraft(null)}>Back to suppliers</button>{actor.admin&&<button type="submit" className="primary" disabled={busy}>Save supplier profile</button>}</div>{originalCompany&&supplierOrders(originalCompany)}</form>:<><p>Keep company and contact details together. Select a supplier to view or edit its profile.</p>{actor.admin&&<button className="primary addsupplier" disabled={busy} onClick={()=>openSupplier()}><Plus size={16}/> Add supplier</button>}<div className="supplierprofiles">{supplierOptions.length?supplierOptions.map(name=>{const profile=data.supplierProfiles?.find(s=>s.company===name);return <div className="suppliercard" key={name}><button className="suppliercardname" onClick={()=>openSupplier(name)}><Truck size={18}/><span><b>{name}</b><small>{profile?.contact||'Add contact information'} · {items.filter(i=>i.supplier===name).length} variants</small></span><span className="textbutton">{actor.admin?'View / edit':'View'}</span></button>{profile&&<div className="suppliercontact">{profile.address&&<p className="postaladdress">{profile.address}</p>}{profile.phone&&<a href={'tel:'+profile.phone}>{profile.phone}</a>}{profile.email&&<a href={'mailto:'+profile.email}>{profile.email}</a>}{websiteUrl(profile.website)&&<a className="supplierwebsite" href={websiteUrl(profile.website)} target="_blank" rel="noopener noreferrer">{profile.website}<ArrowUpRight size={14}/></a>}</div>}</div>}):<p className="hint">No suppliers added yet.</p>}</div></>}</div></div>}
{photosOpen&&<div className="overlay resourceoverlay"><div className="modal photomodal" role="dialog" aria-label="Photo library"><div className="modalhead"><div><span className="eyebrow">YOUR RESOURCES</span><h2>{photoPicker?'Choose a saved photo':'Photo library'}</h2></div><button type="button" className="icon" aria-label="Close photo library" onClick={()=>setPhotosOpen(false)}><X/></button></div><p>Upload once. Use the same photo for any number of items.</p><div className="phototools"><label className="search"><Search size={16}/><input aria-label="Search photos" placeholder="Search photos or linked items…" value={photoSearch} onChange={e=>setPhotoSearch(e.target.value)}/></label>{actor.admin&&<label className="primary uploadcompact"><Plus size={16}/> Upload photo<input aria-label="Upload library photo" type="file" accept="image/jpeg,image/png,image/webp" disabled={busy} onChange={e=>{if(e.target.files?.[0])upload(e.target.files[0],false);e.target.value=''}}/></label>}</div><div className="phototabs"><button type="button" className={!showTrash?'selected':''} onClick={()=>setShowTrash(false)}>All photos ({photos.filter(p=>!data.archivedPhotos?.includes(p.id)).length})</button><button type="button" className={showTrash?'selected':''} onClick={()=>setShowTrash(true)}><Trash2 size={14}/> Trash ({photos.filter(p=>data.archivedPhotos?.includes(p.id)).length})</button></div>{showTrash&&actor.admin&&<button className="secondary" disabled={busy||!(data.archivedPhotos?.length||data.purgedPhotos?.length)} onClick={()=>setEmptyTrashConfirm(true)}>Empty Trash</button>}{emptyTrashConfirm&&<div className="categoryconfirm"><p>Permanently delete all photos in Trash? This cannot be undone. Photos used by inventory are protected.</p><button className="secondary" disabled={busy} onClick={emptyPhotoTrash}>Permanently empty Trash</button><button className="textbutton" disabled={busy} onClick={()=>setEmptyTrashConfirm(false)}>Cancel</button></div>}{showTrash&&<p className="hint">Deleted photos stay in Trash so you can restore them. Restore a photo before reusing it.</p>}{photoLoading?<div className="empty">Loading photos…</div>:<div className="photogrid">{photos.filter(p=>Boolean(data.archivedPhotos?.includes(p.id))===showTrash&&[p.name,...items.filter(i=>i.photo===p.url).map(i=>i.name)].join(' ').toLowerCase().includes(photoSearch.toLowerCase())).map(photo=>{const uses=items.filter(i=>i.photo===photo.url),inDraft=edit?.photo===photo.url;return <div className="photocard" key={photo.id}><button type="button" className="photopreview" disabled={busy} onClick={()=>setPhotoDetail(photo)}><img src={window.PRESS_BASE+photo.url+"?v="+photoRefresh} alt={photo.name}/></button><div className="photodetails"><strong title={photo.name}>{photo.name}</strong><small>{uses.length?'Used by '+uses.length+' item'+(uses.length===1?'':'s'):inDraft?'Selected on current item':'Not used by any items'}</small>{uses.length>0&&<small title={uses.map(i=>i.name).join(', ')}>{unique(uses.map(i=>i.name)).join(', ')}</small>}<div className="photocardactions">{photoPicker&&!showTrash&&<button type="button" className="textbutton" disabled={busy} onClick={()=>{setEdit(v=>v?{...v,photo:photo.url}:v);setPhotosOpen(false)}}>Use photo</button>}{actor.admin&&<button type="button" className="textbutton" disabled={busy||(!showTrash&&(uses.length>0||inDraft))} title={uses.length||inDraft?'Remove this photo from items before deleting it':''} onClick={()=>trashPhoto(photo,showTrash)}>{showTrash?<RotateCcw size={14}/>:<Trash2 size={14}/>} {showTrash?'Restore':'Delete'}</button>}</div></div></div>})}</div>}{!photoLoading&&!photos.filter(p=>Boolean(data.archivedPhotos?.includes(p.id))===showTrash&&[p.name,...items.filter(i=>i.photo===p.url).map(i=>i.name)].join(' ').toLowerCase().includes(photoSearch.toLowerCase())).length&&<div className="empty"><Images size={30}/><h3>{showTrash?'Trash is empty':'No photos found'}</h3><p>{showTrash?'Photos you delete will appear here.':'Upload a photo to build your reusable library.'}</p></div>}<p className="hint">Photos in use are protected. Replace or remove them on each item before deleting.</p></div></div>}
{photosOpen&&photoDetail&&<div className="overlay photodetailoverlay"><div className="modal photomodal" role="dialog" aria-label="Photo details"><div className="modalhead"><h2>{photoDetail.name}</h2><button className="icon" aria-label="Back to photo library" disabled={busy} onClick={()=>setPhotoDetail(null)}><X/></button></div><img className="detailimage" src={window.PRESS_BASE+photoDetail.url+"?v="+photoRefresh} alt={photoDetail.name}/><h3>Items using this photo</h3>{items.filter(i=>i.photo===photoDetail.url).length?<div className="categorylist">{items.filter(i=>i.photo===photoDetail.url).map(i=><div key={i.id}><span><b>{i.name}</b><small>{[i.kind,i.category,i.size,i.color].filter(Boolean).join(' · ')} · {i.qty} on hand</small></span>{actor.admin&&<button className="textbutton" onClick={()=>{setEdit({...i});setPhotoDetail(null);setPhotosOpen(false)}}>Edit item</button>}</div>)}</div>:<p>No inventory items use this photo.</p>}{edit?.photo===photoDetail.url&&<p className="hint">Also selected on your current unsaved item.</p>}{actor.admin&&<div className="photoreplace"><p>Uploading a replacement overwrites this photo for every linked item.</p><label className="primary uploadcompact">Upload replacement<input aria-label="Upload replacement photo" type="file" accept="image/jpeg,image/png,image/webp" disabled={busy} onChange={e=>{if(e.target.files?.[0])replacePhoto(e.target.files[0]);e.target.value=''}}/></label><small>JPG, PNG or WebP · up to 6 MB</small></div>}{photoPicker&&!data.archivedPhotos?.includes(photoDetail.id)&&<button className="secondary" disabled={busy} onClick={()=>{setEdit(v=>v?{...v,photo:photoDetail.url}:v);setPhotoDetail(null);setPhotosOpen(false)}}>Use photo</button>}</div></div>}
{make&&<div className="overlay"><form className="modal smallmodal" onSubmit={produce}><div className="modalhead"><div><span className="eyebrow">AT THE PRESS</span><h2>Make an item</h2></div><button type="button" className="icon" onClick={()=>setMake(false)} aria-label="Close"><X/></button></div><p>Deduct blanks and transfers together, in one step.</p><label>Choose a blank<select required value={blank} onChange={e=>setBlank(e.target.value)}><option value="">Select blank…</option>{items.filter(i=>i.kind==='Blanks').map(i=><option value={i.id} key={i.id}>{i.name} · {i.size} · {i.color} ({i.qty} available)</option>)}</select></label><label>Transfer category<select value={makeCategory} onChange={e=>{setMakeCategory(e.target.value);setTransfer('')}}><option value="All">All transfer categories</option>{categories.map(c=><option key={c.id} value={c.id}>{c.name}</option>)}</select></label><label>Choose a transfer<select required value={transfer} onChange={e=>setTransfer(e.target.value)}><option value="">Select transfer…</option>{availableTransfers.map(i=><option value={i.id} key={i.id}>{i.name} · {i.size||'One size'} · {i.color||'No color specified'} ({i.qty} available)</option>)}</select></label>{!availableTransfers.length&&<p className="hint">No transfers in this category. Choose another category or add a transfer first.</p>}<label>How many?<input type="number" min="1" step="1" value={qty} onChange={e=>setQty(Number(e.target.value))} required/></label><label className="check"><input type="checkbox" checked={ready} onChange={e=>setReady(e.target.checked)}/> Add the finished items to premade inventory</label>{ready&&<label>Finished item name<input required value={output} onChange={e=>setOutput(e.target.value)} placeholder="e.g. Floral racerback tank"/></label>}<p className="hint">{ready?'The blank’s size and color carry over. Unit cost includes the blank and transfer.':'Use this for items made for an order and shipped right away.'}</p><button className="primary full" disabled={busy||!availableTransfers.some(i=>i.id===transfer)}>Make {qty} {qty===1?'item':'items'}</button></form></div>}
{adjust&&<div className="overlay"><form className="modal smallmodal" onSubmit={async e=>{e.preventDefault();if(adjust.qty+delta<0){setMessage('Stock cannot go below zero.');return}if(await save({...data,items:items.map(i=>i.id===adjust.id?{...i,qty:i.qty+delta}:i)},`${reason}: ${delta>0?'+':''}${delta} ${adjust.name} · ${adjust.size} ${adjust.color}`))setAdjust(null)}}><div className="modalhead"><h2>Adjust stock</h2><button type="button" className="icon" onClick={()=>setAdjust(null)} aria-label="Close"><X/></button></div><p>{adjust.name} · {adjust.size} {adjust.color}<br/>Currently {adjust.qty} on hand</p><label>Quantity change<input required type="number" step="1" value={delta} onChange={e=>setDelta(Number(e.target.value))}/><small>Positive to add; negative to deduct.</small></label><label>Reason<select value={reason} onChange={e=>setReason(e.target.value)}>{['Stock received','Sold / shipped','Damaged / wasted','Stock count correction','Other'].map(x=><option key={x}>{x}</option>)}</select></label><p>New quantity: <b>{adjust.qty+delta}</b></p><button className="primary full" disabled={busy}>Save adjustment</button></form></div>}
{activity&&<div className="overlay"><div className="modal"><div className="modalhead"><h2>Activity log</h2><button className="icon" aria-label="Close" onClick={()=>setActivity(false)}><X/></button></div><p>Latest 1,000 inventory changes.</p>{!data.history.length?<div className="empty">Your inventory story starts with your first item.</div>:data.history.map(h=><div className="historyrow" key={h.id}><Check size={17}/><div>{h.text}<small>{new Date(h.date).toLocaleString()}</small></div></div>)}</div></div>}
{catalog&&actor.admin&&<div className="overlay resourceoverlay"><div className="modal" role="dialog" aria-modal="true" aria-label={catalog==='category'?'Item categories':'Item / design names'}><div className="modalhead"><h2>{catalog==='category'?'Item categories':'Item / design names'}</h2><button className="icon" aria-label="Close list manager" onClick={()=>setCatalog(null)}><X/></button></div><p>{catalog==='category'?'These categories are available for every inventory type. Renaming updates all matching items. Deleting moves matching items to Other. Transfer access categories are managed separately.':'Renaming updates every matching inventory item. Deleting removes the saved dropdown choice; existing items and quantities are kept.'}</p><form className="categoryform" onSubmit={saveCatalog}><label>{catalog==='category'?'Category name':'Item / design name'}<input required maxLength={200} value={catalogDraft} onChange={e=>setCatalogDraft(e.target.value)}/></label><button className="primary" disabled={busy}>{catalogOriginal?'Save changes':catalog==='category'?'Add category':'Add name'}</button>{catalogOriginal&&<button type="button" className="textbutton" onClick={()=>{setCatalogOriginal('');setCatalogDraft('')}}>Cancel edit</button>}</form><div className="categorylist">{(catalog==='category'?itemCategoryOptions:nameOptions).map(name=><div key={name}><span><b>{name}</b><small>{items.filter(i=>i[catalog].toLowerCase()===name.toLowerCase()).length} inventory variants</small></span>{catalog==='category'&&name.toLowerCase()==='other'?<small>Fallback category</small>:<><button className="textbutton" disabled={busy} onClick={()=>{setCatalogOriginal(name);setCatalogDraft(name);setCatalogDelete(null)}}>Edit</button><button className="textbutton" disabled={busy} onClick={()=>setCatalogDelete(name)}>Delete</button></>}</div>)}</div>{catalogDelete&&<div className="categoryconfirm"><p>{catalog==='category'?<>Delete <b>{catalogDelete}</b> and move matching items to Other?</>:<>Remove <b>{catalogDelete}</b> from saved names? Existing inventory records will keep their names.</>}</p><button className="secondary" disabled={busy} onClick={e=>saveCatalog(e,true)}>Confirm delete</button><button className="textbutton" onClick={()=>setCatalogDelete(null)}>Cancel</button></div>}</div></div>}
{categoriesOpen&&actor.admin&&<div className="overlay resourceoverlay"><div className="modal"><div className="modalhead"><h2>Transfer categories</h2><button type="button" className="icon" aria-label="Close categories" onClick={()=>{setCategoriesOpen(false);setDeleteCategory(null)}}><X/></button></div><p>Unassigned transfers go into Uncatalogued. Deleting a category moves its items there and does not grant users access to it.</p><form className="categoryform" onSubmit={saveCategory}><label>Category name<input required maxLength={100} value={categoryDraft.name} onChange={e=>setCategoryDraft({...categoryDraft,name:e.target.value})}/></label><button className="primary" disabled={busy}>{categoryDraft.id?'Save name':'Add category'}</button>{categoryDraft.id&&<button className="textbutton" type="button" onClick={()=>setCategoryDraft({id:'',name:''})}>Cancel edit</button>}</form><div className="categorylist">{categories.map(c=><div key={c.id}><span><b>{c.name}</b><small>{items.filter(i=>i.kind==='Transfers'&&(i.transferCategoryId||UNCATALOGUED)===c.id).length} transfers</small></span>{c.id===UNCATALOGUED?<small>Default category</small>:<><button className="textbutton" onClick={()=>setCategoryDraft({...c})}>Edit</button><button className="textbutton" onClick={()=>setDeleteCategory(c)}>Delete</button></>}</div>)}</div>{deleteCategory&&<div className="categoryconfirm"><p>Delete <b>{deleteCategory.name}</b> and move its transfers to Uncatalogued?</p><button className="secondary" disabled={busy} onClick={removeCategory}>Delete category and move items</button><button className="textbutton" onClick={()=>setDeleteCategory(null)}>Cancel</button></div>}</div></div>}
{usersOpen&&actor.admin&&<div className="overlay resourceoverlay"><div className="modal"><div className="modalhead"><h2>{memberDraft?'User category access':'Users'}</h2><button type="button" className="icon" aria-label="Close users" onClick={()=>{setUsersOpen(false);setMemberDraft(null)}}><X/></button></div><p>Users sign in with the username and password you assign. Users can view permitted inventory; the owner manages inventory. Passwords are stored as hashes.</p>{memberDraft?<form className="memberform" onSubmit={saveMember}><label>Display name<input required maxLength={200} value={memberDraft.name} onChange={e=>setMemberDraft({...memberDraft,name:e.target.value})}/></label><label>Username<input required pattern="[a-zA-Z0-9][a-zA-Z0-9_.@+\-]{2,79}" value={memberDraft.email} readOnly={members.some(m=>m.email===memberDraft.email)} onChange={e=>setMemberDraft({...memberDraft,email:e.target.value})}/></label><label>{members.some(m=>m.email===memberDraft.email)?"New password (leave blank to keep current)":"Password"}<input type="password" autoComplete="new-password" minLength={12} maxLength={72} required={!members.some(m=>m.email===memberDraft.email)} value={memberDraft.password||""} onChange={e=>setMemberDraft({...memberDraft,password:e.target.value})}/></label><div className="categorychecks"><label className="check"><input type="checkbox" checked={categories.length>0&&categories.every(c=>memberDraft.categoryIds.includes(c.id))} onChange={e=>setMemberDraft({...memberDraft,categoryIds:e.target.checked?categories.map(c=>c.id):[]})}/> Tick all categories</label>{categories.map(c=><label className="check" key={c.id}><input type="checkbox" checked={memberDraft.categoryIds.includes(c.id)} onChange={e=>setMemberDraft({...memberDraft,categoryIds:e.target.checked?[...memberDraft.categoryIds,c.id]:memberDraft.categoryIds.filter(id=>id!==c.id)})}/>{c.name}</label>)}</div><p className="hint">No boxes checked means no transfers or finished items are visible. New categories are not granted automatically. Finished items require access to every transfer category used.</p><label className="check"><input type="checkbox" checked={!memberDraft.disabled} onChange={e=>setMemberDraft({...memberDraft,disabled:e.target.checked?0:1})}/> Account enabled</label><div className="modalfoot"><button type="button" className="textbutton" onClick={()=>setMemberDraft(null)}>Back</button><button className="primary" disabled={memberBusy}>Save user access</button></div></form>:<><button className="primary addsupplier" onClick={()=>setMemberDraft({email:'',name:'',categoryIds:[],disabled:0})}>Add user</button>{memberBusy?<p>Loading users…</p>:members.map(m=><button key={m.email} className="memberrow" onClick={()=>setMemberDraft({...m,password:"",categoryIds:m.categoryIds.filter(id=>categories.some(c=>c.id===id))})}><span><b>{m.name}</b><small>{m.email}</small></span><span>{m.disabled?'Disabled':m.categoryIds.filter(id=>categories.some(c=>c.id===id)).length+' categories'} · Edit</span></button>)}<p className="hint">Owner: {actor.email} · all categories</p></>}</div></div>}
{printView&&<div className="printview" role="dialog" aria-modal="true" aria-label="Printable inventory"><div className="printcontrols"><span>Print preview · {printView.items.length} filtered variants</span><div><button className="secondary" onClick={()=>setPrintView(null)}><X size={16}/> Back to inventory</button><button className="primary" onClick={()=>window.print()}><Printer size={16}/> Print / Save PDF</button></div></div><article className="printsheet"><div className="printheading"><div><h1>Press Tracker</h1><h2>Inventory report</h2></div><p>Prepared {printView.date}</p></div><p className="printfilters">{printView.summary}</p><div className="printtotals"><span>{printView.items.length} variants</span><span>{printView.items.reduce((n,i)=>n+i.qty,0)} on hand</span><span>{printView.items.reduce((n,i)=>n+(i.onOrder??0),0)} on order</span><b>Inventory value: {money(printView.items.reduce((n,i)=>n+i.qty*i.cost,0))}</b></div><table className="printtable"><thead><tr><th>ITEM / VARIANT</th><th>TYPE</th><th>SUPPLIER</th><th>UNIT COST</th><th>INVENTORY VALUE</th><th>ON HAND</th><th>ON ORDER</th></tr></thead><tbody>{printView.items.map(i=><tr key={i.id}><td><strong>{i.name}</strong><small>{[i.category,i.size||'One size',i.color,i.kind==='Transfers'?(categories.find(c=>c.id===(i.transferCategoryId||UNCATALOGUED))?.name||'Uncatalogued'):''].filter(Boolean).join(' · ')}</small></td><td>{i.kind}{i.kind==='Transfers'&&<small>Used: {i.used??0}</small>}</td><td>{i.supplier||'—'}</td><td>{money(i.cost)}</td><td>{money(i.qty*i.cost)}</td><td>{i.qty}{i.qty<=i.low&&<small>Low stock</small>}</td><td>{i.onOrder??0}</td></tr>)}</tbody><tfoot><tr><td colSpan={4}>TOTAL</td><td>{money(printView.items.reduce((n,i)=>n+i.qty*i.cost,0))}</td><td>{printView.items.reduce((n,i)=>n+i.qty,0)}</td><td>{printView.items.reduce((n,i)=>n+(i.onOrder??0),0)}</td></tr></tfoot></table>{!printView.items.length&&<p className="printempty">No items match these filters.</p>}<p className="printfootnote">Inventory value is based on quantities on hand × unit cost. Only the selected filters are included.</p></article></div>}
</div>}
