// Screens for Dashy
function LoginScreen({ onLogin }) {
const [pickedId, setPickedId] = React.useState('rod');
return (
Dashy
Pick up where you left off.
Sign in to your team workspace · murchison-auto
{USERS.map(u => (
))}
⌘ ⏎ to submit · e.preventDefault()}>Forgot password
04 May → 08 May
v0.1 · build 26.05.08
);
}
function Stat({ n, label }) {
return (
);
}
function BrandMark({ size = 22 }) {
return (
);
}
function TopBar({ me, isAdmin, tab, setTab, onAdd, onLogs, onLogout, onProfile, onDB }) {
return (
Dashy
murchison-auto
{isAdmin && (
)}
);
}
function Tab({ id, label, tab, setTab, user }) {
const active = tab === id;
return (
);
}
function HeadsUp({ items, onDismiss, onOpenTask }) {
if (!items.length) return null;
return (
{items.map(it => (
{it.kind === 'unsuccessful' ? '!' : it.kind === 'billing' ? '⇄' : '✦'}
))}
);
}
function OverviewScreen({ tasks, onOpen, onAddFor, density, onMoveTask }) {
const byUser = Object.fromEntries(USERS.map(u => [u.id, []]));
tasks.forEach(t => { if (byUser[t.assignee] && t.status !== 'closed') byUser[t.assignee].push(t); });
const [draggingTask, setDraggingTask] = React.useState(null);
const [dragOverCol, setDragOverCol] = React.useState(null);
return (
{USERS.map(u => (
onAddFor(u.id)}
density={density}
dragOver={dragOverCol === u.id && draggingTask && draggingTask.assignee !== u.id}
onDragOver={(uid) => setDragOverCol(uid)}
onDragLeave={() => setDragOverCol(prev => prev === u.id ? null : prev)}
onDragStartCard={(t) => setDraggingTask(t)}
onDragEndCard={() => { setDraggingTask(null); setDragOverCol(null); }}
draggingId={draggingTask && draggingTask.id}
onDropTask={(toId) => {
if (draggingTask && draggingTask.assignee !== toId) {
onMoveTask && onMoveTask(draggingTask.id, toId);
}
setDraggingTask(null); setDragOverCol(null);
}}
/>
))}
);
}
function Column({ user, tasks, onOpen, onAdd, density, onDropTask, dragOver, onDragOver, onDragLeave, onDragStartCard, onDragEndCard, draggingId }) {
return (
{ e.preventDefault(); onDragOver && onDragOver(user.id); }}
onDragLeave={(e) => { onDragLeave && onDragLeave(user.id); }}
onDrop={(e) => { e.preventDefault(); onDropTask && onDropTask(user.id); }}
>
{tasks.length === 0 && (
— inbox zero —
)}
{tasks.map(t => (
))}
);
}
function UserScreen({ user, tasks, onOpen, onAddFor, density }) {
const mine = tasks.filter(t => t.assignee === user.id);
const open = mine.filter(t => t.status === 'open');
const flagged = mine.filter(t => t.status === 'unsuccessful' || t.status === 'billing');
const closed = mine.filter(t => t.status === 'closed');
return (
{user.name}
{user.role} · {open.length + flagged.length} open tasks
{flagged.length > 0 && (
)}
{closed.length > 0 && (
)}
);
}
function Section({ title, sub, children }) {
return (
);
}
function AddTaskModal({ open, onClose, onSubmit, defaultAssignee, me }) {
const [title, setTitle] = React.useState('');
const [desc, setDesc] = React.useState('');
const [assignee, setAssignee] = React.useState(defaultAssignee || 'lani');
const [priority, setPriority] = React.useState('med');
React.useEffect(() => { if (open) setAssignee(defaultAssignee || 'lani'); }, [open, defaultAssignee]);
React.useEffect(() => { if (open) { setTitle(''); setDesc(''); setPriority('med'); } }, [open]);
if (!open) return null;
const submit = (e) => {
e && e.preventDefault();
if (!title.trim()) return;
onSubmit({ title: title.trim(), description: desc.trim(), assignee, priority });
};
return (
);
}
function Modal({ children, onClose, title, eyebrow, wide = false }) {
return (
e.stopPropagation()}>
{children}
);
}
function TaskDetail({ task, allAudit = [], onClose, onMove, onPriority, onComplete }) {
if (!task) return null;
const assignee = findUser(task.assignee);
const author = findUser(task.addedBy);
const audit = allAudit.filter(a => a.target === task.id);
if (audit.length === 0) {
audit.push({ at: task.addedAt, actor: task.addedBy, action: 'task_created', summary: '' });
}
return (
{task.status === 'unsuccessful' && (
Auto-marked Unsuccessful. Two missed bookings detected by the scheduler. Decide on next step or revert.
)}
{task.status === 'billing' && (
Form re-routed to Billing. Originally captured as Service Booking — keywords matched billing template.
)}
{task.description && (
{task.description}
)}
Notes & reminders
-
Customer wants pickup before Friday 3pm.
Lani · 09:14
-
Reminder set for Thu 4pm if no response by then.
auto · 2h before due
Activity
{audit.slice().reverse().map((row, i) => (
-
{row.actor === 'system' ? 'System' : (findUser(row.actor) || {}).name}
{row.action.replace(/_/g, ' ')}
{(row.detail || row.summary) &&
{row.detail || row.summary}
}
{fmtDateTime(row.at)}
))}
);
}
function Field({ label, children }) {
return (
);
}
function AuditScreen({ entries, onOpen }) {
const [filter, setFilter] = React.useState('all');
const groups = React.useMemo(() => {
const filtered = filter === 'all'
? entries
: filter === 'system'
? entries.filter(e => e.actor === 'system')
: entries.filter(e => e.actor === filter);
const out = {};
filtered.forEach(e => {
const day = new Date(e.at).toDateString();
(out[day] = out[day] || []).push(e);
});
return out;
}, [filter, entries]);
return (
{Object.entries(groups).map(([day, rows]) => (
{day}
{rows.map(r => {
const actor = r.actor === 'system' ? null : findUser(r.actor);
return (
-
{new Date(r.at).toLocaleTimeString('en-US',{hour:'numeric', minute:'2-digit'})}
{actor ? : SYS}
{actor ? actor.name : 'System'}
{r.action.replace(/_/g, ' ')}
{r.summary}
{r.target && (
)}
);
})}
))}
);
}
function FilterChip({ on, onClick, children }) {
return (
);
}
function SettingsScreen({ user, 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);
const [tab, setTab] = React.useState('profile');
const [pwOld, setPwOld] = React.useState('');
const [pwNew, setPwNew] = React.useState('');
const [pwConfirm, setPwConfirm] = React.useState('');
const [saved, setSaved] = React.useState(false);
React.useEffect(() => {
setName(user.name); setRole(user.role); setPhoto(user.photo || null);
}, [user.id]);
const fileInputRef = React.useRef(null);
const handleFile = (e) => {
const f = e.target.files && e.target.files[0];
if (!f) return;
const reader = new FileReader();
reader.onload = ev => setPhoto(ev.target.result);
reader.readAsDataURL(f);
};
const save = () => {
onSave({ name, role, photo });
setSaved(true);
setTimeout(() => setSaved(false), 1600);
};
const dirty = name !== user.name || role !== user.role || photo !== (user.photo || null);
return (
{tab === 'profile' && (
<>
Profile picture
{!photo &&
s[0]).join('').slice(0,2).toUpperCase()}} size={88} />}
{photo && (
)}
PNG or JPG, square crop recommended · max 2MB
Personal details
{saved && Saved}
>
)}
{tab === 'security' && (
<>
Change password
Sign-in & sessions
-
MacBook · Chrome · this device
Active now · Christchurch, NZ
current
-
iPhone · Safari
Last active 2h ago
>
)}
{tab === 'notifications' && (
<>
When should we ping you?
Channels
>
)}
{tab === 'workspace' && (
)}
);
}
function ToggleRow({ label, defaultOn = false }) {
const [on, setOn] = React.useState(defaultOn);
return (
{label}
);
}
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('');
const [newType, setNewType] = React.useState('standard');
const [wsName, setWsName] = React.useState('murchison-auto');
const [wsTz, setWsTz] = React.useState('Pacific/Auckland');
const submit = () => {
if (!newName.trim()) return;
onCreateUser({ name: newName.trim(), role: newRole.trim() || 'Team member', account_type: newType });
setNewName(''); setNewRole(''); setNewType('standard'); setAdding(false);
};
return (
<>
Switch user
Test other accounts without signing out.
{dbUsers.map(u => (
))}
Team members {!isAdmin && — admin only}
{isAdmin && !adding && (
)}
{isAdmin && adding && (
)}
Workspace {!isAdmin && — admin only}
>
);
}
Object.assign(window, {
LoginScreen, TopBar, OverviewScreen, UserScreen, AddTaskModal, Modal, TaskDetail, AuditScreen, HeadsUp, BrandMark,
SettingsScreen,
});