Fixed react refresh bug and fixed white background when clicking on task which was a TASK_AUDIT hangover from react local storagedb now pointing too python fastapi

This commit is contained in:
NPS Agent
2026-05-11 14:04:13 +09:30
parent 3825c7556b
commit 49dc767922
10 changed files with 156 additions and 656 deletions
+130 -71
View File
@@ -1,4 +1,4 @@
// Dashy — main app (DB-backed via SQLite/WASM)
// Dashy — main app (API-backed)
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"theme": "light",
@@ -9,28 +9,59 @@ const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
const ACCENTS = ['#2A6FDB', '#1F8A5B', '#D97757', '#7A5AF8'];
function useDashyDB() {
const [ready, setReady] = React.useState(DashyDB.isReady);
const [, force] = React.useReducer(x => x + 1, 0);
function useApiData(authed) {
const [data, setData] = React.useState({ tasks: [], users: [], audit: [] });
const [loading, setLoading] = React.useState(true);
React.useEffect(() => {
if (!DashyDB.isReady) DashyDB.init().then(() => setReady(true));
const off = DashyDB.subscribe(() => force());
return off;
}, []);
return ready;
if (!authed) return;
let mounted = true;
const load = async () => {
try {
const [tasks, users, audit] = await Promise.all([
api.getTasks(),
api.getUsers(),
api.getAudit()
]);
if (mounted) {
setData({ tasks, users, audit });
setLoading(false);
}
} catch (e) {
console.error("Failed to load data:", e);
}
};
load();
const off = api.subscribe(load);
return () => { mounted = false; off(); };
}, [authed]);
return { ...data, loading };
}
function App() {
const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
const ready = useDashyDB();
const [authed, setAuthed] = React.useState(false);
const [meId, setMeId] = React.useState('rod');
const [authed, setAuthed] = React.useState(!!api.token);
// Optional: Decode token to get meId if needed, but for prototype we just assume rod if not known yet,
// or decode JWT using a simple base64 parse.
const [meId, setMeId] = React.useState(() => {
if (api.token) {
try {
return JSON.parse(atob(api.token.split('.')[1])).sub;
} catch(e) {}
}
return 'rod';
});
const { tasks, users: dbUsers, audit, loading } = useApiData(authed);
const [tab, setTab] = React.useState('overview');
const [adding, setAdding] = React.useState(null);
const [openTaskId, setOpenTaskId] = React.useState(null);
const [showLogs, setShowLogs] = React.useState(false);
const [showSettings, setShowSettings] = React.useState(false);
const [showDB, setShowDB] = React.useState(false);
const [headsUp, setHeadsUp] = React.useState([
{ id: 'h1', kind: 'unsuccessful', taskId: 't7',
title: 'WO #2188 auto-marked Unsuccessful',
@@ -40,56 +71,91 @@ function App() {
sub: 'K. Wynne · originally Service Booking' },
]);
if (!ready) return <BootSplash />;
if (!authed) {
return <LoginScreen onLogin={async (id, pwd = "password123") => {
try {
await api.login(id, pwd);
setMeId(id);
setAuthed(true);
// Fire & forget audit log
api.addAudit({ actor: id, action: 'login', summary: 'Signed in' }).catch(console.error);
} catch (e) {
alert("Login failed: " + e.message);
}
}} />;
}
if (loading) return <BootSplash />;
const tasks = DashyDB.listTasks();
const audit = DashyDB.listAudit();
const dbUsers = DashyDB.listUsers();
const userMap = Object.fromEntries(dbUsers.map(u => [u.id, u]));
const merge = (id) => {
const base = findUser(id); if (!base && !userMap[id]) return null;
const base = findUser(id);
if (!base && !userMap[id]) return null;
const live = userMap[id] || {};
if (!base) {
// user added at runtime
return { id, name: live.name, role: live.role, hue: live.hue, initials: live.initials,
photo: live.photo || null, account_type: live.account_type || 'standard' };
}
return { ...base, name: live.name || base.name, role: live.role || base.role,
photo: live.photo || null, account_type: live.account_type || 'standard' };
};
const me = merge(meId);
const me = merge(meId) || merge('rod');
const isAdmin = me && me.account_type === 'admin';
const openTask = tasks.find(x => x.id === openTaskId);
const handleLogin = (id) => {
setMeId(id); setAuthed(true);
DashyDB.addAudit({
actor: id, action: 'login',
summary: (merge(id) || {}).name + ' signed in',
});
const addTask = async ({ title, description, assignee, priority }) => {
try {
const t = await api.createTask({
title, description, assignee_id: assignee, priority,
added_by: meId, source: 'manual', status: 'open', tags: []
});
await api.addAudit({
actor: meId, action: 'task_created',
summary: 'Created task "' + title + '" for ' + (merge(assignee)||{}).name,
target: t.id
});
setAdding(null);
} catch(e) {
console.error(e);
alert("Failed to create task");
}
};
const addTask = ({ title, description, assignee, priority }) => {
const id = 't_' + Date.now().toString(36);
const at = new Date('2026-05-08T10:30:00').toISOString();
DashyDB.createTask({
id, title, description, assignee, priority,
addedBy: meId, source: 'manual', status: 'open', addedAt: at, tags: []
});
DashyDB.addAudit({
at, actor: meId, action: 'task_created',
summary: 'Created task "' + title + '" for ' + (merge(assignee)||{}).name,
target: id
});
setAdding(null);
const moveTask = async (taskId, toUserId) => {
try {
await api.updateTask(taskId, { assignee_id: toUserId });
await api.addAudit({ actor: meId, action: 'task_moved', summary: 'Moved task to ' + (merge(toUserId)||{}).name, target: taskId });
} catch(e) {}
};
const setPriority = async (taskId, p) => {
try {
await api.updateTask(taskId, { priority: p });
} catch(e) {}
};
const moveTask = (taskId, toUserId) => DashyDB.moveTask(taskId, toUserId, meId);
const setPriority = (taskId, p) => DashyDB.setPriority(taskId, p);
const dismissHU = (id) => setHeadsUp(h => h.filter(x => x.id !== id));
const openTaskFromAnywhere = (id) => { setOpenTaskId(id); setShowLogs(false); };
if (!authed) return <LoginScreen onLogin={handleLogin} />;
// Map API fields to frontend component expectations
const frontendTasks = tasks.map(t => ({
...t,
assignee: t.assignee_id,
addedBy: t.added_by,
addedAt: t.added_at,
tags: t.tags.map(tagObj => tagObj.tag)
}));
const frontendAudit = audit.map(a => ({
...a,
actor: a.actor,
action: a.action,
summary: a.summary,
target: a.target,
at: a.at
}));
const mappedOpenTask = frontendTasks.find(x => x.id === openTaskId);
return (
<div className="app">
@@ -101,7 +167,6 @@ function App() {
onAdd={() => setAdding(meId)}
onLogs={() => setShowLogs(true)}
onProfile={() => setShowSettings(true)}
onDB={() => setShowDB(true)}
/>
<HeadsUp items={headsUp} onDismiss={dismissHU} onOpenTask={openTaskFromAnywhere} />
@@ -109,7 +174,7 @@ function App() {
<main className="main">
{tab === 'overview' && (
<OverviewScreen
tasks={tasks} density={t.density}
tasks={frontendTasks} density={t.density}
onOpen={(task) => setOpenTaskId(task.id)}
onAddFor={(uid) => setAdding(uid)}
onMoveTask={moveTask}
@@ -117,7 +182,7 @@ function App() {
)}
{tab !== 'overview' && (
<UserScreen
user={merge(tab)} tasks={tasks} density={t.density}
user={merge(tab)} tasks={frontendTasks} density={t.density}
onOpen={(task) => setOpenTaskId(task.id)}
onAddFor={(uid) => setAdding(uid)}
/>
@@ -125,41 +190,40 @@ function App() {
</main>
<AddTaskModal open={!!adding} onClose={() => setAdding(null)} onSubmit={addTask} defaultAssignee={adding} me={me} />
{openTask && (
<TaskDetail task={openTask} onClose={() => setOpenTaskId(null)} onMove={moveTask} onPriority={setPriority} />
{mappedOpenTask && (
<TaskDetail task={mappedOpenTask} allAudit={frontendAudit} onClose={() => setOpenTaskId(null)} onMove={moveTask} onPriority={setPriority} />
)}
{showLogs && (
<Modal title="Audit log" onClose={() => setShowLogs(false)} wide>
<AuditScreen entries={audit} onOpen={openTaskFromAnywhere} />
<AuditScreen entries={frontendAudit} onOpen={openTaskFromAnywhere} />
</Modal>
)}
{showSettings && (
<SettingsScreen
user={me}
dbUsers={dbUsers}
isAdmin={isAdmin}
onClose={() => setShowSettings(false)}
onSave={(edits) => {
DashyDB.updateUser(meId, edits);
DashyDB.addAudit({ actor: meId, action: 'profile_updated', summary: 'Updated profile details' });
onSave={async (edits) => {
// Not implemented on backend yet for user updating, mock success
setShowSettings(false);
}}
onLogout={() => { setShowSettings(false); setAuthed(false); }}
onSwitchUser={(id) => { setMeId(id); setShowSettings(false); }}
onCreateUser={(u) => {
const id = DashyDB.createUser(u);
DashyDB.addAudit({ actor: meId, action: 'user_created', summary: 'Added ' + u.name + ' (' + (u.account_type||'standard') + ')', target: id });
onLogout={() => {
api.logout();
setAuthed(false);
setShowSettings(false);
}}
onDeleteUser={(id) => {
const u = userMap[id];
DashyDB.deleteUser(id);
DashyDB.addAudit({ actor: meId, action: 'user_deleted', summary: 'Removed ' + (u?u.name:id), target: null });
}}
onUpdateUserRole={(id, edits) => {
DashyDB.updateUser(id, edits);
DashyDB.addAudit({ actor: meId, action: 'user_updated', summary: 'Updated ' + (userMap[id]?userMap[id].name:id) + ' permissions', target: null });
onSwitchUser={async (id) => {
try {
await api.login(id, "password123");
setMeId(id);
setShowSettings(false);
} catch(e) {
alert("Failed to switch user");
}
}}
/>
)}
{showDB && isAdmin && <DatabaseInspector onClose={() => setShowDB(false)} />}
<DashyTweaks t={t} setTweak={setTweak} />
</div>
@@ -170,7 +234,7 @@ function BootSplash() {
return (
<div className="boot">
<div className="boot__pulse" />
<div className="boot__label mono">opening dashy.db</div>
<div className="boot__label mono">loading from API</div>
</div>
);
}
@@ -188,11 +252,6 @@ function DashyTweaks({ t, setTweak }) {
onChange={v => setTweak('density', v)} />
<TweakToggle label="Show tags on cards" value={t.showTags} onChange={v => setTweak('showTags', v)} />
</TweakSection>
<TweakSection title="Database">
<TweakButton label="Reset SQLite" onClick={() => { if (confirm('Wipe and reseed dashy.db?')) DashyDB.reset(); }}>
Reset
</TweakButton>
</TweakSection>
</TweaksPanel>
);
}