From 39d26be4476c60c5854396d2c30e72bee4558a9d Mon Sep 17 00:00:00 2001 From: NPS Agent Date: Mon, 11 May 2026 15:23:49 +0930 Subject: [PATCH] Workspace white screen error resolved, issue was when we migrated from the local SQLite database to the Client-Server architecture during Phase 2, we didn't implement backend endpoints for adding, updating, or deleting users. Because the app.jsx file had nowhere to send those requests, the buttons didn't do anything --- PROGRESS.md | 13 +++++++++---- api.js | 26 +++++++++++++++++++++++++ app.jsx | 39 +++++++++++++++++++++++++++++++++++++- backend/main.py | 46 +++++++++++++++++++++++++++++++++++++++++++++ backend/schemas.py | 6 ++++++ dashy.db | Bin 61440 -> 61440 bytes screens.jsx | 6 +++--- 7 files changed, 128 insertions(+), 8 deletions(-) diff --git a/PROGRESS.md b/PROGRESS.md index ccfe9f2..bfc4338 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -41,13 +41,18 @@ ## ⏭️ Upcoming Steps -### Phase 2: Frontend Refactor (✅ Completed) -1. **API Service:** Created `api.js` to handle all network requests to the FastAPI backend. +### Phase 2: Frontend Refactor & Workflow Polish (✅ Completed) +1. **API Service:** Created `api.js` to dynamically connect to the backend (resolved connection refused issues by using dynamic hostnames) and handle all network requests. 2. **Authentication Hook:** Updated the Login screen to use real JWT tokens. 3. **Component Updates:** - Swapped `DashyDB` calls for `async` API calls in `app.jsx`. - - Implemented "Loading" states for UI responsiveness during network calls. -4. **Cleanup:** Archived `db.js`, `data.jsx`, and removed `sql.js` WASM dependency from `Dashy.html`. + - Fixed UI state refresh issues by silently loading subsequent data updates. +4. **Task Workflows:** + - **Completion:** Added a "Mark as completed" button in `TaskDetail` that changes task status to `closed`, records it in the Audit Log, and removes the task from the main Overview board. + - **Reopening:** Added a "Reopen task" button to restore accidentally closed tasks back to the queue. + - **User Views:** Updated `UserScreen` to accurately display open task counts and render a dedicated, faded "Completed" section at the bottom for closed tasks. + - **Audit Rendering:** Fixed crash in `TaskDetail` by passing global API audit logs and filtering them locally for individual tasks. +5. **Cleanup:** Archived `db.js`, `data.jsx` to `Dashy-v1/scraps/` and removed `sql.js` WASM dependency from `Dashy.html`. ### Phase 3: Advanced Features - **Real-time Notifications:** Explore WebSockets for task assignments. diff --git a/api.js b/api.js index 621f33f..b3a9744 100644 --- a/api.js +++ b/api.js @@ -66,6 +66,32 @@ class ApiService { return this.request('/users'); } + async createUser(userData) { + const data = await this.request('/users', { + method: 'POST', + body: JSON.stringify(userData), + }); + this.notify(); + return data; + } + + async updateUser(id, updates) { + const data = await this.request(`/users/${id}`, { + method: 'PATCH', + body: JSON.stringify(updates), + }); + this.notify(); + return data; + } + + async deleteUser(id) { + const data = await this.request(`/users/${id}`, { + method: 'DELETE', + }); + this.notify(); + return data; + } + async getTasks() { return this.request('/tasks'); } diff --git a/app.jsx b/app.jsx index 87c5f70..dd05473 100644 --- a/app.jsx +++ b/app.jsx @@ -235,7 +235,7 @@ function App() { setAuthed(false); setShowSettings(false); }} - onSwitchUser={async (id) => { + onSwitchUser={async (id) => { try { await api.login(id, "password123"); setMeId(id); @@ -244,6 +244,43 @@ function App() { alert("Failed to switch user"); } }} + onCreateUser={async (u) => { + try { + const id = u.name.split(' ')[0].toLowerCase() + Math.floor(Math.random()*100); + await api.createUser({ + id, + name: u.name, + role: u.role, + hue: Math.floor(Math.random() * 360), + initials: u.name.split(' ').map(s=>s[0]).join('').slice(0,2).toUpperCase(), + account_type: u.account_type, + password: "password123" + }); + await api.addAudit({ actor: meId, action: 'user_created', summary: 'Added ' + u.name + ' (' + (u.account_type||'standard') + ')', target: id }); + } catch(e) { + console.error(e); + alert("Failed to create user: " + e.message); + } + }} + onDeleteUser={async (id) => { + try { + const u = userMap[id]; + await api.deleteUser(id); + await api.addAudit({ actor: meId, action: 'user_deleted', summary: 'Removed ' + (u?u.name:id), target: null }); + } catch(e) { + console.error(e); + alert("Failed to delete user"); + } + }} + onUpdateUserRole={async (id, edits) => { + try { + await api.updateUser(id, edits); + await api.addAudit({ actor: meId, action: 'user_updated', summary: 'Updated ' + (userMap[id]?userMap[id].name:id) + ' permissions', target: null }); + } catch(e) { + console.error(e); + alert("Failed to update user"); + } + }} /> )} diff --git a/backend/main.py b/backend/main.py index 57cd76c..c4bd706 100644 --- a/backend/main.py +++ b/backend/main.py @@ -35,6 +35,52 @@ async def login_for_access_token(form_data: schemas.UserLogin, db: Session = Dep def read_users(db: Session = Depends(get_db)): return db.query(models.User).all() +@app.post("/users", response_model=schemas.User) +def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)): + db_user = models.User( + id=user.id, + name=user.name, + role=user.role, + hue=user.hue, + initials=user.initials, + email=user.email, + phone=user.phone, + photo=user.photo, + account_type=user.account_type, + password_hash=auth.get_password_hash(user.password) + ) + db.add(db_user) + db.commit() + db.refresh(db_user) + return db_user + +@app.patch("/users/{user_id}", response_model=schemas.User) +def update_user(user_id: str, user_update: schemas.UserUpdate, db: Session = Depends(get_db)): + db_user = db.query(models.User).filter(models.User.id == user_id).first() + if not db_user: + raise HTTPException(status_code=404, detail="User not found") + + update_data = user_update.dict(exclude_unset=True) + for key, value in update_data.items(): + setattr(db_user, key, value) + + db.commit() + db.refresh(db_user) + return db_user + +@app.delete("/users/{user_id}") +def delete_user(user_id: str, db: Session = Depends(get_db)): + db_user = db.query(models.User).filter(models.User.id == user_id).first() + if not db_user: + raise HTTPException(status_code=404, detail="User not found") + + # Reassign tasks to rod + db.query(models.Task).filter(models.Task.assignee_id == user_id).update({"assignee_id": "rod"}) + + db.delete(db_user) + db.commit() + return {"message": "User deleted"} + @app.get("/tasks", response_model=List[schemas.Task]) def read_tasks(db: Session = Depends(get_db)): return db.query(models.Task).all() diff --git a/backend/schemas.py b/backend/schemas.py index 6829d90..f476bd5 100644 --- a/backend/schemas.py +++ b/backend/schemas.py @@ -20,6 +20,12 @@ class UserLogin(BaseModel): id: str password: str +class UserUpdate(BaseModel): + name: Optional[str] = None + role: Optional[str] = None + account_type: Optional[str] = None + photo: Optional[str] = None + class User(UserBase): created_at: datetime class Config: diff --git a/dashy.db b/dashy.db index 7bdfeeffd731e6df952b4a6476ac910ee83b1fc0..0ee0f94b61220eafcd2b9424e51a3d170921a09e 100644 GIT binary patch delta 1083 zcma)*OH30{6o&hP0(Rzts0Bg1RSGQ)VIG}MsnLYEVAF(#1`}81w)d7!XlKaG@Gz1T zH^wN5Hr)wP<4R#8HYSE88^x7u(1kI?D8|H{i3?XwJ8e;L;cD{F-1FV@pa0C#o_%T0 zey__qR_MNL9ov|E25i*vQd{c+Rl0rVrp*R+N^S5BD18|6L3_ZqUv$PsTN_R})*Ug2 z+rDkTZui^vHww3G`H_Ir!@Ip+3rd9393~+?$uV4LkP!x17BWIaU?KvKuBnPVs}VJU zNtWmYkCU7-M=+c)-o*wg2(c3-(!(Uk5dr;;2o6Vre;`sSK?Y(3V}kP)0`zG*k}*=T zzkG=xM*S7!1*5BAh)WX@lOsqzc+l1C3@#%l%m*A-Fq|Q3PSP|iw~~!2NNU}QG@QBA8benZ)UY zPBb0Lv$-TuAqRD&O`n5lMTJo$OM0Cu_STJXJkRimug|4wM2PUrKa7;dCSJ7k1gKRD z_yvA|@8Ao_(QEVxuuJdL4fJ{NirOspOm_5At6-7(3LM}*&Cv-uM$6z1cuKzpFRAyS zo%%^_Q|rdsUZV1}-D5pi!GI}i>oj1>+A8>~U#nUAOj*mK-;}ip_f-#9$L2L<&2O(M z*;1GM%?jzpxU(@1GV~`Z>C|0Y$E23smbvE7&7)0^UD>8?qt&6tVtj7L;p(=;3WaD< zpMQdGSZJ4rojU1m@9g9)mV@~oO+n8PjX~abbrkru<;R10&8Z4JbzX_g$wQ2w#!8`C4Y=bSZ0+x-xKLbG7+d(&U JY2_}N{0*^(JlFsL delta 232 zcmZp8z})bFd4e>f<3t%}M#qf_8~8UXDpc`q?via|+AKKX4FBdk`Wgz%%B=pA*#q1+ zPGn@=>>s+7%S4$^gn|DT|7ZR;{7?CF`Bw3X@L%J5#;44GoNqS21K(GEL%tsVIKEoG zoqUdb*ZKYVmH2BWJ0yrO@e2bjIL5nKB<};G94}ii8(T1c2HzvzB=!=XL)@8M*SN|! zpK!W!EaJ)8tSDf}HaWQLA`6EML%_s|K9ftzSFrHNFtkjZ*tB_3MH0IlFaK