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:
+9
-4
@@ -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.
|
||||||
|
|||||||
@@ -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');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ function App() {
|
|||||||
setAuthed(false);
|
setAuthed(false);
|
||||||
setShowSettings(false);
|
setShowSettings(false);
|
||||||
}}
|
}}
|
||||||
onSwitchUser={async (id) => {
|
onSwitchUser={async (id) => {
|
||||||
try {
|
try {
|
||||||
await api.login(id, "password123");
|
await api.login(id, "password123");
|
||||||
setMeId(id);
|
setMeId(id);
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
+3
-3
@@ -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('');
|
||||||
|
|||||||
Reference in New Issue
Block a user