Testcraft Studio frontend 28: src/pages/Evidence.jsx
importReact,{useState}from'react';importIconfrom'../Icon.jsx';import{categories}from'../seed.js';import{useStudio,eligibleDocuments,readiness,resolveSelection,bumpVersion,log,uid}from'../store.jsx';import{Badge,Button,ContextBar,Empty,Field,Heading,Modal,Note,SourceDialog,Tabs,UploadBox,WorkspaceRequired}from'../ui.jsx';import{TeamAccess}from'./Workspaces.jsx';importCreateCrfrom'./CreateCr.jsx';exportdefaultfunctionEvidence({navigate}){const{state:s,mutate,workspace:w,notify}=useStudio();const[category,setCategory]=useState(0),[tab,setTab]=useState('Evidence & scope'),[dialog,setDialog]=useState(''),[source,setSource]=useState('');const[ruleId,setRuleId]=useState(''),[title,setTitle]=useState(''),[behavior,setBehavior]=useState(''),[sourceId,setSourceId]=useState('');const[crName,setCrName]=useState(''),[crRef,setCrRef]=useState('');if (!w)return<WorkspaceRequirednavigate={navigate}/>;if (!w.baselineId){constpending=s.baselines.find(b=>b.id===w.pendingBaselineId&&b.status==='Pending approval');return<><Headingeyebrow={`${w.ref} · YOUR NEW WORKSPACE`}title={w.name}description={w.purpose||'Bring your system knowledge into this workspace.'}/><sectionclassName="card workspace-setup"><spanclassName="setup-orbit"><Iconname={pending?'shield':'layers'}size={38}/></span><spanclassName="eyebrow">{pending?'BASELINE SUBMITTED':'NEXT · SYSTEM KNOWLEDGE'}</span><h2>{pending?'Your baseline is awaiting approval':'Give your workspace a foundation'}</h2><p>{pending?`${pending.ref} is ready for an Administrator to review. Your documents become available once the baseline is approved.`:'Add your business, technical, or functional documents to create a System Baseline. One document is enough to get started.'}</p><divclassName="row-actions"><Buttonvariant="primary"icon={pending?'shield':'plus'}onClick={()=>navigate(pending?'approvals':'baselines?create')}>{pending?'View approval request':'Create System Baseline'}</Button><ButtononClick={()=>navigate('versions')}>Use an approved baseline</Button></div><divclassName="setup-path"><spanclassName="done">Workspace created</span><Iconname="chevron"size={15}/><span>{pending?'Baseline submitted':'Add documents'}</span><Iconname="chevron"size={15}/><span>Administrator approval</span><Iconname="chevron"size={15}/><span>Generate tests</span></div></section></>;}constdocs=eligibleDocuments(s,w),ready=readiness(s,w),selected=resolveSelection(s,w.baselineId,w.crIds);construles=[...(selected.baseline?.rules||[])];selected.diff.forEach(change=>{consti=rules.findIndex(r=>r.id===change.ruleId);if (i>=0)rules.splice(i,1);if (change.op!=='REMOVE')rules.push({id:change.ruleId,title:change.title,text:change.after,source:change.source});});constrequirementRows=[...rules,...(w.customRules||[])];ready.questions.filter((q,i,all)=>all.findIndex(item=>item.requirement===q.requirement)===i).forEach(q=>{if (!requirementRows.some(r=>r.id===q.requirement))requirementRows.push({id:q.requirement,title:q.title,text:q.why,source:q.sourceId,question:true});});functionreview(id){mutate(d=>{d.documents.find(doc=>doc.id===id).status='Reviewed locally';log(d,`Reviewed local CR source for ${w.ref}`);});notify('Source reviewed locally. ');}return<><Headingeyebrow={`${w.ref} · ${w.visibility.toUpperCase()} WORKSPACE`}title={w.name}description={`${w.purpose} · Shared team workspace`}><Buttonicon="users"onClick={()=>setDialog('members')}>Team access</Button></Heading><ContextBarnavigate={navigate}/><Tabsvalue={tab}onChange={setTab}items={['Evidence & scope','Requirements','Activity']}/>{tab==='Activity'?<sectionclassName="card"><divclassName="section-heading"><h2>Workspace and team activity</h2><Badge>Audit history</Badge></div>{s.audit.filter(a=>a.team===w.team||a.actor==='Demo fixture').slice(0,20).map(a=><divclassName="activity-row"key={a.id}><spanclassName="soft-icon soft-purple"><Iconname="clock"size={17}/></span><div><strong>{a.action.replace(/Team Baseline/g,'System Baseline').replace(/local demo/gi,'studio')}</strong><p>{a.actor==='Demo fixture'?'System':a.actor}</p></div><small>{newDate(a.at).toLocaleString('en-IN')}</small></div>)}</section>:<divclassName="workspace-grid"><divclassName="workspace-main">{tab==='Evidence & scope'&&<><sectionclassName="card evidence-card"><divclassName="section-heading"><divclassName="inline-heading"><h2>Supporting evidence</h2><spanclassName="count-pill">{docs.length}</span></div><divclassName="row-actions"><ButtononClick={()=>setDialog('cr')}>Add CR</Button><Buttonicon="plus"onClick={()=>setDialog('upload')}>Add evidence</Button></div></div><divclassName="category-tabs"role="tablist"aria-label="CR evidence categories">{categories.map((c,i)=><buttonkey={c}role="tab"aria-selected={category===i}className={category===i?'active':''}onClick={()=>setCategory(i)}>{c}<span>{docs.filter(d=>d.category===i).length}</span></button>)}</div><pclassName="category-description">{['Procedures, functions, packages, tables, screens, APIs, and implementation validations.','Expected behavior, BRDs, acceptance criteria, defects, and regression requirements.','Terminology, processes, participant roles, and background for this change.'][category]}</p><divclassName="document-list">{docs.filter(d=>d.category===category).map(d=><divclassName="evidence-source"key={d.id}><buttonclassName="document-row"onClick={()=>setSource(d.id)}><spanclassName={`file-type file-${d.kind.toLowerCase()}`}><Iconname="file"size={21}/><small>{d.kind}</small></span><spanclassName="document-info"><strong>{d.name}</strong><small>{d.synthetic?'Reference document':'Uploaded document'}{d.team!==w.team?` · Shared by ${d.team}`:''}</small><span>{d.size} · Revision {d.revision}</span></span><Badge>{d.status}</Badge><Iconname="chevron"size={15}/></button>{d.status==='Staged'&&<divclassName="source-review-actions"><label>Route to category<selectvalue={d.category}onChange={e=>mutate(next=>{next.documents.find(doc=>doc.id===d.id).category=Number(e.target.value);})}>{categories.map((c,i)=><optionkey={c}value={i}>{c}</option>)}</select></label><ButtononClick={()=>review(d.id)}>Confirm local routing</Button></div>}</div>)}{!docs.some(d=>d.category===category)&&<Emptytitle="No separate source in this category"description="A combined document or sufficient evidence in other categories may be enough."action={<ButtononClick={()=>setDialog('upload')}>Add evidence</Button>}/>}</div><divclassName="evidence-hint"><Iconname="info"size={15}/>One combined document is welcome. These are categories, not three compulsory files.</div></section><divclassName="scope-card"><spanclassName="soft-icon soft-purple"><Iconname="shield"size={19}/></span><div><h3>Clear team boundaries</h3><p>{w.team}-owned requirements and explicitly shared contracts are in scope.</p><span><Iconname="lock"size={12}/>Other teams’ private implementation details are excluded.</span></div><buttonclassName="text-button"onClick={()=>navigate('graph')}>Explore dependencies <Iconname="arrow"size={14}/></button></div></>}<sectionclassName="card requirements-card"><divclassName="section-heading"><divclassName="inline-heading"><h2>Requirements in scope</h2><spanclassName="count-pill">{requirementRows.length}</span></div><Buttonicon="plus"onClick={()=>setDialog('requirement')}>Add requirement</Button></div><divclassName="table-scroll"><table><thead><tr><th>REQUIREMENT</th><th>EVIDENCE</th><th>STATUS</th></tr></thead><tbody>{requirementRows.map(r=><trkey={r.id}><td><buttonclassName="requirement-name"onClick={()=>setSource(r.source)}><small>{r.id}</small><strong>{r.title}</strong></button></td><td><buttonclassName="text-button"onClick={()=>setSource(r.source)}>Open source <Iconname="external"size={12}/></button></td><td><Badgetone={r.question&&ready.questions.some(q=>q.requirement===r.id&&q.status!=='resolved')?'amber':'green'}>{r.question&&ready.questions.some(q=>q.requirement===r.id&&q.status!=='resolved')?'Evidence gap':'Source linked'}</Badge></td></tr>)}</tbody></table></div>{!requirementRows.length&&<Emptytitle="No reviewed requirements yet"description="Record the expected behavior and link it to an uploaded document."/>}</section></div><sectionclassName="card readiness-card"><divclassName="section-heading"><h2>Your next step</h2><spanclassName="soft-icon soft-purple"><Iconname="spark"size={20}/></span></div><Badgetone={ready.ready?'green':'neutral'}>{ready.ready?'Ready to generate':'Build your evidence'}</Badge><h3>Turn your evidence into test cases.</h3><p>Choose a test-case format and generate from the requirements available in this workspace.</p><ButtonclassName="full-width"variant="primary"icon="spark"onClick={()=>navigate('readiness')}>Generate test cases</Button><ButtonclassName="full-width margin-top"icon="chat"onClick={()=>navigate('chat')}>Ask the system</Button></section></div>}{dialog==='members'&&<TeamAccessclose={()=>setDialog('')}/>}{dialog==='upload'&&<Modaltitle="Add CR evidence"close={()=>setDialog('')}><Fieldlabel="Proposed evidence category"><selectvalue={category}onChange={e=>setCategory(Number(e.target.value))}>{categories.map((c,i)=><optionvalue={i}key={c}>{c}</option>)}</select></Field><Note>Functional evidence defines expected behavior. Business context explains terms and processes. Mixed documents can be reviewed by section later.</Note><divclassName="margin-top"><UploadBoxscope={{team:w.team,workspaceId:w.id,category,crId:w.crIds[0]}}onStaged={files=>{mutate(d=>{d.documents.push(...files);bumpVersion(d,d.workspaces.find(item=>item.id===w.id));log(d,`Staged ${files.length} CR sources for ${w.ref}`);});setDialog('');notify('Files saved locally. Review their routing; knowledge context was updated.');}}/></div></Modal>}{dialog==='requirement'&&<Modaltitle="Add a reviewed requirement"close={()=>setDialog('')}><formonSubmit={e=>{e.preventDefault();if (!sourceId||!ruleId.trim()||!title.trim()||!behavior.trim())return;mutate(d=>{constitem=d.workspaces.find(item=>item.id===w.id);item.customRules=[...item.customRules||[],{id:ruleId.trim(),title,text:behavior,source:sourceId}];item.requirementCount+=1;bumpVersion(d,item);log(d,`Added source-linked requirement ${ruleId}`);});setDialog('');notify('Requirement added to this workspace context.');}}><Fieldlabel="Requirement ID"><inputrequiredvalue={ruleId}onChange={e=>setRuleId(e.target.value)}placeholder={`REQ-${w.team}-04`}/></Field><Fieldlabel="Title"><inputrequiredvalue={title}onChange={e=>setTitle(e.target.value)}/></Field><Fieldlabel="Approved expected behavior"><textarearequiredrows={3}value={behavior}onChange={e=>setBehavior(e.target.value)}/></Field><Fieldlabel="Supporting source"><selectrequiredvalue={sourceId}onChange={e=>setSourceId(e.target.value)}><optionvalue="">Choose evidence</option>{docs.filter(d=>['Ready','Reviewed locally'].includes(d.status)).map(d=><optionvalue={d.id}key={d.id}>{d.name}</option>)}</select></Field><Note>User-provided requirements stay scoped to this workspace. They do not update a System Baseline.</Note><divclassName="modal-actions"><ButtononClick={()=>setDialog('')}>Cancel</Button><Buttontype="submit"variant="primary"disabled={requirementRows.some(r=>r.id===ruleId.trim())}>Save requirement</Button></div></form></Modal>}{dialog==='cr'&&<CreateCrclose={()=>setDialog('')}/>}{source&&<SourceDialogsourceId={source}close={()=>setSource('')}/>}</>;}
Top comments (0)
Subscribe
We're a place where coders share, stay up-to-date and grow their careers.
Top comments (0)