// Screens for Dashy function LoginScreen({ onLogin, dbUsers = [], workspace }) { const [username, setUsername] = React.useState(''); const [password, setPassword] = React.useState(''); const [error, setError] = React.useState(''); const [busy, setBusy] = React.useState(false); const submit = async () => { if (!username) { setError('Enter your username'); return; } if (!password) { setError('Enter your password'); return; } setError(''); setBusy(true); try { await onLogin(username, password); } catch (e) { setError('Incorrect username or password'); } finally { setBusy(false); } }; return (
Dashy

Sign in to Dashy

Enter your details to access the {workspace ? workspace.name : 'loading…'} workspace

{error &&
{error}
}

⌘ ⏎ to submit · e.preventDefault()}>Forgot password

04 May → 08 May
v0.1 · build 26.05.08
); } function Stat({ n, label }) { return (
{n}
{label}
); } function BrandMark({ size = 22 }) { return ( ); } function TopBar({ me, dbUsers = [], isAdmin, tab, setTab, onAdd, onLogs, onLogout, onProfile, workspace, searchQuery, setSearchQuery, showSearch, onToggleSearch }) { return (
{showSearch ? (
setSearchQuery(e.target.value)} />
) : ( <> Dashy {workspace ? workspace.name : 'loading…'} )}
{!showSearch && ( )}
{!showSearch && ( <> )} {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' ? '⇄' : '✦'}
{it.title}
{it.sub}
))}
); } function OverviewScreen({ tasks, onOpen, onAddFor, density, onMoveTask, dbUsers = [] }) { const byUser = Object.fromEntries(dbUsers.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); const [dragOverTaskId, setDragOverTaskId] = React.useState(null); const [dropSide, setDropSide] = React.useState('bottom'); // 'top' or 'bottom' return (
{dbUsers.map(u => ( onAddFor(u.id)} density={density} dragOver={dragOverCol === u.id && draggingTask && draggingTask.assignee !== u.id} onDragOver={(uid) => setDragOverCol(uid)} onDragLeave={() => { setDragOverCol(null); setDragOverTaskId(null); }} onDragStartCard={(t) => { // Use a tiny timeout to ensure the drag image is captured before we hide the element setTimeout(() => setDraggingTask(t), 0); }} onDragEndCard={() => { setDraggingTask(null); setDragOverCol(null); setDragOverTaskId(null); }} draggingId={draggingTask && draggingTask.id} dragOverTaskId={dragOverTaskId} dropSide={dropSide} onDragOverTask={(tid, side) => { setDragOverTaskId(tid); setDropSide(side); }} onDropTask={(toId) => { if (!draggingTask) return; // Calculate position using the list that DOES NOT include the dragging task const colTasks = byUser[toId].filter(t => t.id !== draggingTask.id); let newPos = 0; if (dragOverTaskId) { const idx = colTasks.findIndex(t => t.id === dragOverTaskId); if (dropSide === 'top') { const prev = colTasks[idx - 1]; newPos = prev ? (colTasks[idx].position + prev.position) / 2 : colTasks[idx].position / 2; } else { const next = colTasks[idx + 1]; newPos = next ? (colTasks[idx].position + next.position) / 2 : colTasks[idx].position + 1000; } } else { // Dropped on empty area or no specific task const last = colTasks[colTasks.length - 1]; newPos = last ? last.position + 1000 : 1000; } onMoveTask && onMoveTask(draggingTask.id, toId, newPos); setDraggingTask(null); setDragOverCol(null); setDragOverTaskId(null); }} /> ))}
); } function Column({ user, title, icon, tasks, onOpen, onAdd, density, onDropTask, dragOver, onDragOver, onDragLeave, onDragStartCard, onDragEndCard, draggingId, dragOverTaskId, dropSide, onDragOverTask, colId, faded }) { const columnId = colId || (user ? user.id : title); return (
{ e.preventDefault(); onDragOver && onDragOver(columnId); }} onDragLeave={(e) => { if (e.relatedTarget && !e.currentTarget.contains(e.relatedTarget)) { onDragLeave && onDragLeave(columnId); } }} onDrop={(e) => { e.preventDefault(); onDropTask && onDropTask(columnId); }} >
{user ? ( <>

{user.name}

{user.role}
) : ( <> {icon && {icon}}

{title}

)}
{tasks.length} {onAdd && ( )}
{ e.preventDefault(); if (e.target === e.currentTarget) { onDragOverTask(null, 'bottom'); } }}> {tasks.length === 0 && !dragOver && (
— inbox zero —
)} {tasks.length === 0 && dragOver && (
)} {tasks.map(t => { const isOver = dragOverTaskId === t.id; const isTop = isOver && dropSide === 'top'; const isBottom = isOver && dropSide === 'bottom'; return (
{isOver && (
)} { e.preventDefault(); const rect = e.currentTarget.getBoundingClientRect(); const mid = rect.top + rect.height / 2; onDragOverTask(t.id, e.clientY < mid ? 'top' : 'bottom'); }} />
); })}
); } function UserScreen({ user, tasks, onOpen, onAddFor, density, onMoveTask }) { 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'); const [draggingTask, setDraggingTask] = React.useState(null); const [dragOverCol, setDragOverCol] = React.useState(null); const [dragOverTaskId, setDragOverTaskId] = React.useState(null); const [dropSide, setDropSide] = React.useState('bottom'); const onDrop = (status) => { if (!draggingTask) return; const targetTasks = status === 'flagged' ? flagged : (status === 'open' ? open : closed); const cleanTarget = targetTasks.filter(t => t.id !== draggingTask.id); let newPos = 0; if (dragOverTaskId) { const idx = cleanTarget.findIndex(t => t.id === dragOverTaskId); if (dropSide === 'top') { const prev = cleanTarget[idx - 1]; newPos = prev ? (cleanTarget[idx].position + prev.position) / 2 : cleanTarget[idx].position / 2; } else { const next = cleanTarget[idx + 1]; newPos = next ? (cleanTarget[idx].position + next.position) / 2 : cleanTarget[idx].position + 1000; } } else { const last = cleanTarget[cleanTarget.length - 1]; newPos = last ? last.position + 1000 : 1000; } // Determine target status let targetStatus = status; if (status === 'flagged') targetStatus = 'unsuccessful'; // Default flagged status if (status === 'closed') targetStatus = 'closed'; if (status === 'open') targetStatus = 'open'; onMoveTask && onMoveTask(draggingTask.id, user.id, newPos, targetStatus); setDraggingTask(null); setDragOverCol(null); setDragOverTaskId(null); }; return (

{user.name}

{user.role} · {open.length + flagged.length} open tasks

{ setDragOverCol(null); setDragOverTaskId(null); }} onDragStartCard={(t) => { setTimeout(() => setDraggingTask(t), 0); }} onDragEndCard={() => { setDraggingTask(null); setDragOverCol(null); setDragOverTaskId(null); }} draggingId={draggingTask?.id} dragOverTaskId={dragOverTaskId} dropSide={dropSide} onDragOverTask={(tid, side) => { setDragOverTaskId(tid); setDropSide(side); }} onDropTask={onDrop} /> { setDragOverCol(null); setDragOverTaskId(null); }} onDragStartCard={(t) => { setTimeout(() => setDraggingTask(t), 0); }} onDragEndCard={() => { setDraggingTask(null); setDragOverCol(null); setDragOverTaskId(null); }} draggingId={draggingTask?.id} dragOverTaskId={dragOverTaskId} dropSide={dropSide} onDragOverTask={(tid, side) => { setDragOverTaskId(tid); setDropSide(side); }} onDropTask={onDrop} /> !draggingTask || t.id !== draggingTask.id)} onOpen={onOpen} density={density} dragOver={dragOverCol === 'closed'} onDragOver={setDragOverCol} onDragLeave={() => { setDragOverCol(null); setDragOverTaskId(null); }} onDragStartCard={(t) => { setTimeout(() => setDraggingTask(t), 0); }} onDragEndCard={() => { setDraggingTask(null); setDragOverCol(null); setDragOverTaskId(null); }} draggingId={draggingTask?.id} dragOverTaskId={dragOverTaskId} dropSide={dropSide} onDragOverTask={(tid, side) => { setDragOverTaskId(tid); setDropSide(side); }} onDropTask={onDrop} />
); } function Section({ title, sub, children }) { return (

{title}

{sub && {sub}}
{children}
); } function AddTaskModal({ open, onClose, onSubmit, defaultAssignee, me, dbUsers = [] }) { 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 (