fix: allow adding/removing/editing columns

This commit is contained in:
Shariq Ansari 2025-01-01 03:28:54 +05:30
parent 45c759cc4e
commit 29d23d78c5
4 changed files with 215 additions and 153 deletions

View File

@ -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"

View File

@ -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),

View File

@ -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"

View File

@ -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"