Add functional backend UI for navigating relationships
This commit is contained in:
parent
9a9430e0cf
commit
7aa040d314
|
@ -0,0 +1,32 @@
|
||||||
|
<script>
|
||||||
|
import api from "builderStore/api"
|
||||||
|
import Table from "./Table.svelte"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
|
||||||
|
export let modelId
|
||||||
|
export let recordId
|
||||||
|
export let fieldName
|
||||||
|
|
||||||
|
let record
|
||||||
|
|
||||||
|
$: data = record?.[fieldName] ?? []
|
||||||
|
$: linkedModelId = data?.length ? data[0].modelId : null
|
||||||
|
$: linkedModel = $backendUiStore.models.find(
|
||||||
|
model => model._id === linkedModelId
|
||||||
|
)
|
||||||
|
$: schema = linkedModel?.schema
|
||||||
|
$: model = $backendUiStore.models.find(model => model._id === modelId)
|
||||||
|
$: title = `${record?.[model?.primaryDisplay]} - ${fieldName}`
|
||||||
|
$: fetchData(modelId, recordId)
|
||||||
|
|
||||||
|
async function fetchData(modelId, recordId) {
|
||||||
|
const QUERY_VIEW_URL = `/api/${modelId}/${recordId}/enrich`
|
||||||
|
const response = await api.get(QUERY_VIEW_URL)
|
||||||
|
record = await response.json()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if record && record._id === recordId}
|
||||||
|
<Table {title} {schema} {data} />
|
||||||
|
{/if}
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto, params } 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"
|
||||||
|
@ -32,13 +32,17 @@
|
||||||
$: paginatedData =
|
$: paginatedData =
|
||||||
sorted && sorted.length
|
sorted && sorted.length
|
||||||
? sorted.slice(
|
? sorted.slice(
|
||||||
currentPage * ITEMS_PER_PAGE,
|
currentPage * ITEMS_PER_PAGE,
|
||||||
currentPage * ITEMS_PER_PAGE + ITEMS_PER_PAGE
|
currentPage * ITEMS_PER_PAGE + ITEMS_PER_PAGE,
|
||||||
)
|
)
|
||||||
: []
|
: []
|
||||||
|
$: modelId = data?.length ? data[0].modelId : null
|
||||||
|
|
||||||
function selectRelationship(recordId, fieldName) {
|
function selectRelationship(record, fieldName) {
|
||||||
$goto(`../relationship/${recordId}/${fieldName}`)
|
if (!record?.[fieldName]?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$goto(`/${$params.application}/backend/model/${modelId}/relationship/${record._id}/${fieldName}`)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -46,66 +50,68 @@
|
||||||
<div class="table-controls">
|
<div class="table-controls">
|
||||||
<h2 class="title">{title}</h2>
|
<h2 class="title">{title}</h2>
|
||||||
<div class="popovers">
|
<div class="popovers">
|
||||||
<slot />
|
<slot/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="bb-table">
|
<table class="bb-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{#if allowEditing}
|
{#if allowEditing}
|
||||||
<th class="edit-header">
|
<th class="edit-header">
|
||||||
<div>Edit</div>
|
<div>Edit</div>
|
||||||
</th>
|
</th>
|
||||||
{/if}
|
{/if}
|
||||||
{#each columns as header}
|
{#each columns as header}
|
||||||
<th>
|
<th>
|
||||||
{#if allowEditing}
|
{#if allowEditing}
|
||||||
<ColumnHeaderPopover field={schema[header]} />
|
<ColumnHeaderPopover field={schema[header]}/>
|
||||||
{:else}{header}{/if}
|
{:else}
|
||||||
</th>
|
<div class="header">{header}</div>
|
||||||
{/each}
|
{/if}
|
||||||
</tr>
|
</th>
|
||||||
|
{/each}
|
||||||
|
</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}
|
||||||
|
<td class="no-border">
|
||||||
|
{#if idx === 0}No data.{/if}
|
||||||
|
</td>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
{#each paginatedData as row}
|
||||||
|
<tr>
|
||||||
{#if allowEditing}
|
{#if allowEditing}
|
||||||
<td class="no-border">No data.</td>
|
<td>
|
||||||
|
<EditRowPopover {row}/>
|
||||||
|
</td>
|
||||||
{/if}
|
{/if}
|
||||||
{#each columns as header, idx}
|
{#each columns as header}
|
||||||
<td class="no-border">
|
<td>
|
||||||
{#if idx === 0}No data.{/if}
|
{#if schema[header].type === 'link'}
|
||||||
|
<div
|
||||||
|
class:link={row[header] && row[header].length}
|
||||||
|
on:click={() => selectRelationship(row, header)}>
|
||||||
|
{row[header] ? row[header].length : 0} linked row(s)
|
||||||
|
</div>
|
||||||
|
{:else if schema[header].type === 'attachment'}
|
||||||
|
<AttachmentList files={row[header] || []}/>
|
||||||
|
{:else}{getOr('', header, row)}{/if}
|
||||||
</td>
|
</td>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
</tr>
|
||||||
{#each paginatedData as row}
|
{/each}
|
||||||
<tr>
|
|
||||||
{#if allowEditing}
|
|
||||||
<td>
|
|
||||||
<EditRowPopover {row} />
|
|
||||||
</td>
|
|
||||||
{/if}
|
|
||||||
{#each columns as header}
|
|
||||||
<td>
|
|
||||||
{#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] || []} />
|
|
||||||
{:else}{getOr('', header, row)}{/if}
|
|
||||||
</td>
|
|
||||||
{/each}
|
|
||||||
</tr>
|
|
||||||
{/each}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<TablePagination
|
<TablePagination
|
||||||
{data}
|
{data}
|
||||||
bind:currentPage
|
bind:currentPage
|
||||||
pageItemCount={paginatedData.length}
|
pageItemCount={paginatedData.length}
|
||||||
{ITEMS_PER_PAGE} />
|
{ITEMS_PER_PAGE}/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -121,12 +127,14 @@
|
||||||
border: 1px solid var(--grey-4);
|
border: 1px solid var(--grey-4);
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
thead {
|
thead {
|
||||||
background: var(--grey-3);
|
background: var(--grey-3);
|
||||||
border: 1px solid var(--grey-4);
|
border: 1px solid var(--grey-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
thead th {
|
thead th {
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
@ -138,11 +146,16 @@
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
thead th:hover {
|
thead th:hover {
|
||||||
color: var(--blue);
|
color: var(--blue);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
@ -152,6 +165,7 @@
|
||||||
padding: var(--spacing-l) var(--spacing-m);
|
padding: var(--spacing-l) var(--spacing-m);
|
||||||
font-size: var(--font-size-xs);
|
font-size: var(--font-size-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
td.no-border {
|
td.no-border {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
@ -161,6 +175,7 @@
|
||||||
transition: 0.3s background-color;
|
transition: 0.3s background-color;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody tr:hover {
|
tbody tr:hover {
|
||||||
background: var(--grey-1);
|
background: var(--grey-1);
|
||||||
}
|
}
|
||||||
|
@ -175,11 +190,13 @@
|
||||||
|
|
||||||
:global(.popovers > div) {
|
:global(.popovers > div) {
|
||||||
margin-right: var(--spacing-m);
|
margin-right: var(--spacing-m);
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-header {
|
.edit-header {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-header:hover {
|
.edit-header:hover {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
|
@ -188,6 +205,7 @@
|
||||||
.link {
|
.link {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link:hover {
|
.link:hover {
|
||||||
color: var(--grey-6);
|
color: var(--grey-6);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -69,11 +69,13 @@
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<Toggle
|
{#if field.type !== 'link'}
|
||||||
checked={!field.constraints.presence.allowEmpty}
|
<Toggle
|
||||||
on:change={e => (field.constraints.presence.allowEmpty = !e.target.checked)}
|
checked={!field.constraints.presence.allowEmpty}
|
||||||
thin
|
on:change={e => (field.constraints.presence.allowEmpty = !e.target.checked)}
|
||||||
text="Required" />
|
thin
|
||||||
|
text="Required" />
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if field.type === 'string'}
|
{#if field.type === 'string'}
|
||||||
<Input
|
<Input
|
||||||
|
|
|
@ -13,19 +13,17 @@
|
||||||
export let onClosed
|
export let onClosed
|
||||||
|
|
||||||
let errors = []
|
let errors = []
|
||||||
let selectedModel
|
$: model = record.modelId ? $backendUiStore.models.find(model => model._id === record?.modelId) : $backendUiStore.selectedModel
|
||||||
|
$: modelSchema = Object.entries(model?.schema ?? {})
|
||||||
$: modelSchema = $backendUiStore.selectedModel
|
$: console.log(modelSchema)
|
||||||
? Object.entries($backendUiStore.selectedModel.schema)
|
|
||||||
: []
|
|
||||||
|
|
||||||
async function saveRecord() {
|
async function saveRecord() {
|
||||||
const recordResponse = await api.saveRecord(
|
const recordResponse = await api.saveRecord(
|
||||||
{
|
{
|
||||||
...record,
|
...record,
|
||||||
modelId: $backendUiStore.selectedModel._id,
|
modelId: model._id,
|
||||||
},
|
},
|
||||||
$backendUiStore.selectedModel._id
|
model._id
|
||||||
)
|
)
|
||||||
if (recordResponse.errors) {
|
if (recordResponse.errors) {
|
||||||
errors = Object.keys(recordResponse.errors)
|
errors = Object.keys(recordResponse.errors)
|
||||||
|
|
|
@ -1,18 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
|
||||||
import { params } from "@sveltech/routify"
|
import { params } from "@sveltech/routify"
|
||||||
import { backendUiStore } from "builderStore"
|
import RelationshipDataTable from "components/backend/DataTable/RelationshipDataTable.svelte"
|
||||||
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>
|
</script>
|
||||||
|
|
||||||
<div>hello world!</div>
|
<RelationshipDataTable modelId={$params.selectedModel} recordId={$params.selectedRecord}
|
||||||
|
fieldName={$params.selectedField}/>
|
||||||
|
|
Loading…
Reference in New Issue