- 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)
182 lines
9.4 KiB
HTML
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>
|