crm/frontend/src/components/ColumnSettings.vue
Shariq Ansari 7b2168232e refactor: replace NestedPopover with Popover component across multiple files
(cherry picked from commit 38b6674cc134a2cffa907c03dbdfce9b7c916e01)
2025-08-18 17:10:22 +00:00

301 lines
8.1 KiB
Vue

<template>
<Popover placement="bottom-end">
<template #target="{ togglePopover }">
<Button :label="__('Columns')" @click="togglePopover">
<template v-if="hideLabel">
<ColumnsIcon class="h-4" />
</template>
<template v-if="!hideLabel" #prefix>
<ColumnsIcon class="h-4" />
</template>
</Button>
</template>
<template #body="{ close }">
<div
class="my-2 p-1.5 min-w-40 rounded-lg bg-surface-modal shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none"
>
<div v-if="!edit">
<Draggable
:list="columns"
@end="apply"
:delay="isTouchScreenDevice() ? 200 : 0"
item-key="key"
class="list-group"
>
<template #item="{ element }">
<div
class="flex cursor-grab items-center justify-between gap-6 rounded px-2 py-1.5 text-base text-ink-gray-8 hover:bg-surface-gray-2"
>
<div class="flex items-center gap-2">
<DragIcon class="h-3.5" />
<div>{{ __(element.label) }}</div>
</div>
<div class="flex cursor-pointer items-center gap-0.5">
<Button
variant="ghost"
class="!h-5 w-5 !p-1"
@click="editColumn(element)"
>
<template #icon>
<EditIcon class="h-3.5" />
</template>
</Button>
<Button
variant="ghost"
class="!h-5 w-5 !p-1"
@click="removeColumn(element)"
>
<template #icon>
<FeatherIcon name="x" class="h-3.5" />
</template>
</Button>
</div>
</div>
</template>
</Draggable>
<div
class="mt-1.5 flex flex-col gap-1 border-t border-outline-gray-modals pt-1.5"
>
<Autocomplete
value=""
:options="fields"
@change="(e) => addColumn(e)"
>
<template #target="{ togglePopover }">
<Button
class="w-full !justify-start !text-ink-gray-5"
variant="ghost"
:label="__('Add Column')"
iconLeft="plus"
@click="togglePopover"
/>
</template>
</Autocomplete>
<Button
v-if="columnsUpdated"
class="w-full !justify-start !text-ink-gray-5"
variant="ghost"
:label="__('Reset Changes')"
:iconLeft="ReloadIcon"
@click="reset(close)"
/>
<Button
v-if="!is_default"
class="w-full !justify-start !text-ink-gray-5"
variant="ghost"
:label="__('Reset to Default')"
:iconLeft="ReloadIcon"
@click="resetToDefault(close)"
/>
</div>
</div>
<div v-else>
<div
class="flex flex-col items-center justify-between gap-2 rounded px-2 py-1.5 text-base text-ink-gray-8"
>
<div class="flex flex-col items-center gap-3">
<FormControl
type="text"
size="md"
:label="__('Label')"
v-model="column.label"
class="sm:w-full w-52"
:placeholder="__('First Name')"
/>
<FormControl
type="text"
size="md"
:label="__('Width')"
class="sm:w-full w-52"
v-model="column.width"
placeholder="10rem"
:description="
__(
'Width can be in number, pixel or rem (eg. 3, 30px, 10rem)',
)
"
:debounce="500"
/>
</div>
<div class="flex w-full gap-2 border-t pt-2">
<Button
variant="subtle"
:label="__('Cancel')"
class="w-full flex-1"
@click="cancelUpdate"
/>
<Button
variant="solid"
:label="__('Update')"
class="w-full flex-1"
@click="updateColumn(column)"
/>
</div>
</div>
</div>
</div>
</template>
</Popover>
</template>
<script setup>
import ColumnsIcon from '@/components/Icons/ColumnsIcon.vue'
import EditIcon from '@/components/Icons/EditIcon.vue'
import DragIcon from '@/components/Icons/DragIcon.vue'
import ReloadIcon from '@/components/Icons/ReloadIcon.vue'
import { isTouchScreenDevice } from '@/utils'
import { Autocomplete, Popover } from 'frappe-ui'
import Draggable from 'vuedraggable'
import { computed, ref } from 'vue'
import { watchOnce } from '@vueuse/core'
const props = defineProps({
doctype: {
type: String,
required: true,
},
hideLabel: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['update'])
const columnsUpdated = ref(false)
const oldValues = ref({
columns: [],
rows: [],
isDefault: false,
})
const list = defineModel()
const edit = ref(false)
const column = ref({
old: {},
label: '',
key: '',
width: '10rem',
})
const is_default = computed({
get: () => list.value?.data?.is_default,
set: (val) => {
list.value.data.is_default = val
},
})
const columns = computed({
get: () => list.value?.data?.columns,
set: (val) => {
list.value.data.columns = val
},
})
const rows = computed({
get: () => list.value?.data?.rows,
set: (val) => {
list.value.data.rows = val
},
})
const fields = computed(() => {
let allFields = list.value?.data?.fields
if (!allFields) return []
return allFields.filter((field) => {
return !columns.value.find((column) => column.key === field.fieldname)
})
})
function addColumn(c) {
let align = ['Float', 'Int', 'Percent', 'Currency'].includes(c.type)
? 'right'
: 'left'
let _column = {
label: c.label,
type: c.fieldtype,
key: c.fieldname,
width: '10rem',
align,
}
columns.value.push(_column)
rows.value.push(c.value)
apply(true)
}
function removeColumn(c) {
columns.value = columns.value.filter((column) => column.key !== c.key)
if (c.key !== 'name') {
rows.value = rows.value.filter((row) => row !== c.key)
}
apply()
}
function editColumn(c) {
edit.value = true
column.value = c
column.value.old = { ...c }
}
function updateColumn(c) {
edit.value = false
let index = columns.value.findIndex((column) => column.key === c.key)
columns.value[index].label = c.label
columns.value[index].width = c.width
if (columns.value[index].old) {
delete columns.value[index].old
}
apply()
}
function cancelUpdate() {
edit.value = false
column.value.label = column.value.old.label
column.value.width = column.value.old.width
delete column.value.old
}
function reset(close) {
apply(true, false, true)
close()
}
function resetToDefault(close) {
apply(true, true)
close()
}
function apply(reload = false, isDefault = false, reset = false) {
is_default.value = isDefault
columnsUpdated.value = true
let obj = {
columns: reset ? oldValues.value.columns : columns.value,
rows: reset ? oldValues.value.rows : rows.value,
isDefault: reset ? oldValues.value.isDefault : isDefault,
reload,
reset,
}
emit('update', obj)
if (reload) {
// will have think of a better way to do this
setTimeout(() => {
is_default.value = reset ? oldValues.value.isDefault : isDefault
columnsUpdated.value = !reset
}, 100)
}
}
watchOnce(
() => list.value.data,
(val) => {
if (!val) return
oldValues.value.columns = JSON.parse(JSON.stringify(val.columns))
oldValues.value.rows = JSON.parse(JSON.stringify(val.rows))
oldValues.value.isDefault = val.is_default
},
)
</script>