Use common Table component across backend UI and add initial empty page for relationships

This commit is contained in:
Andrew Kingston 2020-10-01 07:19:38 +01:00
parent b5d38dd6e4
commit cd69d17c87
7 changed files with 107 additions and 201 deletions

View File

@ -1,187 +1,30 @@
<script> <script>
import { onMount } from "svelte" import { backendUiStore } from "builderStore"
import fsort from "fast-sort"
import getOr from "lodash/fp/getOr"
import { store, backendUiStore } from "builderStore"
import { Button, Icon } from "@budibase/bbui"
import ActionButton from "components/common/ActionButton.svelte"
import LinkedRecord from "./LinkedRecord.svelte"
import AttachmentList from "./AttachmentList.svelte"
import TablePagination from "./TablePagination.svelte"
import CreateEditRecordModal from "./popovers/CreateEditRecord.svelte"
import RowPopover from "./popovers/Row.svelte" import RowPopover from "./popovers/Row.svelte"
import ColumnPopover from "./popovers/Column.svelte" import ColumnPopover from "./popovers/Column.svelte"
import ViewPopover from "./popovers/View.svelte" import ViewPopover from "./popovers/View.svelte"
import ColumnHeaderPopover from "./popovers/ColumnHeader.svelte"
import EditRowPopover from "./popovers/EditRow.svelte"
import * as api from "./api" import * as api from "./api"
import Table from "./Table.svelte"
const ITEMS_PER_PAGE = 10
// Internal headers we want to hide from the user
const INTERNAL_HEADERS = ["_id", "_rev", "modelId", "type"]
let modalOpen = false
let data = [] let data = []
let headers = []
let currentPage = 0
let search
$: title = $backendUiStore.selectedModel.name
$: schema = $backendUiStore.selectedModel.schema
// Fetch records for specified model
$: { $: {
if ( if ($backendUiStore.selectedView?.name?.startsWith("all_")) {
$backendUiStore.selectedView &&
$backendUiStore.selectedView.name.startsWith("all_")
) {
api.fetchDataForView($backendUiStore.selectedView).then(records => { api.fetchDataForView($backendUiStore.selectedView).then(records => {
data = records || [] data = records || []
}) })
} }
} }
$: sort = $backendUiStore.sort
$: sorted = sort ? fsort(data)[sort.direction](sort.column) : data
$: paginatedData = sorted
? sorted.slice(
currentPage * ITEMS_PER_PAGE,
currentPage * ITEMS_PER_PAGE + ITEMS_PER_PAGE
)
: []
$: headers = Object.keys($backendUiStore.selectedModel.schema)
.sort()
.filter(id => !INTERNAL_HEADERS.includes(id))
$: schema = $backendUiStore.selectedModel.schema
</script> </script>
<section> <Table {title} {schema} {data} allowEditing={true}>
<h2 class="title">{$backendUiStore.selectedModel.name}</h2> <ColumnPopover />
<div class="popovers"> {#if Object.keys(schema).length > 0}
<ColumnPopover /> <RowPopover />
{#if Object.keys($backendUiStore.selectedModel.schema).length > 0} <ViewPopover />
<RowPopover /> {/if}
<ViewPopover /> </Table>
{/if}
</div>
<table class="bb-table">
<thead>
<tr>
<th class="edit-header">
<div>Edit</div>
</th>
{#each headers as header}
<th>
<ColumnHeaderPopover
field={$backendUiStore.selectedModel.schema[header]} />
</th>
{/each}
</tr>
</thead>
<tbody>
{#if paginatedData.length === 0}
<td class="no-border">No data.</td>
{#each headers as header}
<td class="no-border" />
{/each}
{/if}
{#each paginatedData as row}
<tr>
<td>
<EditRowPopover {row} />
</td>
{#each headers as header}
<td>
{#if schema[header].type === 'link'}
{JSON.stringify(row[header])}
{:else if schema[header].type === 'attachment'}
<AttachmentList files={row[header] || []} />
{:else}{getOr('', header, row)}{/if}
</td>
{/each}
</tr>
{/each}
</tbody>
</table>
<TablePagination
{data}
bind:currentPage
pageItemCount={paginatedData.length}
{ITEMS_PER_PAGE} />
</section>
<style>
.title {
font-size: 24px;
font-weight: 600;
text-rendering: optimizeLegibility;
text-transform: capitalize;
margin-top: 0;
}
.popovers {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
table {
border: 1px solid var(--grey-4);
background: #fff;
border-collapse: collapse;
}
thead {
background: var(--grey-3);
border-bottom: 1px solid var(--grey-4);
}
thead th {
color: var(--ink);
font-weight: 500;
font-size: 14px;
text-rendering: optimizeLegibility;
transition: 0.5s all;
vertical-align: middle;
height: 48px;
padding-top: 0;
padding-bottom: 0;
}
.edit-header {
width: 100px;
cursor: default;
}
.edit-header:hover {
color: var(--ink);
}
th:hover {
color: var(--blue);
cursor: pointer;
}
td {
max-width: 200px;
text-overflow: ellipsis;
border: 1px solid var(--grey-4);
overflow: hidden;
white-space: nowrap;
box-sizing: border-box;
padding: var(--spacing-l) var(--spacing-m);
font-size: var(--font-size-xs);
}
td.no-border {
border: none;
}
tbody {
border: 1px solid var(--grey-4);
}
tbody tr {
border-bottom: 1px solid var(--grey-4);
transition: 0.3s background-color;
color: var(--ink);
}
tbody tr:hover {
background: var(--grey-1);
}
</style>

View File

@ -1,4 +1,5 @@
<script> <script>
import { goto } from "@sveltech/routify"
import { onMount } from "svelte" import { onMount } from "svelte"
import fsort from "fast-sort" import fsort from "fast-sort"
import getOr from "lodash/fp/getOr" import getOr from "lodash/fp/getOr"
@ -16,16 +17,16 @@
import EditRowPopover from "./popovers/EditRow.svelte" import EditRowPopover from "./popovers/EditRow.svelte"
import CalculationPopover from "./popovers/Calculate.svelte" import CalculationPopover from "./popovers/Calculate.svelte"
const ITEMS_PER_PAGE = 10
export let schema = [] export let schema = []
export let data = [] export let data = []
export let title export let title
export let allowEditing = false
const ITEMS_PER_PAGE = 10
let currentPage = 0 let currentPage = 0
$: columns = schema ? Object.keys(schema) : [] $: columns = schema ? Object.keys(schema) : []
$: sort = $backendUiStore.sort $: sort = $backendUiStore.sort
$: sorted = sort ? fsort(data)[sort.direction](sort.column) : data $: sorted = sort ? fsort(data)[sort.direction](sort.column) : data
$: paginatedData = $: paginatedData =
@ -35,6 +36,10 @@
currentPage * ITEMS_PER_PAGE + ITEMS_PER_PAGE currentPage * ITEMS_PER_PAGE + ITEMS_PER_PAGE
) )
: [] : []
function selectRelationship(recordId, fieldName) {
$goto(`../relationship/${recordId}/${fieldName}`)
}
</script> </script>
<section> <section>
@ -47,13 +52,25 @@
<table class="bb-table"> <table class="bb-table">
<thead> <thead>
<tr> <tr>
{#if allowEditing}
<th class="edit-header">
<div>Edit</div>
</th>
{/if}
{#each columns as header} {#each columns as header}
<th>{header}</th> <th>
{#if allowEditing}
<ColumnHeaderPopover field={schema[header]} />
{:else}{header}{/if}
</th>
{/each} {/each}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#if paginatedData.length === 0} {#if paginatedData.length === 0}
{#if allowEditing}
<td class="no-border">No data.</td>
{/if}
{#each columns as header, idx} {#each columns as header, idx}
<td class="no-border"> <td class="no-border">
{#if idx === 0}No data.{/if} {#if idx === 0}No data.{/if}
@ -62,9 +79,20 @@
{/if} {/if}
{#each paginatedData as row} {#each paginatedData as row}
<tr> <tr>
{#if allowEditing}
<td>
<EditRowPopover {row} />
</td>
{/if}
{#each columns as header} {#each columns as header}
<td> <td>
{#if schema[header].type === 'attachment'} {#if schema[header].type === 'link'}
<div
class="link"
on:click={() => selectRelationship(row._id, header)}>
{row[header] ? row[header].length : 0} linked row(s)
</div>
{:else if schema[header].type === 'attachment'}
<AttachmentList files={row[header] || []} /> <AttachmentList files={row[header] || []} />
{:else}{getOr('', header, row)}{/if} {:else}{getOr('', header, row)}{/if}
</td> </td>
@ -101,7 +129,6 @@
} }
thead th { thead th {
color: var(--ink); color: var(--ink);
text-transform: capitalize;
font-weight: 500; font-weight: 500;
font-size: 14px; font-size: 14px;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
@ -149,4 +176,20 @@
:global(.popovers > div) { :global(.popovers > div) {
margin-right: var(--spacing-m); margin-right: var(--spacing-m);
} }
.edit-header {
width: 60px;
}
.edit-header:hover {
cursor: default;
color: var(--ink);
}
.link {
text-decoration: underline;
}
.link:hover {
color: var(--grey-6);
cursor: pointer;
}
</style> </style>

View File

@ -1,20 +1,6 @@
<script> <script>
import { onMount } from "svelte"
import fsort from "fast-sort"
import getOr from "lodash/fp/getOr"
import { store, backendUiStore } from "builderStore"
import api from "builderStore/api" import api from "builderStore/api"
import { Button, Icon } from "@budibase/bbui"
import Table from "./Table.svelte" import Table from "./Table.svelte"
import ActionButton from "components/common/ActionButton.svelte"
import LinkedRecord from "./LinkedRecord.svelte"
import TablePagination from "./TablePagination.svelte"
import CreateEditRecordModal from "./popovers/CreateEditRecord.svelte"
import RowPopover from "./popovers/Row.svelte"
import ColumnPopover from "./popovers/Column.svelte"
import ViewPopover from "./popovers/View.svelte"
import ColumnHeaderPopover from "./popovers/ColumnHeader.svelte"
import EditRowPopover from "./popovers/EditRow.svelte"
import CalculationPopover from "./popovers/Calculate.svelte" import CalculationPopover from "./popovers/Calculate.svelte"
import GroupByPopover from "./popovers/GroupBy.svelte" import GroupByPopover from "./popovers/GroupBy.svelte"
import FilterPopover from "./popovers/Filter.svelte" import FilterPopover from "./popovers/Filter.svelte"
@ -24,22 +10,24 @@
let data = [] let data = []
$: name = view.name $: name = view.name
$: filters = view.filters
$: field = view.field // Fetch records for specified view
$: groupBy = view.groupBy $: {
$: !name.startsWith("all_") && filters && fetchViewData(name, field, groupBy) if (!name.startsWith("all_")) {
fetchViewData(name, view.field, view.groupBy)
}
}
async function fetchViewData(name, field, groupBy) { async function fetchViewData(name, field, groupBy) {
const params = new URLSearchParams() const params = new URLSearchParams()
if (field) { if (field) {
params.set("field", field) params.set("field", field)
params.set("stats", true) params.set("stats", true)
} }
if (groupBy) params.set("group", groupBy) if (groupBy) {
params.set("group", groupBy)
let QUERY_VIEW_URL = `/api/views/${name}?${params}` }
const QUERY_VIEW_URL = `/api/views/${name}?${params}`
const response = await api.get(QUERY_VIEW_URL) const response = await api.get(QUERY_VIEW_URL)
data = await response.json() data = await response.json()
} }

View File

@ -20,13 +20,14 @@
} }
function hideEditor() { function hideEditor() {
dropdown.hide() dropdown?.hide()
editing = false editing = false
} }
async function deleteTable() { async function deleteTable() {
await backendUiStore.actions.models.delete(table) await backendUiStore.actions.models.delete(table)
notifier.success("Table deleted") notifier.success("Table deleted")
hideEditor()
} }
async function save() { async function save() {
@ -77,6 +78,7 @@
body={`Are you sure you wish to delete the table '${table.name}'? Your data will be deleted and this action cannot be undone.`} body={`Are you sure you wish to delete the table '${table.name}'? Your data will be deleted and this action cannot be undone.`}
okText="Delete Table" okText="Delete Table"
onOk={deleteTable} onOk={deleteTable}
onCancel={hideEditor}
title="Confirm Delete" /> title="Confirm Delete" />
<style> <style>

View File

@ -0,0 +1,18 @@
<script>
import { onMount } from "svelte"
import { params } from "@sveltech/routify"
import { backendUiStore } from "builderStore"
import api from "../../../../../../../../builderStore/api"
console.log($params)
let data
onMount(async () => {
const QUERY_VIEW_URL = `/api/${$params.selectedModel}/${$params.selectedRecord}/enrich`
const response = await api.get(QUERY_VIEW_URL)
data = await response.json()
console.log(data)
})
</script>
<div>hello world!</div>

View File

@ -0,0 +1,6 @@
<script>
import { goto } from "@sveltech/routify"
$goto("../../")
</script>
<!-- routify:options index=false -->

View File

@ -0,0 +1,6 @@
<script>
import { goto } from "@sveltech/routify"
$goto("../")
</script>
<!-- routify:options index=false -->