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
### 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.
+26
View File
@@ -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');
}
+38 -1
View File
@@ -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");
}
}}
/>
)}
+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)):
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()
+6
View File
@@ -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:
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 [role, setRole] = React.useState(user.role);
const [photo, setPhoto] = React.useState(user.photo || null);
@@ -804,7 +804,7 @@ function SettingsScreen({ user, isAdmin, onClose, onSave, onLogout, onSwitchUser
{tab === 'workspace' && (
<WorkspaceTab
user={user} isAdmin={isAdmin}
user={user} isAdmin={isAdmin} dbUsers={dbUsers}
onSwitchUser={onSwitchUser}
onCreateUser={onCreateUser}
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 [newName, setNewName] = React.useState('');
const [newRole, setNewRole] = React.useState('');