Files
flowdeck/app/templates/_database_table.html
T
bruno 24a760c5eb
FlowDeck CI / test (push) Failing after 5s
FlowDeck CI / docker (push) Has been skipped
feat(database): full-page database conversion (Notion-style)
- Migration: add collection_id column to pages table
- POST /api/pages/{id}/convert-to-database: transforms page into database
- GET /api/collections/{id}/table-data: returns props + pages for table view
- POST /api/collections/{id}/pages: creates new row in collection
- New page_editor_collection.html template for database view
- _database_table.html: Notion-style table with columns, rows, New Page, Add Property
- Alpine.js component for cell editing, new page creation, property mgmt
- CSS: complete database table styling (view bar, table, cells, modals, dropdowns)
- Frontend: createDatabase() now converts page to full-page DB (was inline embed)
2026-07-22 14:34:39 -04:00

182 lines
9.4 KiB
HTML

<!-- ═══════════ Database Table View (Notion-style) ═══════════ -->
<div x-data="databaseTable()" x-init="init($el)" class="database-table-container">
<!-- View Selector Bar -->
<div class="db-view-bar">
<div class="db-view-tabs">
<button class="db-view-tab active"
@click="switchView('table')">
{{ fd_icon("table",14) }} Table
</button>
<button class="db-view-add" @click="addViewMenuOpen=!addViewMenuOpen"
title="Add a view">
{{ fd_icon("plus",14) }}
</button>
<!-- View type dropdown -->
<div x-show="addViewMenuOpen" @click.outside="addViewMenuOpen=false"
class="db-dropdown" x-transition>
<div class="db-dropdown-header">Add a new view</div>
<div class="db-dropdown-item" @click="addViewMenuOpen=false">{{ fd_icon("table",14) }} Table</div>
<div class="db-dropdown-item" @click="addViewMenuOpen=false">{{ fd_icon("columns",14) }} Board</div>
<div class="db-dropdown-item" @click="addViewMenuOpen=false">{{ fd_icon("list",14) }} List</div>
<div class="db-dropdown-item" @click="addViewMenuOpen=false">{{ fd_icon("calendar",14) }} Calendar</div>
</div>
</div>
<div class="db-view-actions">
<button class="db-btn-icon" title="Filter">{{ fd_icon("filter",14) }}</button>
<button class="db-btn-new" @click="addNewPage()">
<span class="db-new-icon">{{ fd_icon("plus",12) }}</span> New
</button>
</div>
</div>
<!-- Database Table -->
<div class="db-table-wrapper">
<table class="db-table">
<thead>
<tr class="db-header-row">
<!-- Drag handle column -->
<th class="db-th db-th-handle" style="width:32px"></th>
<!-- Property columns -->
<template x-for="prop in properties" :key="prop.id">
<th class="db-th db-th-property">
<div class="db-th-content">
<span class="db-prop-type-icon" x-text="propTypeIcon(prop.prop_type)"></span>
<span class="db-prop-name" x-text="prop.name"></span>
</div>
</th>
</template>
<!-- Add Property column -->
<th class="db-th db-th-add-prop">
<button class="db-add-prop-btn" @click="addProperty()"
title="Add a property">
{{ fd_icon("plus",14) }} Add property
</button>
</th>
</tr>
</thead>
<tbody>
<!-- New Page row (always first) -->
<tr class="db-row db-row-new">
<td class="db-td db-td-handle">
<span class="db-plus-icon">{{ fd_icon("plus",14) }}</span>
</td>
<td class="db-td db-td-name" colspan="99">
<button class="db-new-page-btn" @click="addNewPage()">
New page
</button>
</td>
</tr>
<!-- Data rows -->
<template x-for="(cpage, idx) in pages" :key="cpage.id">
<tr class="db-row"
@mouseenter="cpage._hover=true"
@mouseleave="cpage._hover=false"
:class="{ 'db-row-selected': cpage._selected }">
<!-- Drag handle / hover actions -->
<td class="db-td db-td-handle">
<span class="db-drag-handle" x-show="cpage._hover"
@mousedown.prevent="startDrag(idx, $event)">
{{ fd_icon("grip-vertical",14) }}
</span>
</td>
<!-- Property cells -->
<template x-for="prop in properties" :key="prop.id">
<td class="db-td db-td-cell">
<div class="db-cell-content"
:class="{'db-cell-editing': editingCell && editingCell.pageId === cpage.id && editingCell.propId === prop.id}"
@click="startEditCell(cpage.id, prop.id, $event)"
@dblclick="openPage(cpage.id)">
<!-- Render property value -->
<span x-show="!(editingCell && editingCell.pageId === cpage.id && editingCell.propId === prop.id)"
x-text="cellValue(cpage, prop) || '—'"
class="db-cell-value"></span>
<!-- Edit mode -->
<input x-show="editingCell && editingCell.pageId === cpage.id && editingCell.propId === prop.id"
x-ref="cellInput"
:value="cellValue(cpage, prop)"
@keydown.enter="commitEditCell(cpage, prop)"
@keydown.escape="cancelEditCell()"
@blur="commitEditCell(cpage, prop)"
class="db-cell-input" />
</div>
</td>
</template>
<!-- Open button on hover -->
<td class="db-td db-td-open" x-show="cpage._hover">
<button class="db-open-btn" @click="openPage(cpage.id)"
title="Open">
{{ fd_icon("external-link",14) }}
</button>
</td>
</tr>
</template>
<!-- Empty rows (fill to 5 total if less than 4 data rows) -->
<template x-for="i in emptyRows()" :key="'e'+i">
<tr class="db-row db-row-empty">
<td class="db-td db-td-handle"></td>
<template x-for="prop in properties" :key="prop.id">
<td class="db-td db-td-cell"><div class="db-cell-content"></div></td>
</template>
<td class="db-td db-td-open"></td>
</tr>
</template>
</tbody>
</table>
</div>
<!-- Add Property Modal -->
<div x-show="propModalOpen" class="db-modal-overlay" @click.self="propModalOpen=false">
<div class="db-modal" x-transition>
<div class="db-modal-header">
<h3>Add a property</h3>
<button @click="propModalOpen=false">{{ fd_icon("x",14) }}</button>
</div>
<div class="db-modal-body">
<div class="db-modal-field">
<label>Name</label>
<input x-model="newPropName" type="text" placeholder="Property name"
@keydown.enter="createProperty()" />
</div>
<div class="db-modal-field">
<label>Type</label>
<select x-model="newPropType">
<option value="text">Text</option>
<option value="number">Number</option>
<option value="select">Select</option>
<option value="multi_select">Multi-select</option>
<option value="date">Date</option>
<option value="checkbox">Checkbox</option>
<option value="url">URL</option>
<option value="email">Email</option>
<option value="phone">Phone</option>
</select>
</div>
<div class="db-modal-actions">
<button class="db-btn-cancel" @click="propModalOpen=false">Cancel</button>
<button class="db-btn-create" @click="createProperty()">Create</button>
</div>
</div>
</div>
</div>
<!-- Page title rename (inline) -->
<div x-show="renameOpen" class="db-modal-overlay" @click.self="renameOpen=false" style="z-index:600">
<div class="db-modal" x-transition style="max-width:400px">
<div class="db-modal-header">
<h3>Rename page</h3>
<button @click="renameOpen=false">{{ fd_icon("x",14) }}</button>
</div>
<div class="db-modal-body">
<div class="db-modal-field">
<input x-model="renameTitle" type="text" placeholder="Page title"
@keydown.enter="commitRename()" x-ref="renameInput" />
</div>
<div class="db-modal-actions">
<button class="db-btn-cancel" @click="renameOpen=false">Cancel</button>
<button class="db-btn-create" @click="commitRename()">Save</button>
</div>
</div>
</div>
</div>
</div>