fix: allow adding/removing/editing columns
This commit is contained in:
parent
45c759cc4e
commit
29d23d78c5
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog v-model="show" :options="{ size: '3xl' }">
|
<Dialog v-model="show" :options="{ size: '4xl' }">
|
||||||
<template #body-title>
|
<template #body-title>
|
||||||
<h3
|
<h3
|
||||||
class="flex items-center gap-2 text-2xl font-semibold leading-6 text-ink-gray-9"
|
class="flex items-center gap-2 text-2xl font-semibold leading-6 text-ink-gray-9"
|
||||||
|
|||||||
@ -72,8 +72,10 @@
|
|||||||
item-key="label"
|
item-key="label"
|
||||||
class="flex flex-col gap-5.5"
|
class="flex flex-col gap-5.5"
|
||||||
>
|
>
|
||||||
<template #item="{ element: section }">
|
<template #item="{ element: section, index: i }">
|
||||||
<div class="flex flex-col gap-1.5 p-2.5 bg-surface-gray-2 rounded">
|
<div
|
||||||
|
class="section flex flex-col gap-1.5 p-2.5 bg-surface-gray-2 rounded cursor-grab"
|
||||||
|
>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div
|
<div
|
||||||
class="flex h-7 max-w-fit cursor-pointer items-center gap-2 text-base font-medium leading-4 text-ink-gray-9"
|
class="flex h-7 max-w-fit cursor-pointer items-center gap-2 text-base font-medium leading-4 text-ink-gray-9"
|
||||||
@ -82,9 +84,12 @@
|
|||||||
<div
|
<div
|
||||||
v-if="!section.editingLabel"
|
v-if="!section.editingLabel"
|
||||||
class="flex items-center gap-2"
|
class="flex items-center gap-2"
|
||||||
:class="{ 'text-ink-gray-3': section.hideLabel }"
|
:class="{
|
||||||
|
'text-ink-gray-3': section.hideLabel || !section.label,
|
||||||
|
italic: !section.label,
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
{{ __(section.label) || __('Untitled') }}
|
{{ __(section.label) || __('No label') }}
|
||||||
<FeatherIcon
|
<FeatherIcon
|
||||||
v-if="section.collapsible"
|
v-if="section.collapsible"
|
||||||
name="chevron-down"
|
name="chevron-down"
|
||||||
@ -106,7 +111,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Dropdown :options="getSectionOptions(section)">
|
<Dropdown :options="getSectionOptions(i, section, tab)">
|
||||||
<template #default>
|
<template #default>
|
||||||
<Button variant="ghost">
|
<Button variant="ghost">
|
||||||
<FeatherIcon name="more-horizontal" class="h-4" />
|
<FeatherIcon name="more-horizontal" class="h-4" />
|
||||||
@ -114,69 +119,77 @@
|
|||||||
</template>
|
</template>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-1.5">
|
<Draggable
|
||||||
<div
|
class="flex gap-2"
|
||||||
class="w-full p-2 border border-dashed border-outline-gray-2 rounded bg-surface-white"
|
:list="section.columns"
|
||||||
v-for="(column, index) in section.columns"
|
group="columns"
|
||||||
:key="index"
|
item-key="fields"
|
||||||
>
|
>
|
||||||
<Draggable
|
<template #item="{ element: column }">
|
||||||
:list="column.fields"
|
<div
|
||||||
group="fields"
|
class="flex flex-col gap-1.5 flex-1 p-2 border border-dashed border-outline-gray-2 rounded bg-surface-modal cursor-grab"
|
||||||
item-key="label"
|
|
||||||
class="flex flex-col gap-1.5"
|
|
||||||
handle=".cursor-grab"
|
|
||||||
>
|
>
|
||||||
<template #item="{ element: field }">
|
<Draggable
|
||||||
<div
|
:list="column.fields"
|
||||||
class="px-2.5 py-2 border border-outline-gray-2 rounded text-base bg-surface-modal text-ink-gray-8 flex items-center leading-4 justify-between gap-2"
|
group="fields"
|
||||||
>
|
item-key="label"
|
||||||
<div class="flex items-center gap-2 truncate">
|
class="flex flex-col gap-1.5"
|
||||||
<DragVerticalIcon class="h-3.5 cursor-grab" />
|
handle=".cursor-grab"
|
||||||
<div class="truncate">{{ field.label }}</div>
|
>
|
||||||
</div>
|
<template #item="{ element: field }">
|
||||||
<Button
|
<div
|
||||||
variant="ghost"
|
class="field px-2.5 py-2 border border-outline-gray-2 rounded text-base bg-surface-modal text-ink-gray-8 flex items-center leading-4 justify-between gap-2"
|
||||||
class="!size-4 rounded-sm"
|
|
||||||
icon="x"
|
|
||||||
@click="
|
|
||||||
column.fields.splice(column.fields.indexOf(field), 1)
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Draggable>
|
|
||||||
<Autocomplete
|
|
||||||
v-if="fields.data"
|
|
||||||
value=""
|
|
||||||
:options="fields.data"
|
|
||||||
@change="(e) => addField(column, e)"
|
|
||||||
>
|
|
||||||
<template #target="{ togglePopover }">
|
|
||||||
<div class="gap-2 w-full">
|
|
||||||
<Button
|
|
||||||
class="w-full !h-8 !bg-surface-modal"
|
|
||||||
variant="outline"
|
|
||||||
@click="togglePopover()"
|
|
||||||
:label="__('Add Field')"
|
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<div class="flex items-center gap-2 truncate">
|
||||||
<FeatherIcon name="plus" class="h-4" />
|
<DragVerticalIcon class="h-3.5 cursor-grab" />
|
||||||
</template>
|
<div class="truncate">{{ field.label }}</div>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
<Button
|
||||||
</template>
|
variant="ghost"
|
||||||
<template #item-label="{ option }">
|
class="!size-4 rounded-sm"
|
||||||
<div class="flex flex-col gap-1 text-ink-gray-9">
|
icon="x"
|
||||||
<div>{{ option.label }}</div>
|
@click="
|
||||||
<div class="text-ink-gray-4 text-sm">
|
column.fields.splice(
|
||||||
{{ `${option.fieldname} - ${option.fieldtype}` }}
|
column.fields.indexOf(field),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
</Draggable>
|
||||||
</Autocomplete>
|
<Autocomplete
|
||||||
</div>
|
v-if="fields.data"
|
||||||
</div>
|
value=""
|
||||||
|
:options="fields.data"
|
||||||
|
@change="(e) => addField(column, e)"
|
||||||
|
>
|
||||||
|
<template #target="{ togglePopover }">
|
||||||
|
<div class="gap-2 w-full">
|
||||||
|
<Button
|
||||||
|
class="w-full !h-8 !bg-surface-modal"
|
||||||
|
variant="outline"
|
||||||
|
@click="togglePopover()"
|
||||||
|
:label="__('Add Field')"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<FeatherIcon name="plus" class="h-4" />
|
||||||
|
</template>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #item-label="{ option }">
|
||||||
|
<div class="flex flex-col gap-1 text-ink-gray-9">
|
||||||
|
<div>{{ option.label }}</div>
|
||||||
|
<div class="text-ink-gray-4 text-sm">
|
||||||
|
{{ `${option.fieldname} - ${option.fieldtype}` }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Autocomplete>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Draggable>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Draggable>
|
</Draggable>
|
||||||
@ -189,7 +202,7 @@
|
|||||||
tabs[tabIndex].sections.push({
|
tabs[tabIndex].sections.push({
|
||||||
label: __('New Section'),
|
label: __('New Section'),
|
||||||
opened: true,
|
opened: true,
|
||||||
fields: [],
|
columns: [{ fields: [] }],
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
@ -297,12 +310,12 @@ function addField(column, field) {
|
|||||||
function getTabOptions(tab) {
|
function getTabOptions(tab) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: 'Edit',
|
label: __('Edit'),
|
||||||
icon: 'edit',
|
icon: 'edit',
|
||||||
onClick: () => (tab.editingLabel = true),
|
onClick: () => (tab.editingLabel = true),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Remove tab',
|
label: __('Remove tab'),
|
||||||
icon: 'trash-2',
|
icon: 'trash-2',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (props.tabs.length == 1) {
|
if (props.tabs.length == 1) {
|
||||||
@ -316,96 +329,145 @@ function getTabOptions(tab) {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSectionOptions(section) {
|
function getSectionOptions(i, section, tab) {
|
||||||
|
let column = section.columns[section.columns.length - 1]
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: 'Edit',
|
group: __('Section'),
|
||||||
icon: 'edit',
|
items: [
|
||||||
onClick: () => (section.editingLabel = true),
|
{
|
||||||
condition: () => section.editable !== false,
|
label: __('Edit'),
|
||||||
|
icon: 'edit',
|
||||||
|
onClick: () => (section.editingLabel = true),
|
||||||
|
condition: () => section.editable !== false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: section.collapsible ? __('Uncollapsible') : __('Collapsible'),
|
||||||
|
icon: section.collapsible ? 'chevron-up' : 'chevron-down',
|
||||||
|
onClick: () => (section.collapsible = !section.collapsible),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: section.hideLabel ? __('Show label') : __('Hide label'),
|
||||||
|
icon: section.hideLabel ? 'eye' : 'eye-off',
|
||||||
|
onClick: () => (section.hideLabel = !section.hideLabel),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: section.hideBorder ? __('Show border') : __('Hide border'),
|
||||||
|
icon: 'minus',
|
||||||
|
onClick: () => (section.hideBorder = !section.hideBorder),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Remove section'),
|
||||||
|
icon: 'trash-2',
|
||||||
|
onClick: () => {
|
||||||
|
tab.sections.splice(tab.sections.indexOf(section), 1)
|
||||||
|
},
|
||||||
|
condition: () => section.editable !== false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Remove and move columns to {0} section', [
|
||||||
|
i == 0 ? __('next') : __('previous'),
|
||||||
|
]),
|
||||||
|
icon: 'trash-2',
|
||||||
|
onClick: () => {
|
||||||
|
let targetSection = tab.sections[i == 0 ? i + 1 : i - 1]
|
||||||
|
if (i == 0) {
|
||||||
|
targetSection.columns = section.columns.concat(
|
||||||
|
targetSection.columns,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
targetSection.columns = targetSection.columns.concat(
|
||||||
|
section.columns,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
tab.sections.splice(tab.sections.indexOf(section), 1)
|
||||||
|
},
|
||||||
|
condition: () => section.editable !== false && section.columns.length,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Move to previous tab'),
|
||||||
|
icon: 'corner-up-left',
|
||||||
|
onClick: () => {
|
||||||
|
let previousTab = props.tabs[tabIndex.value - 1]
|
||||||
|
previousTab.sections.push(section)
|
||||||
|
props.tabs[tabIndex.value].sections.splice(
|
||||||
|
props.tabs[tabIndex.value].sections.indexOf(section),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
tabIndex.value -= 1
|
||||||
|
},
|
||||||
|
condition: () =>
|
||||||
|
section.editable !== false && props.tabs[tabIndex.value - 1],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Move to next tab'),
|
||||||
|
icon: 'corner-up-right',
|
||||||
|
onClick: () => {
|
||||||
|
let nextTab = props.tabs[tabIndex.value + 1]
|
||||||
|
nextTab.sections.push(section)
|
||||||
|
props.tabs[tabIndex.value].sections.splice(
|
||||||
|
props.tabs[tabIndex.value].sections.indexOf(section),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
tabIndex.value += 1
|
||||||
|
},
|
||||||
|
condition: () =>
|
||||||
|
section.editable !== false && props.tabs[tabIndex.value + 1],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: section.collapsible ? 'Uncollapsible' : 'Collapsible',
|
group: __('Column'),
|
||||||
icon: section.collapsible ? 'chevron-up' : 'chevron-down',
|
items: [
|
||||||
onClick: () => (section.collapsible = !section.collapsible),
|
{
|
||||||
},
|
label: __('Add column'),
|
||||||
{
|
icon: 'columns',
|
||||||
label: section.hideLabel ? 'Show label' : 'Hide label',
|
onClick: () => {
|
||||||
icon: section.hideLabel ? 'eye' : 'eye-off',
|
section.columns.push({ label: '', fields: [] })
|
||||||
onClick: () => (section.hideLabel = !section.hideLabel),
|
},
|
||||||
},
|
condition: () => section.columns.length < 4,
|
||||||
{
|
},
|
||||||
label: section.hideBorder ? 'Show border' : 'Hide border',
|
{
|
||||||
icon: 'minus',
|
label: __('Remove column'),
|
||||||
onClick: () => (section.hideBorder = !section.hideBorder),
|
icon: 'trash-2',
|
||||||
},
|
onClick: () => section.columns.pop(),
|
||||||
{
|
condition: () => section.columns.length > 1,
|
||||||
label: 'Add column',
|
},
|
||||||
icon: 'columns',
|
{
|
||||||
onClick: () =>
|
label: __('Remove and move fields to previous column'),
|
||||||
(section.columns = section.columns ? section.columns + 1 : 4),
|
icon: 'trash-2',
|
||||||
condition: () => !section.columns || section.columns < 4,
|
onClick: () => {
|
||||||
},
|
let previousColumn = section.columns[section.columns.length - 2]
|
||||||
{
|
previousColumn.fields = previousColumn.fields.concat(column.fields)
|
||||||
label: 'Remove column',
|
section.columns.pop()
|
||||||
icon: 'columns',
|
},
|
||||||
onClick: () =>
|
condition: () => section.columns.length > 1 && column.fields.length,
|
||||||
(section.columns = section.columns ? section.columns - 1 : 2),
|
},
|
||||||
condition: () => !section.columns || section.columns > 1,
|
{
|
||||||
},
|
label: __('Move to next section'),
|
||||||
{
|
icon: 'corner-up-right',
|
||||||
label: 'Remove section',
|
onClick: () => {
|
||||||
icon: 'trash-2',
|
let nextSection = tab.sections[i + 1]
|
||||||
onClick: () => {
|
nextSection.columns.push(column)
|
||||||
let currentTab = props.tabs[tabIndex.value]
|
section.columns.pop()
|
||||||
currentTab.sections.splice(currentTab.sections.indexOf(section), 1)
|
},
|
||||||
},
|
condition: () => tab.sections[i + 1],
|
||||||
condition: () => section.editable !== false,
|
},
|
||||||
},
|
{
|
||||||
{
|
label: __('Move to previous section'),
|
||||||
label: 'Move to previous tab',
|
icon: 'corner-up-left',
|
||||||
icon: 'trash-2',
|
onClick: () => {
|
||||||
onClick: () => {
|
let previousSection = tab.sections[i - 1]
|
||||||
let previousTab = props.tabs[tabIndex.value - 1]
|
previousSection.columns.push(column)
|
||||||
previousTab.sections.push(section)
|
section.columns.pop()
|
||||||
props.tabs[tabIndex.value].sections.splice(
|
},
|
||||||
props.tabs[tabIndex.value].sections.indexOf(section),
|
condition: () => tab.sections[i - 1],
|
||||||
1,
|
},
|
||||||
)
|
],
|
||||||
tabIndex.value -= 1
|
|
||||||
},
|
|
||||||
condition: () =>
|
|
||||||
section.editable !== false && props.tabs[tabIndex.value - 1],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Move to next tab',
|
|
||||||
icon: 'trash-2',
|
|
||||||
onClick: () => {
|
|
||||||
let nextTab = props.tabs[tabIndex.value + 1]
|
|
||||||
nextTab.sections.push(section)
|
|
||||||
props.tabs[tabIndex.value].sections.splice(
|
|
||||||
props.tabs[tabIndex.value].sections.indexOf(section),
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
tabIndex.value += 1
|
|
||||||
},
|
|
||||||
condition: () =>
|
|
||||||
section.editable !== false && props.tabs[tabIndex.value + 1],
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
function gridClass(columns) {
|
|
||||||
columns = columns || 3
|
|
||||||
let griColsMap = {
|
|
||||||
1: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-1',
|
|
||||||
2: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-2',
|
|
||||||
3: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3',
|
|
||||||
4: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4',
|
|
||||||
}
|
|
||||||
return griColsMap[columns]
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.doctype,
|
() => props.doctype,
|
||||||
() => fields.fetch(params.value),
|
() => fields.fetch(params.value),
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog v-model="show" :options="{ size: '3xl' }">
|
<Dialog v-model="show" :options="{ size: '4xl' }">
|
||||||
<template #body-title>
|
<template #body-title>
|
||||||
<h3
|
<h3
|
||||||
class="flex items-center gap-2 text-2xl font-semibold leading-6 text-ink-gray-9"
|
class="flex items-center gap-2 text-2xl font-semibold leading-6 text-ink-gray-9"
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog v-model="show" :options="{ size: '3xl' }">
|
<Dialog v-model="show" :options="{ size: '4xl' }">
|
||||||
<template #body-title>
|
<template #body-title>
|
||||||
<h3
|
<h3
|
||||||
class="flex items-center gap-2 text-2xl font-semibold leading-6 text-ink-gray-9"
|
class="flex items-center gap-2 text-2xl font-semibold leading-6 text-ink-gray-9"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user