mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-21 18:13:26 +00:00
Add collection template library with pre-built schemas for common use cases
CreateCollectionModal now shows a template picker as the first step with 8 templates: Bug Tracker, Feature Requests, Meeting Notes, Decisions, Tech Debt, OKRs, Sprint Backlog, and Knowledge Base. Selecting a template pre-fills name, icon, description, fields, and settings. A "Blank" option starts from scratch.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { api } from '$lib/api/client';
|
||||
import type { CollectionCreate, FieldDef } from '$lib/types';
|
||||
import type { CollectionCreate, FieldDef, CollectionSettings } from '$lib/types';
|
||||
import { COLLECTION_TEMPLATES, type CollectionTemplate } from './collection-templates';
|
||||
import EmojiPicker from '$lib/components/common/EmojiPicker.svelte';
|
||||
import { toastStore } from '$lib/stores/toast.svelte';
|
||||
|
||||
@@ -24,14 +25,68 @@
|
||||
'relation'
|
||||
];
|
||||
|
||||
// Step state: 'templates' or 'editor'
|
||||
let step = $state<'templates' | 'editor'>('templates');
|
||||
|
||||
// Form state
|
||||
let name = $state('');
|
||||
let selectedIcon = $state('');
|
||||
let description = $state('');
|
||||
let showEmojiPicker = $state(false);
|
||||
let fields = $state<{ key: string; type: FieldDef['type']; options: string }[]>([]);
|
||||
let selectedSettings = $state<CollectionSettings | null>(null);
|
||||
let creating = $state(false);
|
||||
let error = $state('');
|
||||
|
||||
// Track previous open state to detect open transitions
|
||||
let prevOpen = $state(false);
|
||||
|
||||
// Reset to step 1 whenever the modal opens (false -> true transition)
|
||||
$effect.pre(() => {
|
||||
if (open && !prevOpen) {
|
||||
step = 'templates';
|
||||
name = '';
|
||||
selectedIcon = '';
|
||||
description = '';
|
||||
fields = [];
|
||||
selectedSettings = null;
|
||||
showEmojiPicker = false;
|
||||
error = '';
|
||||
}
|
||||
prevOpen = open;
|
||||
});
|
||||
|
||||
function resetForm() {
|
||||
name = '';
|
||||
selectedIcon = '';
|
||||
description = '';
|
||||
fields = [];
|
||||
selectedSettings = null;
|
||||
showEmojiPicker = false;
|
||||
error = '';
|
||||
}
|
||||
|
||||
function selectTemplate(template: CollectionTemplate | null) {
|
||||
resetForm();
|
||||
if (template) {
|
||||
name = template.name;
|
||||
selectedIcon = template.icon;
|
||||
description = template.description;
|
||||
selectedSettings = { ...template.settings };
|
||||
fields = template.fields.map((f) => ({
|
||||
key: f.key,
|
||||
type: f.type,
|
||||
options: f.options ? f.options.join(', ') : ''
|
||||
}));
|
||||
}
|
||||
step = 'editor';
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
step = 'templates';
|
||||
resetForm();
|
||||
}
|
||||
|
||||
function addField() {
|
||||
fields.push({ key: '', type: 'text', options: '' });
|
||||
}
|
||||
@@ -40,15 +95,6 @@
|
||||
fields.splice(index, 1);
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
name = '';
|
||||
selectedIcon = '';
|
||||
description = '';
|
||||
fields = [];
|
||||
showEmojiPicker = false;
|
||||
error = '';
|
||||
}
|
||||
|
||||
async function handleCreate() {
|
||||
if (!name.trim() || creating) return;
|
||||
creating = true;
|
||||
@@ -71,7 +117,8 @@
|
||||
name: name.trim(),
|
||||
icon: selectedIcon || undefined,
|
||||
description: description.trim() || undefined,
|
||||
schema: JSON.stringify({ fields: fieldDefs })
|
||||
schema: JSON.stringify({ fields: fieldDefs }),
|
||||
settings: selectedSettings ? JSON.stringify(selectedSettings) : undefined
|
||||
};
|
||||
await api.collections.create(wsSlug, data);
|
||||
toastStore.show(`Created ${name.trim()}`, 'success');
|
||||
@@ -97,104 +144,143 @@
|
||||
<div class="overlay" onclick={onclose}>
|
||||
<div class="modal" onclick={(e) => e.stopPropagation()}>
|
||||
<div class="modal-header">
|
||||
<h2>New Collection</h2>
|
||||
{#if step === 'editor'}
|
||||
<div class="header-left">
|
||||
<button class="back-btn" type="button" onclick={goBack} aria-label="Back to templates">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M10 12L6 8L10 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<h2>New Collection</h2>
|
||||
</div>
|
||||
{:else}
|
||||
<h2>New Collection</h2>
|
||||
{/if}
|
||||
<button class="close-btn" type="button" onclick={onclose}>✕</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
{#if error}
|
||||
<div class="error-banner">{error}</div>
|
||||
{/if}
|
||||
|
||||
<div class="name-row">
|
||||
<button
|
||||
class="icon-btn"
|
||||
type="button"
|
||||
onclick={() => (showEmojiPicker = !showEmojiPicker)}
|
||||
>
|
||||
{#if selectedIcon}
|
||||
<span class="icon-preview">{selectedIcon}</span>
|
||||
{:else}
|
||||
<span class="icon-placeholder">+</span>
|
||||
{/if}
|
||||
</button>
|
||||
<input
|
||||
class="name-input"
|
||||
type="text"
|
||||
placeholder="Collection name"
|
||||
bind:value={name}
|
||||
/>
|
||||
{#if step === 'templates'}
|
||||
<div class="modal-body">
|
||||
<p class="step-hint">Choose a template or start from scratch</p>
|
||||
<div class="template-grid">
|
||||
<button
|
||||
class="template-card template-card--blank"
|
||||
type="button"
|
||||
onclick={() => selectTemplate(null)}
|
||||
>
|
||||
<span class="template-icon">+</span>
|
||||
<span class="template-name">Blank</span>
|
||||
<span class="template-desc">Start from scratch</span>
|
||||
</button>
|
||||
{#each COLLECTION_TEMPLATES as template (template.id)}
|
||||
<button
|
||||
class="template-card"
|
||||
type="button"
|
||||
onclick={() => selectTemplate(template)}
|
||||
>
|
||||
<span class="template-icon">{template.icon}</span>
|
||||
<span class="template-name">{template.name}</span>
|
||||
<span class="template-desc">{template.description}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="modal-body">
|
||||
{#if error}
|
||||
<div class="error-banner">{error}</div>
|
||||
{/if}
|
||||
|
||||
{#if showEmojiPicker}
|
||||
<div class="emoji-picker-container">
|
||||
<EmojiPicker
|
||||
selected={selectedIcon}
|
||||
onselect={(emoji) => {
|
||||
selectedIcon = emoji;
|
||||
showEmojiPicker = false;
|
||||
}}
|
||||
<div class="name-row">
|
||||
<button
|
||||
class="icon-btn"
|
||||
type="button"
|
||||
onclick={() => (showEmojiPicker = !showEmojiPicker)}
|
||||
>
|
||||
{#if selectedIcon}
|
||||
<span class="icon-preview">{selectedIcon}</span>
|
||||
{:else}
|
||||
<span class="icon-placeholder">+</span>
|
||||
{/if}
|
||||
</button>
|
||||
<input
|
||||
class="name-input"
|
||||
type="text"
|
||||
placeholder="Collection name"
|
||||
bind:value={name}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<input
|
||||
class="desc-input"
|
||||
type="text"
|
||||
placeholder="Description (optional)"
|
||||
bind:value={description}
|
||||
/>
|
||||
|
||||
<div class="fields-section">
|
||||
<div class="fields-header">
|
||||
<span class="fields-label">Fields</span>
|
||||
<button class="add-field-btn" type="button" onclick={addField}>+ Add</button>
|
||||
</div>
|
||||
|
||||
{#each fields as field, i (i)}
|
||||
<div class="field-row">
|
||||
<input
|
||||
class="field-name-input"
|
||||
type="text"
|
||||
placeholder="Field name"
|
||||
bind:value={field.key}
|
||||
{#if showEmojiPicker}
|
||||
<div class="emoji-picker-container">
|
||||
<EmojiPicker
|
||||
selected={selectedIcon}
|
||||
onselect={(emoji) => {
|
||||
selectedIcon = emoji;
|
||||
showEmojiPicker = false;
|
||||
}}
|
||||
/>
|
||||
<select class="field-type-select" bind:value={field.type}>
|
||||
{#each FIELD_TYPES as ft (ft)}
|
||||
<option value={ft}>{ft.replace('_', ' ')}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if field.type === 'select' || field.type === 'multi_select'}
|
||||
<input
|
||||
class="field-options-input"
|
||||
type="text"
|
||||
placeholder="option1, option2, ..."
|
||||
bind:value={field.options}
|
||||
/>
|
||||
{/if}
|
||||
<button
|
||||
class="remove-field-btn"
|
||||
type="button"
|
||||
onclick={() => removeField(i)}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="btn-cancel" type="button" onclick={onclose}>Cancel</button>
|
||||
<button
|
||||
class="btn-create"
|
||||
type="button"
|
||||
onclick={handleCreate}
|
||||
disabled={!name.trim() || creating}
|
||||
>
|
||||
{creating ? 'Creating...' : 'Create Collection'}
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
class="desc-input"
|
||||
type="text"
|
||||
placeholder="Description (optional)"
|
||||
bind:value={description}
|
||||
/>
|
||||
|
||||
<div class="fields-section">
|
||||
<div class="fields-header">
|
||||
<span class="fields-label">Fields</span>
|
||||
<button class="add-field-btn" type="button" onclick={addField}>+ Add</button>
|
||||
</div>
|
||||
|
||||
{#each fields as field, i (i)}
|
||||
<div class="field-row">
|
||||
<input
|
||||
class="field-name-input"
|
||||
type="text"
|
||||
placeholder="Field name"
|
||||
bind:value={field.key}
|
||||
/>
|
||||
<select class="field-type-select" bind:value={field.type}>
|
||||
{#each FIELD_TYPES as ft (ft)}
|
||||
<option value={ft}>{ft.replace('_', ' ')}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if field.type === 'select' || field.type === 'multi_select'}
|
||||
<input
|
||||
class="field-options-input"
|
||||
type="text"
|
||||
placeholder="option1, option2, ..."
|
||||
bind:value={field.options}
|
||||
/>
|
||||
{/if}
|
||||
<button
|
||||
class="remove-field-btn"
|
||||
type="button"
|
||||
onclick={() => removeField(i)}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="btn-cancel" type="button" onclick={onclose}>Cancel</button>
|
||||
<button
|
||||
class="btn-create"
|
||||
type="button"
|
||||
onclick={handleCreate}
|
||||
disabled={!name.trim() || creating}
|
||||
>
|
||||
{creating ? 'Creating...' : 'Create Collection'}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -223,7 +309,7 @@
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* ── Header ─────────────────────────────────────────────────────────────── */
|
||||
/* -- Header ------------------------------------------------------------ */
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
@@ -239,6 +325,32 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-hover);
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
@@ -255,7 +367,7 @@
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
/* ── Body ───────────────────────────────────────────────────────────────── */
|
||||
/* -- Body -------------------------------------------------------------- */
|
||||
|
||||
.modal-body {
|
||||
padding: var(--space-5);
|
||||
@@ -272,7 +384,68 @@
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
/* ── Icon + Name row ────────────────────────────────────────────────────── */
|
||||
/* -- Step 1: Template picker ------------------------------------------- */
|
||||
|
||||
.step-hint {
|
||||
margin: 0;
|
||||
font-size: 0.85em;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.template-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.template-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-1);
|
||||
padding: var(--space-4);
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
transition: border-color 0.15s ease, background 0.15s ease;
|
||||
}
|
||||
|
||||
.template-card:hover {
|
||||
border-color: var(--accent-blue);
|
||||
background: color-mix(in srgb, var(--accent-blue) 5%, var(--bg-tertiary));
|
||||
}
|
||||
|
||||
.template-card--blank {
|
||||
border-style: dashed;
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
.template-card--blank:hover {
|
||||
border-style: dashed;
|
||||
border-color: var(--accent-blue);
|
||||
}
|
||||
|
||||
.template-icon {
|
||||
font-size: 1.5em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.template-name {
|
||||
font-size: 0.9em;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-top: var(--space-1);
|
||||
}
|
||||
|
||||
.template-desc {
|
||||
font-size: 0.78em;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
/* -- Step 2: Icon + Name row ------------------------------------------- */
|
||||
|
||||
.name-row {
|
||||
display: flex;
|
||||
@@ -328,13 +501,13 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* ── Emoji picker ───────────────────────────────────────────────────────── */
|
||||
/* -- Emoji picker ------------------------------------------------------ */
|
||||
|
||||
.emoji-picker-container {
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
/* ── Description ────────────────────────────────────────────────────────── */
|
||||
/* -- Description ------------------------------------------------------- */
|
||||
|
||||
.desc-input {
|
||||
width: 100%;
|
||||
@@ -359,7 +532,7 @@
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* ── Fields builder ─────────────────────────────────────────────────────── */
|
||||
/* -- Fields builder ---------------------------------------------------- */
|
||||
|
||||
.fields-section {
|
||||
display: flex;
|
||||
@@ -476,7 +649,7 @@
|
||||
background: color-mix(in srgb, var(--accent-red, #ef4444) 10%, transparent);
|
||||
}
|
||||
|
||||
/* ── Footer ─────────────────────────────────────────────────────────────── */
|
||||
/* -- Footer ------------------------------------------------------------ */
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
import type { FieldDef, CollectionSettings } from '$lib/types';
|
||||
|
||||
export interface CollectionTemplate {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
description: string;
|
||||
fields: FieldDef[];
|
||||
settings: CollectionSettings;
|
||||
}
|
||||
|
||||
export const COLLECTION_TEMPLATES: CollectionTemplate[] = [
|
||||
{
|
||||
id: 'bug-tracker',
|
||||
name: 'Bug Tracker',
|
||||
icon: '🐛',
|
||||
description: 'Track bugs, defects, and issues',
|
||||
fields: [
|
||||
{
|
||||
key: 'status',
|
||||
label: 'Status',
|
||||
type: 'select',
|
||||
options: ['open', 'confirmed', 'in-progress', 'fixed', 'wont-fix'],
|
||||
default: 'open',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: 'severity',
|
||||
label: 'Severity',
|
||||
type: 'select',
|
||||
options: ['low', 'medium', 'high', 'critical']
|
||||
},
|
||||
{
|
||||
key: 'priority',
|
||||
label: 'Priority',
|
||||
type: 'select',
|
||||
options: ['low', 'medium', 'high'],
|
||||
default: 'medium'
|
||||
},
|
||||
{ key: 'assignee', label: 'Assignee', type: 'text' },
|
||||
{ key: 'reported_by', label: 'Reported By', type: 'text' }
|
||||
],
|
||||
settings: {
|
||||
layout: 'fields-primary',
|
||||
default_view: 'board',
|
||||
board_group_by: 'status',
|
||||
list_sort_by: 'severity'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'feature-requests',
|
||||
name: 'Feature Requests',
|
||||
icon: '⭐',
|
||||
description: 'Collect and prioritize feature requests',
|
||||
fields: [
|
||||
{
|
||||
key: 'status',
|
||||
label: 'Status',
|
||||
type: 'select',
|
||||
options: ['submitted', 'under-review', 'planned', 'building', 'shipped', 'declined'],
|
||||
default: 'submitted',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: 'priority',
|
||||
label: 'Priority',
|
||||
type: 'select',
|
||||
options: ['low', 'medium', 'high']
|
||||
},
|
||||
{ key: 'votes', label: 'Votes', type: 'number' },
|
||||
{ key: 'category', label: 'Category', type: 'text' }
|
||||
],
|
||||
settings: {
|
||||
layout: 'balanced',
|
||||
default_view: 'board',
|
||||
board_group_by: 'status',
|
||||
list_sort_by: 'priority'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'meeting-notes',
|
||||
name: 'Meeting Notes',
|
||||
icon: '📝',
|
||||
description: 'Capture meeting agendas, notes, and action items',
|
||||
fields: [
|
||||
{
|
||||
key: 'status',
|
||||
label: 'Status',
|
||||
type: 'select',
|
||||
options: ['draft', 'published'],
|
||||
default: 'draft',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
label: 'Type',
|
||||
type: 'select',
|
||||
options: ['standup', 'planning', 'retro', '1-on-1', 'all-hands', 'other']
|
||||
},
|
||||
{ key: 'date', label: 'Date', type: 'date' },
|
||||
{ key: 'attendees', label: 'Attendees', type: 'text' }
|
||||
],
|
||||
settings: {
|
||||
layout: 'content-primary',
|
||||
default_view: 'list',
|
||||
list_sort_by: 'date',
|
||||
list_group_by: 'type'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'decisions',
|
||||
name: 'Decisions',
|
||||
icon: '⚖️',
|
||||
description: 'Record architecture decisions and their rationale (ADRs)',
|
||||
fields: [
|
||||
{
|
||||
key: 'status',
|
||||
label: 'Status',
|
||||
type: 'select',
|
||||
options: ['proposed', 'accepted', 'rejected', 'superseded'],
|
||||
default: 'proposed',
|
||||
required: true
|
||||
},
|
||||
{ key: 'category', label: 'Category', type: 'text' },
|
||||
{ key: 'decision_date', label: 'Decision Date', type: 'date' }
|
||||
],
|
||||
settings: {
|
||||
layout: 'content-primary',
|
||||
default_view: 'list',
|
||||
list_sort_by: 'created_at',
|
||||
list_group_by: 'status'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'tech-debt',
|
||||
name: 'Tech Debt',
|
||||
icon: '🔧',
|
||||
description: 'Track technical debt and improvement opportunities',
|
||||
fields: [
|
||||
{
|
||||
key: 'status',
|
||||
label: 'Status',
|
||||
type: 'select',
|
||||
options: ['identified', 'planned', 'in-progress', 'resolved'],
|
||||
default: 'identified',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: 'severity',
|
||||
label: 'Severity',
|
||||
type: 'select',
|
||||
options: ['low', 'medium', 'high', 'critical']
|
||||
},
|
||||
{
|
||||
key: 'area',
|
||||
label: 'Area',
|
||||
type: 'select',
|
||||
options: ['backend', 'frontend', 'infrastructure', 'database', 'testing']
|
||||
}
|
||||
],
|
||||
settings: {
|
||||
layout: 'balanced',
|
||||
default_view: 'board',
|
||||
board_group_by: 'status',
|
||||
list_sort_by: 'severity'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'okrs',
|
||||
name: 'OKRs',
|
||||
icon: '🎯',
|
||||
description: 'Set objectives and track key results',
|
||||
fields: [
|
||||
{
|
||||
key: 'status',
|
||||
label: 'Status',
|
||||
type: 'select',
|
||||
options: ['draft', 'active', 'completed', 'abandoned'],
|
||||
default: 'draft',
|
||||
required: true
|
||||
},
|
||||
{ key: 'quarter', label: 'Quarter', type: 'text' },
|
||||
{
|
||||
key: 'type',
|
||||
label: 'Type',
|
||||
type: 'select',
|
||||
options: ['objective', 'key-result']
|
||||
},
|
||||
{ key: 'progress', label: 'Progress', type: 'number', suffix: '%' }
|
||||
],
|
||||
settings: {
|
||||
layout: 'balanced',
|
||||
default_view: 'list',
|
||||
list_sort_by: 'quarter',
|
||||
list_group_by: 'type'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'sprint-backlog',
|
||||
name: 'Sprint Backlog',
|
||||
icon: '🏃',
|
||||
description: 'Manage sprint work items and track progress',
|
||||
fields: [
|
||||
{
|
||||
key: 'status',
|
||||
label: 'Status',
|
||||
type: 'select',
|
||||
options: ['backlog', 'ready', 'in-progress', 'review', 'done'],
|
||||
default: 'backlog',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: 'priority',
|
||||
label: 'Priority',
|
||||
type: 'select',
|
||||
options: ['low', 'medium', 'high'],
|
||||
default: 'medium'
|
||||
},
|
||||
{ key: 'story_points', label: 'Story Points', type: 'number' },
|
||||
{ key: 'assignee', label: 'Assignee', type: 'text' },
|
||||
{ key: 'sprint', label: 'Sprint', type: 'text' }
|
||||
],
|
||||
settings: {
|
||||
layout: 'fields-primary',
|
||||
default_view: 'board',
|
||||
board_group_by: 'status',
|
||||
list_sort_by: 'priority'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'knowledge-base',
|
||||
name: 'Knowledge Base',
|
||||
icon: '📚',
|
||||
description: 'Organize documentation, guides, and reference material',
|
||||
fields: [
|
||||
{
|
||||
key: 'status',
|
||||
label: 'Status',
|
||||
type: 'select',
|
||||
options: ['draft', 'published', 'archived'],
|
||||
default: 'draft',
|
||||
required: true
|
||||
},
|
||||
{ key: 'category', label: 'Category', type: 'text' },
|
||||
{
|
||||
key: 'audience',
|
||||
label: 'Audience',
|
||||
type: 'select',
|
||||
options: ['team', 'public', 'onboarding']
|
||||
}
|
||||
],
|
||||
settings: {
|
||||
layout: 'content-primary',
|
||||
default_view: 'list',
|
||||
list_sort_by: 'updated_at',
|
||||
list_group_by: 'category'
|
||||
}
|
||||
}
|
||||
];
|
||||
Reference in New Issue
Block a user