// Board-level dialogs: activity log, CSV import, column settings. import { html, useState, useEffect } from '../vendor/preact-htm.mjs'; import { api } from './api.js'; import { state, useStore, loadBoard, navigate, boardPath, toast } from './store.js'; import { Icon, Avatar, ModalHeader, closeModal, timeAgo, Spinner, LABEL_COLORS, textOn } from './ui.js'; import { COLUMN_TYPES } from './cells.js'; import { activityText } from './item.js'; import * as A from './actions.js'; export function ActivityModal({ boardId }) { const [rows, setRows] = useState(null); const [who, setWho] = useState(''); useEffect(() => { api.get(`/api/boards/${boardId}/activity`).then(setRows); }, [boardId]); const shown = (rows || []).filter((a) => !who || a.user_id === who); const people = [...new Set((rows || []).map((a) => a.user_id).filter(Boolean))]; return html`
<${ModalHeader} title="Activity log" sub=${state.boardData[boardId]?.board.name} />
${people.map((u) => html``)}
${!rows ? html`<${Spinner} />` : shown.map((a) => html`
${timeAgo(a.created_at)} <${Avatar} user=${state.usersById[a.user_id] || (a.user_id ? null : { name: 'Automation', email: 'auto' })} size=${24} /> ${a.item_name ? html`` : html`${state.usersById[a.user_id]?.name || 'Automation'}`} ${a.action === 'board_created' ? 'created the board' : a.action === 'item_deleted' ? html`deleted ${a.data?.name}` : a.action.startsWith('column_') || a.action === 'group_deleted' ? html`${a.action.replace('_', ' ')} ${a.data?.title}` : activityText(a)}
`)} ${rows && !shown.length ? html`

Nothing yet.

` : null}
`; } export function ImportCsvModal({ boardId }) { const [busy, setBusy] = useState(false); const pick = async (file) => { if (!file) return; setBusy(true); try { const text = await file.text(); const r = await api.post(`/api/boards/${boardId}/import.csv`, text, { contentType: 'text/csv' }); toast(`Imported ${r.created} items`); closeModal(); loadBoard(boardId); } catch (e) { toast(e.message, 'error'); } setBusy(false); }; return html`
<${ModalHeader} title="Import items from CSV" />

The first row must be column headers. A Name (or first) column becomes the item name, a Group column picks the group, and other headers fill columns with the same title — new columns are created as text. Export from Excel or Google Sheets with "Save as CSV".

`; } export function ColumnSettingsModal({ boardId, colId }) { const s = useStore(); const col = s.boardData[boardId]?.columns.find((c) => c.id === colId); const [title, setTitle] = useState(col?.title || ''); const [type, setType] = useState(col?.type); const [settings, setSettings] = useState(() => JSON.parse(JSON.stringify(col?.settings || {}))); if (!col) return null; const labels = settings.labels || []; const setLabels = (l) => setSettings({ ...settings, labels: l }); const save = async () => { const patch = {}; if (title.trim() && title !== col.title) patch.title = title.trim(); if (type !== col.type) patch.type = type; else patch.settings = settings; await A.patchColumn(boardId, colId, patch); if (patch.type) await loadBoard(boardId); closeModal(); }; return html`
<${ModalHeader} title="Column settings" /> setTitle(e.target.value)} /> ${type !== col.type ? html`

Changing the type keeps values that convert to text cleanly and clears the rest.

` : null} ${type === col.type && (col.type === 'status' || col.type === 'dropdown') ? html`
${labels.map((l, i) => html`
setLabels(labels.map((x, j) => (j === i ? { ...x, color: e.target.value } : x)))} /> setLabels(labels.map((x, j) => (j === i ? { ...x, name: e.target.value } : x)))} /> ${col.type === 'status' ? html`` : null}
`)}
${labels.map((l) => html`${l.name}`)}
` : null} ${type === col.type && col.type === 'number' ? html` setSettings({ ...settings, unit: e.target.value })} />` : null} ${type === col.type && col.type === 'rating' ? html` ` : null}
`; }