Add multi-project support: projects entity, picker home page, project context
Projects become the top-level container; SOPs and Work Packages belong to one. Backend: - New projects table + CRUD (/api/projects). - sops.project_id (FK, cascade) and work_packages.project_id added; list/latest/metrics endpoints accept a project_id filter. Front end (now under html/): - project-data.js: shared API-first ProjectData adapter with localStorage fallback + active-project helpers. - Home page: removed "About This Suite"; added a Project picker (create / use sample / select). Tool cards stay hidden until a project is active and carry &project=<id>; hero shows the active project. - Suite reads ?project, resolves it, shows it in the header, and prefills the SOP project fields; passes &project into the WP-creator iframe. - WP creator stamps projectId onto saved packages. SOP/WP localStorage is not yet namespaced per project (next step). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -130,8 +130,9 @@ window.addEventListener('DOMContentLoaded',()=>{
|
||||
updateStepUI();
|
||||
updateProjectDisplay();
|
||||
|
||||
// Deep-link: ?tab=sop | ?tab=wp | ?view=dashboard from the home page cards.
|
||||
// Deep-link: ?tab=sop | ?tab=wp | ?view=dashboard | ?project=<id> from the home page.
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
applyProjectContext(params.get('project'));
|
||||
const tab = params.get('tab');
|
||||
if(params.get('view') === 'dashboard') switchTool('wp');
|
||||
else if(tab === 'wp' || tab === 'sop') switchTool(tab);
|
||||
@@ -284,8 +285,11 @@ function renderWPTab(){
|
||||
gate.style.display = 'none';
|
||||
frame.style.display = 'block';
|
||||
// Reload each time so the creator picks up the latest SOP from localStorage.
|
||||
const wantDash = new URLSearchParams(window.location.search).get('view') === 'dashboard';
|
||||
frame.src = 'wp-creation-index.html?embedded=1' + (wantDash ? '&view=dashboard' : '') + '&t=' + Date.now();
|
||||
const sp = new URLSearchParams(window.location.search);
|
||||
const wantDash = sp.get('view') === 'dashboard';
|
||||
const projId = sp.get('project') || (activeProject && activeProject.id) || '';
|
||||
frame.src = 'wp-creation-index.html?embedded=1' + (wantDash ? '&view=dashboard' : '')
|
||||
+ (projId ? '&project=' + encodeURIComponent(projId) : '') + '&t=' + Date.now();
|
||||
}else{
|
||||
gate.style.display = 'block';
|
||||
frame.style.display = 'none';
|
||||
@@ -297,8 +301,40 @@ function onSOPReady(){
|
||||
if(currentTool === 'wp') renderWPTab();
|
||||
}
|
||||
|
||||
// Active project comes from the home page (?project=<id> + ProjectData.getActive()).
|
||||
// When the SOP's project fields are still empty, prefill them from the project
|
||||
// record so the SOP is authored against the chosen project.
|
||||
let activeProject = null;
|
||||
function applyProjectContext(projectId){
|
||||
try { if(typeof ProjectData !== 'undefined') activeProject = ProjectData.getActive(); } catch(e){}
|
||||
if(projectId && (!activeProject || activeProject.id !== projectId)){
|
||||
// Deep-linked to a project we don't have cached yet — resolve it, then prefill.
|
||||
try {
|
||||
if(typeof ProjectData !== 'undefined'){
|
||||
ProjectData.get(projectId).then(p => { if(p){ activeProject = p; ProjectData.setActive(p); prefillProjectFields(); updateProjectDisplay(); } });
|
||||
}
|
||||
} catch(e){}
|
||||
}
|
||||
prefillProjectFields();
|
||||
}
|
||||
function prefillProjectFields(){
|
||||
if(!activeProject) return;
|
||||
const set = (id,v)=>{ const el=document.getElementById(id); if(el && !el.value && v) el.value = v; };
|
||||
set('proj_name', activeProject.name);
|
||||
set('proj_number', activeProject.number);
|
||||
set('proj_client', activeProject.client);
|
||||
set('proj_division', activeProject.division);
|
||||
set('proj_site', activeProject.site);
|
||||
if(typeof state !== 'undefined' && state.project){
|
||||
state.project.name = state.project.name || activeProject.name || '';
|
||||
state.project.number = state.project.number || activeProject.number || '';
|
||||
state.project.client = state.project.client || activeProject.client || '';
|
||||
state.project.division = state.project.division || activeProject.division || '';
|
||||
state.project.site = state.project.site || activeProject.site || '';
|
||||
}
|
||||
}
|
||||
function updateProjectDisplay(){
|
||||
const projName = document.getElementById('proj_name')?.value || 'Project';
|
||||
const projName = document.getElementById('proj_name')?.value || (activeProject && activeProject.name) || 'Project';
|
||||
const display = document.getElementById('project-display');
|
||||
if(display) display.textContent = sopComplete ? `✓ ${projName} (SOP Ready)` : projName;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user