Testcraft Studio frontend 35: src/pages/Workspaces.jsx
importReact,{useState}from'react';importIconfrom'../Icon.jsx';import{useStudio,uid,timestamp,log,workspaceStatus,questionsFor,resolveSelection,eligibleDocuments,visibleWorkspaces}from'../store.jsx';import{Badge,Button,Empty,Field,Heading,Modal,Note,Pagination,Search}from'../ui.jsx';exportfunctionTeamAccess({close}){const{state,workspace}=useStudio();return<Modaltitle="Shared team access"close={close}><pclassName="modal-intro">{workspace.name}</p><divclassName="selection-summary"><span>Owning team</span><strong>{workspace.team}</strong><small>Everyone signed in to this team uses the same workspace access.</small></div><Note>Users can create System Baselines, upload evidence, resolve questions, generate tests, and edit suites. Administrators also add teams and approve System Baseline creation and edits.</Note><divclassName="modal-actions"><ButtononClick={close}>Done</Button></div></Modal>;}exportfunctionWorkspaceWizard({close,navigate,copyId=''}){const{state:s,mutate,notify}=useStudio();const[step,setStep]=useState(0),[path,setPath]=useState(copyId?'copy':'fresh');const[source,setSource]=useState(copyId||visibleWorkspaces(s)[0]?.id||'');const[baselineId,setBaselineId]=useState(s.baselines.find(b=>b.team===s.session.team&&['Active','Approved'].includes(b.status))?.id||'');const[name,setName]=useState(''),[purpose,setPurpose]=useState(''),[visibility]=useState('team');constoriginal=visibleWorkspaces(s).find(w=>w.id===source);constselectedId=path==='fresh'?'':path==='copy'?original?.baselineId:baselineId;constselectedCrs=path==='copy'?original?.crIds||[]:[];constresolution=selectedId?resolveSelection(s,selectedId,selectedCrs):{issues:[],baseline:null};constcanContinue=path==='fresh'||(path==='copy'?!!original:!!baselineId);functioncreate(e){e.preventDefault();if (step!==2||resolution.issues.length||!name.trim())return;constid=uid('workspace');mutate(d=>{constw={id,name:name.trim(),ref:`WS-${String(d.workspaces.length+1).padStart(4,'0')}`,purpose:purpose.trim(),team:s.session.team,baselineId:selectedId||'',crIds:selectedCrs,visibility,members:[s.session.team],version:1,updatedAt:timestamp(),requirementCount:path==='copy'?original.requirementCount:(resolution.baseline?.rules.length||0),sharedContractIds:path==='copy'?original.sharedContractIds:[],sourceIds:path==='copy'?eligibleDocuments(s,original).map(doc=>doc.id):[],customRules:path==='copy'?structuredClone(original.customRules||[]):[]};d.workspaces.unshift(w);d.activeWorkspaceId=id;if (path==='copy')questionsFor(s,original).forEach(q=>d.questions.push({...q,id:uid('question'),workspaceId:id,version:1,answer:'',sourceReference:'',answerSourceId:'',author:null,resolvedBy:null,status:'open',validatedAt:null}));log(d,`Created ${w.ref}${resolution.baseline?` from ${resolution.baseline.ref}`:''}`);});notify('Workspace created. Chat starts with a fresh context.');close();navigate('workspace');}return<Modaltitle="Create a workspace"close={close}wide><divclassName="wizard-steps">{['Starting point','Workspace details','Review & create'].map((text,i)=><spankey={text}className={step>=i?'active':''}>{i+1}<small>{text}</small></span>)}</div>{step===0?<><pclassName="modal-intro">Start with the right knowledge for your change.</p><divclassName="choice-list">{[{id:'fresh',icon:'plus',title:'Start a fresh workspace',text:'Create your workspace first, then upload its baseline documents.'},{id:'baseline',icon:'book',title:'Use a System Baseline',text:'Start from available, reusable system knowledge.'},{id:'copy',icon:'folder',title:'Copy an existing workspace',text:'Reuse its selection. Chat history and answers start fresh.'}].map(c=><buttonclassName={`choice-card ${path===c.id?'selected':''}`}key={c.id}onClick={()=>setPath(c.id)}><spanclassName="soft-icon soft-purple"><Iconname={c.icon}size={20}/></span><span><strong>{c.title}</strong><p>{c.text}</p></span><spanclassName="radio-dot"/></button>)}</div>{path==='baseline'&&<Fieldlabel="System Baseline"><selectvalue={baselineId}onChange={e=>setBaselineId(e.target.value)}><optionvalue="">Choose a baseline</option>{s.baselines.filter(b=>b.team===s.session.team&&['Active','Approved'].includes(b.status)).map(b=><optionkey={b.id}value={b.id}>{b.ref} · v{b.version}</option>)}</select></Field>}{path==='copy'&&<Fieldlabel="Source workspace"><selectvalue={source}onChange={e=>setSource(e.target.value)}><optionvalue="">Choose a workspace</option>{visibleWorkspaces(s).map(w=><optionkey={w.id}value={w.id}>{w.ref} · {w.name}</option>)}</select></Field>}<divclassName="modal-actions"><ButtononClick={close}>Cancel</Button><Buttonvariant="primary"disabled={!canContinue}onClick={()=>{setStep(1);}}>Continue <Iconname="arrow"size={15}/></Button></div></>:<formonSubmit={create}>{step===1?<><Fieldlabel="Workspace name"><inputrequiredautoFocusvalue={name}maxLength={100}onChange={e=>setName(e.target.value)}placeholder="e.g. Participant validation review"/></Field><Fieldlabel="Purpose"><textareavalue={purpose}maxLength={600}onChange={e=>setPurpose(e.target.value)}placeholder="What change will this workspace test?"rows={3}/></Field><Note>This workspace is shared by everyone signed in to {s.session.team}. There are no individual access roles.</Note></>:<><divclassName="selection-summary"><span>Workspace</span><strong>{name}</strong><small>{purpose||'No description added.'}</small><strong>{resolution.baseline?.ref||'Add a System Baseline after creating the workspace'}{selectedCrs.map(id=>`+ ${s.crs.find(c=>c.id===id)?.ref}`).join('')}</strong><small>{visibility} · {s.session.team} · New chat context</small></div>{resolution.issues.map(issue=><Notekey={issue}tone="amber">{issue}</Note>)}<Note>Immutable source revisions are referenced. Copies do not duplicate source files or inherit private chat history.</Note></>}<divclassName="modal-actions"><ButtononClick={()=>setStep(step-1)}>Back</Button>{step===1?<Buttonvariant="primary"disabled={!name.trim()}onClick={e=>{e.preventDefault();setStep(2);}}>Review workspace</Button>:<Buttontype="submit"variant="primary"icon="check"disabled={resolution.issues.length>0}>Create workspace</Button>}</div></form>}</Modal>;}exportdefaultfunctionWorkspaces({navigate,create=false}){const{state:s,mutate}=useStudio();const[query,setQuery]=useState(''),[filter,setFilter]=useState('All statuses'),[page,setPage]=useState(1),[wizard,setWizard]=useState(create),[copy,setCopy]=useState('');constlist=visibleWorkspaces(s).filter(w=>`${w.name}${w.ref}`.toLowerCase().includes(query.toLowerCase())&&(filter==='All statuses'||workspaceStatus(s,w)===filter));constopen=w=>{mutate(d=>{d.activeWorkspaceId=w.id;});navigate('workspace');};return<><Headingeyebrow={`${s.session.team} · TEAM SPACE`}title="Your workspaces"description="Every change, its own knowledge context and review history."><Buttonicon="plus"variant="primary"onClick={()=>{setCopy('');setWizard(true);}}>Create workspace</Button></Heading><sectionclassName="card"><divclassName="table-toolbar spacious"><Searchvalue={query}onChange={v=>{setQuery(v);setPage(1);}}placeholder="Search names or workspace references…"/><selectclassName="simple-select"value={filter}onChange={e=>{setFilter(e.target.value);setPage(1);}}>{['All statuses','Awaiting baseline approval','Add baseline','Ready for review','Ready to generate','Draft'].map(v=><optionkey={v}>{v}</option>)}</select></div><divclassName="table-scroll"><table><thead><tr><th>WORKSPACE</th><th>BASELINE</th><th>STATUS</th><th>ACCESS</th><th>ACTIONS</th></tr></thead><tbody>{list.slice((page-1)*6,page*6).map(w=><trkey={w.id}><td><buttonclassName="workspace-table-name"onClick={()=>open(w)}><spanclassName="table-icon soft-purple"><Iconname="folder"size={18}/></span><span><strong>{w.name}</strong><small>{w.ref} · Shared team access</small></span></button></td><td>{s.baselines.find(b=>b.id===w.baselineId)?.ref||'Not connected yet'}</td><td><Badge>{workspaceStatus(s,w)}</Badge></td><td><Iconname="users"size={13}/> Shared team</td><td><divclassName="row-actions"><ButtononClick={()=>open(w)}>Open</Button><ButtononClick={()=>{setCopy(w.id);setWizard(true);}}>Copy</Button></div></td></tr>)}</tbody></table></div>{!list.length&&<Emptytitle="No workspaces found"description="Try another search or create your first workspace."/>}<Paginationcount={list.length}page={page}onChange={setPage}/></section>{wizard&&<WorkspaceWizardkey={copy}copyId={copy}close={()=>{setWizard(false);if (create)navigate('workspaces');}}navigate={navigate}/>}</>;}
Top comments (0)
Subscribe
We're a place where coders share, stay up-to-date and grow their careers.
Top comments (0)