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

This commit is contained in:
NPS Agent
2026-05-11 15:23:49 +09:30
parent d959c89d5f
commit 39d26be447
7 changed files with 128 additions and 8 deletions
+9 -4
View File
@@ -41,13 +41,18 @@
## ⏭️ Upcoming Steps ## ⏭️ Upcoming Steps
### Phase 2: Frontend Refactor (✅ Completed) ### Phase 2: Frontend Refactor & Workflow Polish (✅ Completed)
1. **API Service:** Created `api.js` to handle all network requests to the FastAPI backend. 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. 2. **Authentication Hook:** Updated the Login screen to use real JWT tokens.
3. **Component Updates:** 3. **Component Updates:**
- Swapped `DashyDB` calls for `async` API calls in `app.jsx`. - Swapped `DashyDB` calls for `async` API calls in `app.jsx`.
- Implemented "Loading" states for UI responsiveness during network calls. - Fixed UI state refresh issues by silently loading subsequent data updates.
4. **Cleanup:** Archived `db.js`, `data.jsx`, and removed `sql.js` WASM dependency from `Dashy.html`. 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 ### Phase 3: Advanced Features
- **Real-time Notifications:** Explore WebSockets for task assignments. - **Real-time Notifications:** Explore WebSockets for task assignments.
+26
View File
@@ -66,6 +66,32 @@ class ApiService {
return this.request('/users'); 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() { async getTasks() {
return this.request('/tasks'); return this.request('/tasks');
} }
+37
View File
@@ -244,6 +244,43 @@ function App() {
alert("Failed to switch user"); 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");
}
}}
/> />
)} )}
+46
View File
@@ -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)): def read_users(db: Session = Depends(get_db)):
return db.query(models.User).all() 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]) @app.get("/tasks", response_model=List[schemas.Task])
def read_tasks(db: Session = Depends(get_db)): def read_tasks(db: Session = Depends(get_db)):
return db.query(models.Task).all() return db.query(models.Task).all()
+6
View File
@@ -20,6 +20,12 @@ class UserLogin(BaseModel):
id: str id: str
password: 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): class User(UserBase):
created_at: datetime created_at: datetime
class Config: class Config:
BIN
View File
Binary file not shown.
+3 -3
View File
@@ -626,7 +626,7 @@ function FilterChip({ on, onClick, children }) {
); );
} }
function SettingsScreen({ user, isAdmin, onClose, onSave, onLogout, onSwitchUser, onCreateUser, onDeleteUser, onUpdateUserRole }) { function SettingsScreen({ user, dbUsers, isAdmin, onClose, onSave, onLogout, onSwitchUser, onCreateUser, onDeleteUser, onUpdateUserRole }) {
const [name, setName] = React.useState(user.name); const [name, setName] = React.useState(user.name);
const [role, setRole] = React.useState(user.role); const [role, setRole] = React.useState(user.role);
const [photo, setPhoto] = React.useState(user.photo || null); const [photo, setPhoto] = React.useState(user.photo || null);
@@ -804,7 +804,7 @@ function SettingsScreen({ user, isAdmin, onClose, onSave, onLogout, onSwitchUser
{tab === 'workspace' && ( {tab === 'workspace' && (
<WorkspaceTab <WorkspaceTab
user={user} isAdmin={isAdmin} user={user} isAdmin={isAdmin} dbUsers={dbUsers}
onSwitchUser={onSwitchUser} onSwitchUser={onSwitchUser}
onCreateUser={onCreateUser} onCreateUser={onCreateUser}
onDeleteUser={onDeleteUser} onDeleteUser={onDeleteUser}
@@ -829,7 +829,7 @@ function ToggleRow({ label, defaultOn = false }) {
); );
} }
function WorkspaceTab({ user, isAdmin, dbUsers, onSwitchUser, onCreateUser, onDeleteUser, onUpdateUserRole }) { function WorkspaceTab({ user, isAdmin, dbUsers = [], onSwitchUser, onCreateUser, onDeleteUser, onUpdateUserRole }) {
const [adding, setAdding] = React.useState(false); const [adding, setAdding] = React.useState(false);
const [newName, setNewName] = React.useState(''); const [newName, setNewName] = React.useState('');
const [newRole, setNewRole] = React.useState(''); const [newRole, setNewRole] = React.useState('');