Initial commit -- just started to factor and implement python fast API backend
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
// Dashy — main app (DB-backed via SQLite/WASM)
|
||||
|
||||
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
|
||||
"theme": "light",
|
||||
"accent": "#2A6FDB",
|
||||
"density": "cozy",
|
||||
"showTags": true
|
||||
}/*EDITMODE-END*/;
|
||||
|
||||
const ACCENTS = ['#2A6FDB', '#1F8A5B', '#D97757', '#7A5AF8'];
|
||||
|
||||
function useDashyDB() {
|
||||
const [ready, setReady] = React.useState(DashyDB.isReady);
|
||||
const [, force] = React.useReducer(x => x + 1, 0);
|
||||
React.useEffect(() => {
|
||||
if (!DashyDB.isReady) DashyDB.init().then(() => setReady(true));
|
||||
const off = DashyDB.subscribe(() => force());
|
||||
return off;
|
||||
}, []);
|
||||
return ready;
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
|
||||
const ready = useDashyDB();
|
||||
const [authed, setAuthed] = React.useState(false);
|
||||
const [meId, setMeId] = React.useState('rod');
|
||||
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',
|
||||
sub: 'Two missed bookings — assigned to Kirra for review' },
|
||||
{ id: 'h2', kind: 'billing', taskId: 't4',
|
||||
title: 'Form response switched to Billing',
|
||||
sub: 'K. Wynne · originally Service Booking' },
|
||||
]);
|
||||
|
||||
if (!ready) 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 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 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 = ({ 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 = (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} />;
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<TopBar
|
||||
me={me}
|
||||
isAdmin={isAdmin}
|
||||
tab={tab}
|
||||
setTab={setTab}
|
||||
onAdd={() => setAdding(meId)}
|
||||
onLogs={() => setShowLogs(true)}
|
||||
onProfile={() => setShowSettings(true)}
|
||||
onDB={() => setShowDB(true)}
|
||||
/>
|
||||
|
||||
<HeadsUp items={headsUp} onDismiss={dismissHU} onOpenTask={openTaskFromAnywhere} />
|
||||
|
||||
<main className="main">
|
||||
{tab === 'overview' && (
|
||||
<OverviewScreen
|
||||
tasks={tasks} density={t.density}
|
||||
onOpen={(task) => setOpenTaskId(task.id)}
|
||||
onAddFor={(uid) => setAdding(uid)}
|
||||
onMoveTask={moveTask}
|
||||
/>
|
||||
)}
|
||||
{tab !== 'overview' && (
|
||||
<UserScreen
|
||||
user={merge(tab)} tasks={tasks} density={t.density}
|
||||
onOpen={(task) => setOpenTaskId(task.id)}
|
||||
onAddFor={(uid) => setAdding(uid)}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
|
||||
<AddTaskModal open={!!adding} onClose={() => setAdding(null)} onSubmit={addTask} defaultAssignee={adding} me={me} />
|
||||
{openTask && (
|
||||
<TaskDetail task={openTask} onClose={() => setOpenTaskId(null)} onMove={moveTask} onPriority={setPriority} />
|
||||
)}
|
||||
{showLogs && (
|
||||
<Modal title="Audit log" onClose={() => setShowLogs(false)} wide>
|
||||
<AuditScreen entries={audit} onOpen={openTaskFromAnywhere} />
|
||||
</Modal>
|
||||
)}
|
||||
{showSettings && (
|
||||
<SettingsScreen
|
||||
user={me}
|
||||
isAdmin={isAdmin}
|
||||
onClose={() => setShowSettings(false)}
|
||||
onSave={(edits) => {
|
||||
DashyDB.updateUser(meId, edits);
|
||||
DashyDB.addAudit({ actor: meId, action: 'profile_updated', summary: 'Updated profile details' });
|
||||
}}
|
||||
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 });
|
||||
}}
|
||||
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 });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showDB && isAdmin && <DatabaseInspector onClose={() => setShowDB(false)} />}
|
||||
|
||||
<DashyTweaks t={t} setTweak={setTweak} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BootSplash() {
|
||||
return (
|
||||
<div className="boot">
|
||||
<div className="boot__pulse" />
|
||||
<div className="boot__label mono">opening dashy.db…</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DashyTweaks({ t, setTweak }) {
|
||||
return (
|
||||
<TweaksPanel title="Tweaks">
|
||||
<TweakSection title="Appearance">
|
||||
<TweakRadio label="Theme" value={t.theme}
|
||||
options={[{value:'light',label:'Light'},{value:'dark',label:'Dark'}]}
|
||||
onChange={v => setTweak('theme', v)} />
|
||||
<TweakColor label="Accent" value={t.accent} options={ACCENTS} onChange={v => setTweak('accent', v)} />
|
||||
<TweakRadio label="Density" value={t.density}
|
||||
options={[{value:'compact',label:'Compact'},{value:'cozy',label:'Cozy'}]}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
function ThemeBridge() {
|
||||
const [t] = useTweaks(TWEAK_DEFAULTS);
|
||||
React.useEffect(() => {
|
||||
document.documentElement.dataset.theme = t.theme;
|
||||
document.documentElement.style.setProperty('--accent', t.accent);
|
||||
document.documentElement.dataset.showTags = t.showTags ? 'on' : 'off';
|
||||
}, [t.theme, t.accent, t.showTags]);
|
||||
return null;
|
||||
}
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(<><ThemeBridge /><App /></>);
|
||||
Reference in New Issue
Block a user