// Automations: "When … then …" recipes for a board. import { html, useState } from '../vendor/preact-htm.mjs'; import { api } from './api.js'; import { state, useStore, loadBoard, toast } from './store.js'; import { Icon, ModalHeader, confirmDialog } from './ui.js'; const TRIGGERS = { status_change: 'When a status changes', column_change: 'When a column changes', item_created: 'When an item is created', date_arrives: 'When a date arrives', }; const ACTIONS = { notify: 'notify someone', move_to_group: 'move the item to a group', set_status: 'set a status', assign: 'assign a person', set_date_today: "set a date to today", clear_column: 'clear a column', archive: 'archive the item', }; export function AutomationsModal({ boardId }) { const s = useStore(); const b = s.boardData[boardId]; const [adding, setAdding] = useState(false); if (!b) return null; const toggle = async (a) => { await api.patch(`/api/automations/${a.id}`, { enabled: !a.enabled }); loadBoard(boardId); }; const remove = async (a) => { if (await confirmDialog({ title: 'Delete this automation?' })) { await api.del(`/api/automations/${a.id}`); loadBoard(boardId); } }; return html`
<${ModalHeader} title="Automations" sub="Let the board do the repetitive work." /> ${b.automations.map((a) => html`
<${Icon} name="bolt" /> ${describe(b, a.trigger, a.action)}
`)} ${!b.automations.length && !adding ? html`

No automations yet. Some ideas: notify the owner when an item is stuck, move items to "Done" when their status is Done, remind people on their due date.

` : null} ${adding ? html`<${Builder} board=${b} done=${() => setAdding(false)} />` : html``}
`; } function describe(b, t, a) { const col = (id) => b.columns.find((c) => c.id === id)?.title || '?'; const label = (cid, lid) => b.columns.find((c) => c.id === cid)?.settings.labels?.find((l) => l.id === lid)?.name || 'anything'; const group = (id) => b.groups.find((g) => g.id === id)?.title || '?'; const user = (id) => state.usersById[id]?.name || '?'; let when = ''; if (t.type === 'status_change') when = html`When ${col(t.column_id)} changes to ${t.to ? label(t.column_id, t.to) : 'anything'}`; else if (t.type === 'column_change') when = html`When ${col(t.column_id)} changes`; else if (t.type === 'item_created') when = html`When an item is created`; else if (t.type === 'date_arrives') when = html`When ${col(t.column_id)} arrives`; let then = ''; if (a.type === 'notify') then = html`notify ${a.to === 'user' ? user(a.user_id) : `people in ${col(a.column_id)}`}`; else if (a.type === 'move_to_group') then = html`move item to ${group(a.group_id)}`; else if (a.type === 'set_status') then = html`set ${col(a.column_id)} to ${label(a.column_id, a.label)}`; else if (a.type === 'assign') then = html`assign ${user(a.user_id)} in ${col(a.column_id)}`; else if (a.type === 'set_date_today') then = html`set ${col(a.column_id)} to today`; else if (a.type === 'clear_column') then = html`clear ${col(a.column_id)}`; else if (a.type === 'archive') then = html`archive the item`; return html`${when}, ${then}`; } function Builder({ board, done }) { const cols = (types) => board.columns.filter((c) => types.includes(c.type)); const [t, setT] = useState({ type: 'status_change', column_id: cols(['status'])[0]?.id, to: '' }); const [a, setA] = useState({ type: 'notify', to: 'people_column', column_id: cols(['people'])[0]?.id }); const statusCol = board.columns.find((c) => c.id === t.column_id); const save = async () => { try { await api.post(`/api/boards/${board.board.id}/automations`, { trigger: { ...t, to: t.to || null }, action: a }); loadBoard(board.board.id); done(); } catch (e) { toast(e.message, 'error'); } }; const colSelect = (types, value, onChange) => html``; const valid = (t.type === 'item_created' || t.column_id) && ( a.type === 'archive' || (a.type === 'notify' && (a.to === 'user' ? a.user_id : a.column_id)) || (a.type === 'move_to_group' && a.group_id) || (a.type === 'set_status' && a.column_id && a.label) || (a.type === 'assign' && a.column_id && a.user_id) || (['set_date_today', 'clear_column'].includes(a.type) && a.column_id)); return html`
${t.type === 'status_change' ? html`${colSelect(['status'], t.column_id, (v) => setT({ ...t, column_id: v, to: '' }))} changes to ` : null} ${t.type === 'column_change' ? colSelect(board.columns.map((c) => c.type), t.column_id, (v) => setT({ ...t, column_id: v })) : null} ${t.type === 'date_arrives' ? colSelect(['date', 'timeline'], t.column_id, (v) => setT({ ...t, column_id: v })) : null}
then ${a.type === 'notify' ? html` ${a.to === 'user' ? html`` : colSelect(['people'], a.column_id, (v) => setA({ ...a, column_id: v }))} setA({ ...a, text: e.target.value })} />` : null} ${a.type === 'move_to_group' ? html`` : null} ${a.type === 'set_status' ? html`${colSelect(['status'], a.column_id, (v) => setA({ ...a, column_id: v, label: '' }))} ` : null} ${a.type === 'assign' ? html`${colSelect(['people'], a.column_id, (v) => setA({ ...a, column_id: v }))} ` : null} ${a.type === 'set_date_today' ? colSelect(['date'], a.column_id, (v) => setA({ ...a, column_id: v })) : null} ${a.type === 'clear_column' ? colSelect(board.columns.map((c) => c.type), a.column_id, (v) => setA({ ...a, column_id: v })) : null}
`; }