Improve UX when editing view calculations

This commit is contained in:
Andrew Kingston 2024-10-10 16:05:02 +01:00
parent 1cbae36683
commit 88b2f423f7
No known key found for this signature in database
3 changed files with 69 additions and 65 deletions

View File

@ -147,6 +147,9 @@
.spectrum-Dialog--extraLarge { .spectrum-Dialog--extraLarge {
width: 1000px; width: 1000px;
} }
.spectrum-Dialog--medium {
width: 540px;
}
.content-grid { .content-grid {
display: grid; display: grid;

View File

@ -5,8 +5,10 @@
ModalContent, ModalContent,
Select, Select,
Icon, Icon,
Multiselect,
} from "@budibase/bbui" } from "@budibase/bbui"
import { CalculationType, FieldType } from "@budibase/types" import { CalculationType, canGroupBy, FieldType } from "@budibase/types"
import InfoDisplay from "pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/InfoDisplay.svelte"
import { getContext } from "svelte" import { getContext } from "svelte"
const { definition, datasource, rows } = getContext("grid") const { definition, datasource, rows } = getContext("grid")
@ -35,13 +37,16 @@
let modal let modal
let calculations = [] let calculations = []
let groupings = [] let groupBy = []
let schema = {}
$: schema = $definition?.schema || {} $: schema = $definition?.schema || {}
$: count = extractCalculations($definition?.schema || {}).length
$: groupByOptions = getGroupByOptions(schema)
const open = () => { const open = () => {
calculations = extractCalculations(schema) calculations = extractCalculations(schema)
groupings = calculations.length ? extractGroupings(schema) : [] groupBy = calculations.length ? extractGroupBy(schema) : []
modal?.show() modal?.show()
} }
@ -59,15 +64,13 @@
})) }))
} }
const extractGroupings = schema => { const extractGroupBy = schema => {
if (!schema) { if (!schema) {
return [] return []
} }
return Object.keys(schema) return Object.keys(schema).filter(field => {
.filter(field => {
return schema[field].calculationType == null && schema[field].visible return schema[field].calculationType == null && schema[field].visible
}) })
.map(field => ({ field }))
} }
// Gets the available types for a given calculation // Gets the available types for a given calculation
@ -103,18 +106,16 @@
.map(([field]) => field) .map(([field]) => field)
} }
// Gets the available fields to group by for a given grouping // Gets the available fields to group by
const getGroupingOptions = (self, groupings, schema) => { const getGroupByOptions = schema => {
return Object.entries(schema) return Object.entries(schema)
.filter(([field, fieldSchema]) => { .filter(([field, fieldSchema]) => {
// Don't allow grouping by calculations // Don't allow grouping by calculations
if (fieldSchema.calculationType) { if (fieldSchema.calculationType) {
return false return false
} }
// Don't allow duplicates // Don't allow complex types
return !groupings.some(grouping => { return canGroupBy(fieldSchema.type)
return grouping !== self && grouping.field === field
})
}) })
.map(([field]) => field) .map(([field]) => field)
} }
@ -126,71 +127,65 @@
const deleteCalc = idx => { const deleteCalc = idx => {
calculations = calculations.toSpliced(idx, 1) calculations = calculations.toSpliced(idx, 1)
// Remove any groupings if clearing the last calculation // Remove any grouping if clearing the last calculation
if (!calculations.length) { if (!calculations.length) {
groupings = [] groupBy = []
} }
} }
const addGrouping = () => {
groupings = [...groupings, {}]
}
const deleteGrouping = idx => {
groupings = groupings.toSpliced(idx, 1)
}
const save = async () => { const save = async () => {
let schema = {} let newSchema = {}
// Prune empty stuff
calculations = calculations.filter(calc => calc.type && calc.field)
groupings = groupings.filter(grouping => grouping.field)
// Add calculations // Add calculations
for (let calc of calculations) { for (let calc of calculations) {
if (!calc.type || !calc.field) {
continue
}
const typeOption = calculationTypeOptions.find(x => x.value === calc.type) const typeOption = calculationTypeOptions.find(x => x.value === calc.type)
const name = `${typeOption.label} ${calc.field}` const name = `${typeOption.label} ${calc.field}`
schema[name] = { newSchema[name] = {
calculationType: calc.type, calculationType: calc.type,
field: calc.field, field: calc.field,
visible: true, visible: true,
} }
} }
// Add groupings // Add other fields
for (let grouping of groupings) { for (let field of Object.keys(schema)) {
schema[grouping.field] = { if (schema[field].calculationType) {
...$definition.schema[grouping.field], continue
visible: true, }
newSchema[field] = {
...schema[field],
visible: groupBy.includes(field),
} }
} }
// Ensure primary display is valid // Ensure primary display is valid
let primaryDisplay = $definition.primaryDisplay let primaryDisplay = $definition.primaryDisplay
if (!primaryDisplay || !schema[primaryDisplay]) { if (!primaryDisplay || !newSchema[primaryDisplay]?.visible) {
primaryDisplay = groupings[0]?.field primaryDisplay = groupBy[0]
} }
// Save changes // Save changes
await datasource.actions.saveDefinition({ await datasource.actions.saveDefinition({
...$definition, ...$definition,
primaryDisplay, primaryDisplay,
schema, schema: newSchema,
}) })
await rows.actions.refreshData() await rows.actions.refreshData()
} }
</script> </script>
<ActionButton icon="WebPage" quiet on:click={open}> <ActionButton icon="WebPage" quiet on:click={open}>
Configure calculations Configure calculations{count ? `: ${count}` : ""}
</ActionButton> </ActionButton>
<Modal bind:this={modal}> <Modal bind:this={modal}>
<ModalContent <ModalContent
title="Calculations" title="Calculations"
confirmText="Save" confirmText="Save"
size="L" size="M"
onConfirm={save} onConfirm={save}
> >
{#if !calculations.length} {#if !calculations.length}
@ -222,32 +217,30 @@
color="var(--spectrum-global-color-gray-700)" color="var(--spectrum-global-color-gray-700)"
/> />
{/each} {/each}
{#each groupings as group, idx} <span>Group by</span>
<span>{idx === 0 ? "Group by" : "and"}</span> <div class="group-by">
<Select <Multiselect
options={getGroupingOptions(group, groupings, schema)} options={groupByOptions}
bind:value={group.field} bind:value={groupBy}
placeholder="Column" placeholder="None"
/> />
<Icon </div>
hoverable
name="Close"
size="S"
on:click={() => deleteGrouping(idx)}
color="var(--spectrum-global-color-gray-700)"
/>
<span />
<span />
{/each}
</div> </div>
<div class="buttons"> <div class="buttons">
<ActionButton quiet icon="Add" on:click={addCalc}> <ActionButton
quiet
icon="Add"
on:click={addCalc}
disabled={calculations.length >= 5}
>
Add calculation Add calculation
</ActionButton> </ActionButton>
<ActionButton quiet icon="Add" on:click={addGrouping}>
Group by
</ActionButton>
</div> </div>
<InfoDisplay
icon="Help"
quiet
body="Calculations only work with numeric columns and a maximum of 5 calculations can be added at once."
/>
{/if} {/if}
</ModalContent> </ModalContent>
</Modal> </Modal>
@ -264,7 +257,9 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
} }
.group-by {
grid-column: 2 / 5;
}
span { span {
text-align: right;
} }
</style> </style>

View File

@ -4,9 +4,10 @@
export let title export let title
export let body export let body
export let icon = "HelpOutline" export let icon = "HelpOutline"
export let quiet = false
</script> </script>
<div class="info" class:noTitle={!title}> <div class="info" class:noTitle={!title} class:quiet>
{#if title} {#if title}
<div class="title"> <div class="title">
<Icon name={icon} /> <Icon name={icon} />
@ -40,15 +41,20 @@
color: var(--spectrum-global-color-gray-600); color: var(--spectrum-global-color-gray-600);
} }
.info { .info {
padding: var(--spacing-m) var(--spacing-l) var(--spacing-l) var(--spacing-l);
background-color: var(--background-alt); background-color: var(--background-alt);
padding: var(--spacing-m) var(--spacing-l) var(--spacing-m) var(--spacing-l);
border-radius: var(--border-radius-s); border-radius: var(--border-radius-s);
font-size: 13px; font-size: 13px;
} }
.quiet {
background: none;
color: var(--spectrum-global-color-gray-700);
padding: 0;
}
.noTitle { .noTitle {
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--spacing-m); gap: var(--spacing-l);
} }
.info :global(a) { .info :global(a) {
color: inherit; color: inherit;