init-source
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import Layout from '@/components/layout/Layout'
|
||||
import { Card, EmptyState, Btn, Tag, Field, Textarea, showToast } from '@/components/ui'
|
||||
import { FormFieldList } from '@/components/forms/FieldRenderer'
|
||||
import { useApp } from '@/lib/context'
|
||||
|
||||
const ISSUE_AREAS = [
|
||||
{ value: 'Production', icon: 'M9 22V12h6v10M3 9l9-7 9 7' },
|
||||
{ value: 'Goods-In', icon: 'M3 3h18v18H3zM3 9h18M9 21V9' },
|
||||
{ value: 'Warehouse', icon: 'M21 8V7l-3-4H6L3 7v1m18 0v12H3V8m18 0H3m9 4v4' },
|
||||
{ value: 'QA Lab', icon: 'M9 2v6l-5 8a2 2 0 002 3h12a2 2 0 002-3l-5-8V2M8.5 13h7' },
|
||||
{ value: 'Shipping', icon: 'M16 16V8a2 2 0 00-2-2H4a2 2 0 00-2 2v8a2 2 0 002 2h10a2 2 0 002-2zm0-3h2.5a2 2 0 011.6.8l1.4 1.87a2 2 0 01.4 1.2V16a1 1 0 01-1 1h-3z' },
|
||||
{ value: 'Other', icon: 'M12 5v.01M12 12v.01M12 19v.01' },
|
||||
]
|
||||
|
||||
export default function FillPage() {
|
||||
const { user } = useApp()
|
||||
const [forms, setForms] = useState<any[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [selected, setSelected] = useState<any>(null)
|
||||
const [answers, setAnswers] = useState<Record<string, any>>({})
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [submitted, setSubmitted] = useState(false)
|
||||
|
||||
// Report an issue
|
||||
const [pageTab, setPageTab] = useState<'forms' | 'report'>('forms')
|
||||
const [reportDesc, setReportDesc] = useState('')
|
||||
const [reportArea, setReportArea] = useState('')
|
||||
const [reportSubmitting, setReportSubmitting] = useState(false)
|
||||
const [reportRef, setReportRef] = useState('')
|
||||
|
||||
useEffect(() => { loadForms() }, [])
|
||||
|
||||
async function loadForms() {
|
||||
setLoading(true)
|
||||
const res = await fetch('/api/forms?status=ACTIVE')
|
||||
if (res.ok) {
|
||||
const { data } = await res.json()
|
||||
setForms(data || [])
|
||||
}
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
function selectForm(form: any) {
|
||||
setSelected(form)
|
||||
setAnswers({})
|
||||
setSubmitted(false)
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
const required = selected.fields.filter((f: any) => f.required)
|
||||
for (const f of required) {
|
||||
if (!answers[f.id]) {
|
||||
showToast(`"${f.label}" is required`, 'error'); return
|
||||
}
|
||||
}
|
||||
setSubmitting(true)
|
||||
const res = await fetch('/api/submissions', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ formId: selected.id, data: answers }),
|
||||
})
|
||||
setSubmitting(false)
|
||||
if (res.ok) {
|
||||
const { submissionCount } = await res.json()
|
||||
setSubmitted(true)
|
||||
showToast(`Submitted — you are contributor #${submissionCount}`)
|
||||
} else {
|
||||
showToast('Submission failed', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
function setAnswer(fieldId: string, value: any) {
|
||||
setAnswers(a => ({ ...a, [fieldId]: value }))
|
||||
}
|
||||
|
||||
async function submitReport() {
|
||||
if (!reportDesc.trim()) { showToast('Please describe what you noticed', 'error'); return }
|
||||
if (!reportArea) { showToast('Please pick where', 'error'); return }
|
||||
setReportSubmitting(true)
|
||||
const res = await fetch('/api/ncrs', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ description: reportDesc, source: reportArea }),
|
||||
})
|
||||
setReportSubmitting(false)
|
||||
if (res.ok) {
|
||||
const { data } = await res.json()
|
||||
setReportRef(data.ref)
|
||||
setReportDesc('')
|
||||
setReportArea('')
|
||||
} else {
|
||||
showToast('Could not submit report', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
function resetReport() {
|
||||
setReportRef('')
|
||||
setReportDesc('')
|
||||
setReportArea('')
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout title="My forms">
|
||||
{!selected ? (
|
||||
<>
|
||||
<div style={{ display: 'flex', gap: '20px', borderBottom: '0.5px solid #eee', marginBottom: '16px' }}>
|
||||
{(['forms', 'report'] as const).map(t => (
|
||||
<button key={t} onClick={() => setPageTab(t)} style={{
|
||||
padding: '8px 4px', fontSize: '12px', fontWeight: pageTab === t ? 500 : 400,
|
||||
color: pageTab === t ? '#534AB7' : '#888', background: 'none', border: 'none',
|
||||
borderBottom: pageTab === t ? '2px solid #534AB7' : '2px solid transparent',
|
||||
cursor: 'pointer', fontFamily: 'inherit',
|
||||
}}>{t === 'forms' ? 'First build forms' : 'Report an issue'}</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{pageTab === 'forms' ? (
|
||||
<>
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<h2 style={{ fontSize: '16px', fontWeight: '500', margin: 0 }}>First build forms</h2>
|
||||
<p style={{ fontSize: '11px', color: '#aaa', margin: '2px 0 0' }}>Select a form to fill out — your data helps set quality standards</p>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div style={{ textAlign: 'center', padding: '40px', color: '#aaa', fontSize: '12px' }}>Loading…</div>
|
||||
) : forms.length === 0 ? (
|
||||
<EmptyState
|
||||
title="No active forms"
|
||||
message="Your admin hasn't published any data collection forms yet. Check back soon."
|
||||
/>
|
||||
) : (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: '12px' }}>
|
||||
{forms.map((form: any) => (
|
||||
<div key={form.id} onClick={() => selectForm(form)} style={{
|
||||
background: 'white', border: '0.5px solid #eee', borderRadius: '12px',
|
||||
padding: '16px', cursor: 'pointer', transition: 'border-color 0.1s'
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', marginBottom: '8px' }}>
|
||||
<div style={{ width: '32px', height: '32px', borderRadius: '8px', background: '#EEEDFE', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#534AB7" strokeWidth="2">
|
||||
<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
|
||||
</svg>
|
||||
</div>
|
||||
<Tag color="green">Active</Tag>
|
||||
</div>
|
||||
<div style={{ fontSize: '13px', fontWeight: '500', marginBottom: '4px' }}>{form.name}</div>
|
||||
{form.product && <div style={{ fontSize: '11px', color: '#aaa', marginBottom: '8px' }}>{form.product}</div>}
|
||||
<div style={{ fontSize: '11px', color: '#888' }}>{form.fields?.length || 0} fields · {form._count?.submissions || 0} submissions so far</div>
|
||||
<div style={{ marginTop: '12px' }}>
|
||||
<Btn size="sm" style={{ width: '100%', justifyContent: 'center' }}>Fill out →</Btn>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div style={{ maxWidth: '420px', margin: '0 auto', textAlign: 'center', padding: '8px 0' }}>
|
||||
{reportRef ? (
|
||||
<Card style={{ padding: '32px' }}>
|
||||
<div style={{ width: '48px', height: '48px', borderRadius: '50%', background: '#EAF3DE', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 16px' }}>
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#1D9E75" strokeWidth="2.5"><path d="M5 13l4 4L19 7"/></svg>
|
||||
</div>
|
||||
<div style={{ fontSize: '15px', fontWeight: '500', marginBottom: '6px' }}>Reported — thanks</div>
|
||||
<div style={{ fontSize: '12px', color: '#888', marginBottom: '20px' }}>
|
||||
QC will review <span style={{ color: '#534AB7', fontWeight: '500' }}>{reportRef}</span>. You'll be notified automatically once a fix is confirmed — nothing else to do.
|
||||
</div>
|
||||
<Btn onClick={resetReport}>Report another</Btn>
|
||||
</Card>
|
||||
) : (
|
||||
<Card>
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#bbb" strokeWidth="2" style={{ margin: '0 auto 8px', display: 'block' }}><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>
|
||||
<h3 style={{ fontSize: '16px', fontWeight: '500', marginBottom: '6px' }}>Report an issue</h3>
|
||||
<p style={{ fontSize: '12px', color: '#888', marginBottom: '18px' }}>Notice something off? Let QC know — takes 30 seconds. No quality background needed.</p>
|
||||
|
||||
<Field label="What did you notice?">
|
||||
<Textarea value={reportDesc} onChange={e => setReportDesc(e.target.value)} placeholder="e.g. Found 3 units with loose end caps during inspection on Line 2" style={{ textAlign: 'left' }}/>
|
||||
</Field>
|
||||
|
||||
<div style={{ textAlign: 'left', marginBottom: '14px' }}>
|
||||
<label style={{ display: 'block', fontSize: '11px', color: '#666', marginBottom: '4px', fontWeight: '500' }}>Where?</label>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: '6px' }}>
|
||||
{ISSUE_AREAS.map(a => (
|
||||
<div key={a.value} onClick={() => setReportArea(a.value)} style={{
|
||||
border: `0.5px solid ${reportArea === a.value ? '#534AB7' : '#ddd'}`,
|
||||
background: reportArea === a.value ? '#EEEDFE' : 'transparent',
|
||||
color: reportArea === a.value ? '#534AB7' : '#666',
|
||||
borderRadius: '8px', padding: '9px 6px', textAlign: 'center', cursor: 'pointer', fontSize: '11px',
|
||||
fontWeight: reportArea === a.value ? 500 : 400,
|
||||
}}>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ display: 'block', margin: '0 auto 4px' }}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d={a.icon}/>
|
||||
</svg>
|
||||
{a.value}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Btn onClick={submitReport} disabled={reportSubmitting} style={{ width: '100%', justifyContent: 'center' }}>
|
||||
{reportSubmitting ? 'Submitting…' : 'Submit report'}
|
||||
</Btn>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : submitted ? (
|
||||
<Card style={{ maxWidth: '480px', margin: '40px auto', textAlign: 'center', padding: '32px' }}>
|
||||
<div style={{ width: '48px', height: '48px', borderRadius: '50%', background: '#EAF3DE', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 16px' }}>
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#1D9E75" strokeWidth="2.5"><path d="M5 13l4 4L19 7"/></svg>
|
||||
</div>
|
||||
<div style={{ fontSize: '16px', fontWeight: '500', marginBottom: '6px' }}>Submitted</div>
|
||||
<div style={{ fontSize: '12px', color: '#888', marginBottom: '20px' }}>Your data has been recorded and will contribute to setting quality standards for {selected.product || selected.name}.</div>
|
||||
<div style={{ display: 'flex', gap: '8px', justifyContent: 'center' }}>
|
||||
<Btn variant="ghost" onClick={() => { setSubmitted(false); setAnswers({}) }}>Fill again</Btn>
|
||||
<Btn onClick={() => setSelected(null)}>Back to forms</Btn>
|
||||
</div>
|
||||
</Card>
|
||||
) : (
|
||||
<div style={{ maxWidth: '560px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '20px' }}>
|
||||
<button onClick={() => setSelected(null)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#aaa', padding: '4px', display: 'flex' }}>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M19 12H5m7-7l-7 7 7 7"/></svg>
|
||||
</button>
|
||||
<div>
|
||||
<h2 style={{ fontSize: '15px', fontWeight: '500', margin: 0 }}>{selected.name}</h2>
|
||||
{selected.product && <div style={{ fontSize: '11px', color: '#aaa' }}>{selected.product}</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<div style={{ background: '#EEEDFE', borderRadius: '8px', padding: '9px 12px', fontSize: '11px', color: '#3C3489', marginBottom: '16px' }}>
|
||||
Fill in each field from your direct observation. Your submission is anonymous to QC — it's just your data point.
|
||||
</div>
|
||||
|
||||
<FormFieldList
|
||||
fields={(selected.fields || []).slice().sort((a: any, b: any) => a.order - b.order)}
|
||||
values={answers}
|
||||
onChange={setAnswer}
|
||||
/>
|
||||
|
||||
<div style={{ display: 'flex', gap: '8px', marginTop: '20px', paddingTop: '16px', borderTop: '0.5px solid #eee' }}>
|
||||
<Btn variant="ghost" onClick={() => { setAnswers({}); }}>Clear</Btn>
|
||||
<Btn onClick={submitForm} disabled={submitting} style={{ flex: 1, justifyContent: 'center' }}>
|
||||
{submitting ? 'Submitting…' : 'Submit build data'}
|
||||
</Btn>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user