ad499f6782
Build and Push Docker Image / build (push) Successful in 1m12s
Reconstruct the full app from init-source overlays (base + fix-1..6 + update-1..3, last-wins) at the repo root, complete the missing pieces so it builds and runs, and stage the Unraid deployment. App completion: - types/index.ts: former Prisma enums as string-literal unions + AppUser - pages/_app.tsx + styles/globals.css (mount AppProvider/ToastProvider) - API routes: auth/login, auth/me, users, submissions (+REVIEW_READY notify), forms (list/create), notifications - scripts/create-admin.js: idempotent first-admin bootstrap - 14 unbuilt nav targets stubbed via ComingSoon placeholder SQLite refactor (single-container, no external DB): - schema provider -> sqlite; enums -> String; Json -> String; FormField.options String[] -> JSON-encoded String - lib/forms.ts (de)serialises options at the DB boundary - drop mode:"insensitive" (unsupported on SQLite) - enum imports repointed from @prisma/client to @/types Deploy: - multi-stage Dockerfile (next build -> prod runner), docker-entrypoint.sh (prisma db push -> create-admin -> next start), .dockerignore - docker-compose.yml: br0 10.2.0.x, /mnt/user/appdata/qms -> /data volume - README rewritten for the Unraid/Gitea Actions flow; .env scrubbed of the live Supabase credential; vercel.json removed Verified: next build clean (41 routes); live SQLite round-trip of login/session, form options array, and submission -> REVIEW_READY. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
78 lines
3.4 KiB
TypeScript
78 lines
3.4 KiB
TypeScript
import { useState, useEffect } from 'react'
|
|
import Layout from '@/components/layout/Layout'
|
|
import { Card, EmptyState, Btn, Field, Input, showToast } from '@/components/ui'
|
|
import { useApp } from '@/lib/context'
|
|
|
|
export default function ShippingStandardPage() {
|
|
const { user } = useApp()
|
|
const [items, setItems] = useState<any[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [newText, setNewText] = useState('')
|
|
const [adding, setAdding] = useState(false)
|
|
|
|
const canEdit = user && (user.role === 'ADMIN' || user.role === 'QC')
|
|
|
|
useEffect(() => { load() }, [])
|
|
|
|
async function load() {
|
|
setLoading(true)
|
|
const res = await fetch('/api/shipping-standard')
|
|
if (res.ok) { const { data } = await res.json(); setItems(data || []) }
|
|
setLoading(false)
|
|
}
|
|
|
|
async function addItem() {
|
|
if (!newText.trim()) return
|
|
setAdding(true)
|
|
const res = await fetch('/api/shipping-standard', {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ text: newText, source: 'Baseline' }),
|
|
})
|
|
setAdding(false)
|
|
if (res.ok) {
|
|
setNewText('')
|
|
showToast('Added to shipping standard')
|
|
load()
|
|
} else {
|
|
showToast('Failed to add', 'error')
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Layout title="Shipping standard">
|
|
<div style={{ marginBottom: '16px' }}>
|
|
<h2 style={{ fontSize: '16px', fontWeight: '500', margin: 0 }}>Living shipping standard</h2>
|
|
<p style={{ fontSize: '11px', color: '#aaa', margin: '2px 0 0' }}>What must be true before a product ships. Updates automatically when a client-reported quality escape is resolved.</p>
|
|
</div>
|
|
|
|
<Card>
|
|
{loading ? (
|
|
<div style={{ textAlign: 'center', padding: '32px', color: '#aaa', fontSize: '12px' }}>Loading…</div>
|
|
) : items.length === 0 ? (
|
|
<EmptyState title="No standard items yet" message="Add baseline checks below, or resolve a client issue with 'update shipping standard' to add one automatically."/>
|
|
) : items.map((item: any, i: number) => (
|
|
<div key={item.id} style={{ display: 'flex', alignItems: 'flex-start', gap: '10px', border: '0.5px solid #eee', borderRadius: '8px', padding: '11px 13px', marginBottom: '8px' }}>
|
|
<div style={{ width: '22px', height: '22px', borderRadius: '50%', background: '#f5f5f5', color: '#aaa', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '11px', fontWeight: '500', flexShrink: 0 }}>{i + 1}</div>
|
|
<div style={{ flex: 1 }}>
|
|
<div style={{ fontSize: '13px', lineHeight: '1.5' }}>{item.text}</div>
|
|
<div style={{ fontSize: '10px', color: '#aaa', marginTop: '3px' }}>
|
|
Source: {item.source === 'Baseline' ? 'Baseline' : <span style={{ color: '#534AB7', fontWeight: '500' }}>{item.source}</span>}
|
|
{item.source !== 'Baseline' && <> · {new Date(item.createdAt).toLocaleDateString()}</>}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
{canEdit && (
|
|
<div style={{ display: 'flex', gap: '8px', marginTop: '12px', borderTop: '0.5px solid #eee', paddingTop: '14px' }}>
|
|
<div style={{ flex: 1 }}>
|
|
<Input value={newText} onChange={e => setNewText(e.target.value)} placeholder="Add a baseline check…"/>
|
|
</div>
|
|
<Btn onClick={addItem} disabled={adding}>{adding ? 'Adding…' : 'Add'}</Btn>
|
|
</div>
|
|
)}
|
|
</Card>
|
|
</Layout>
|
|
)
|
|
}
|