snapshot of https://github.com/frappe/crm.git (develop, 2026-09-18; blobs > 131072B omitted)
Some checks failed
Frontend / Unit Tests & Coverage (push) Has been skipped
Regenerate POT file (translatable strings) / Regenerate POT file (develop) (push) Failing after 1m13s
Regenerate POT file (translatable strings) / Regenerate POT file (main-hotfix) (push) Failing after 6s
Server / Tests (frappe ${{ matrix.frappe-branch }}) (push) Has been skipped
Server / Resolve frappe matrix (push) Failing after 1s
Frontend / Lint (push) Failing after 1s
Frontend / Production Build (push) Failing after 1s
Server / Coverage Wrap Up (push) Has been skipped
Frontend / Coverage Wrap Up (push) Has been skipped
Some checks failed
Frontend / Unit Tests & Coverage (push) Has been skipped
Regenerate POT file (translatable strings) / Regenerate POT file (develop) (push) Failing after 1m13s
Regenerate POT file (translatable strings) / Regenerate POT file (main-hotfix) (push) Failing after 6s
Server / Tests (frappe ${{ matrix.frappe-branch }}) (push) Has been skipped
Server / Resolve frappe matrix (push) Failing after 1s
Frontend / Lint (push) Failing after 1s
Frontend / Production Build (push) Failing after 1s
Server / Coverage Wrap Up (push) Has been skipped
Frontend / Coverage Wrap Up (push) Has been skipped
This commit is contained in:
209
frontend/src/components/Controls/GridFieldsEditorModal.vue
Normal file
209
frontend/src/components/Controls/GridFieldsEditorModal.vue
Normal file
@@ -0,0 +1,209 @@
|
||||
<template>
|
||||
<Dialog v-model:open="show">
|
||||
<template #title>
|
||||
<h3
|
||||
class="flex items-center gap-2 text-3xl-semibold leading-6 text-ink-gray-9"
|
||||
>
|
||||
<div>{{ __('Edit Grid Fields Layout') }}</div>
|
||||
<Badge
|
||||
v-if="dirty"
|
||||
:label="__('Not Saved')"
|
||||
variant="subtle"
|
||||
theme="orange"
|
||||
/>
|
||||
</h3>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="mt-4">
|
||||
<div class="text-base text-ink-gray-8 mb-2">
|
||||
{{ __('Fields Order') }}
|
||||
</div>
|
||||
<Draggable
|
||||
v-if="oldFields?.length"
|
||||
:list="fields"
|
||||
group="fields"
|
||||
item-key="fieldname"
|
||||
class="flex flex-col gap-1"
|
||||
>
|
||||
<template #item="{ element: field }">
|
||||
<div
|
||||
class="px-1 py-0.5 bg-surface-gray-2 border border-outline-elevation-2 rounded text-base text-ink-gray-8 flex items-center justify-between gap-2"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<DragVerticalIcon class="h-3.5 cursor-grab" />
|
||||
<div>{{ field.label }}</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<TextInput
|
||||
v-model="field.columns"
|
||||
variant="outline"
|
||||
type="number"
|
||||
class="w-20"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
icon="lucide-x"
|
||||
@click="removeField(field)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Draggable>
|
||||
<Combobox
|
||||
v-if="dropdownFields?.length"
|
||||
:model-value="null"
|
||||
:options="dropdownFields"
|
||||
@update:selected-option="(e) => addField(e)"
|
||||
>
|
||||
<template #trigger="{ open, setOpen }">
|
||||
<Button
|
||||
class="w-full mt-2"
|
||||
:label="__('Add Field')"
|
||||
iconLeft="plus"
|
||||
@click="setOpen(!open)"
|
||||
/>
|
||||
</template>
|
||||
<template #item-label="{ item }">
|
||||
<div class="flex flex-col gap-1 text-ink-gray-9">
|
||||
<div>{{ item.label }}</div>
|
||||
<div class="text-ink-gray-4 text-sm">
|
||||
{{ `${item.fieldname} - ${item.fieldtype}` }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Combobox>
|
||||
<ErrorMessage v-if="error" class="mt-3" :message="error" />
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<div class="flex items-center gap-2 justify-end">
|
||||
<Button
|
||||
v-if="dirty"
|
||||
class="w-full"
|
||||
:label="__('Reset')"
|
||||
@click="reset"
|
||||
/>
|
||||
<Button
|
||||
class="w-full"
|
||||
:label="__('Save')"
|
||||
variant="solid"
|
||||
:loading="loading"
|
||||
:disabled="!dirty"
|
||||
@click="update"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup>
|
||||
import DragVerticalIcon from '@/components/Icons/DragVerticalIcon.vue'
|
||||
import { getMeta } from '@/stores/meta'
|
||||
import Draggable from 'vuedraggable'
|
||||
import { Combobox, Dialog, ErrorMessage } from 'frappe-ui'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
doctype: { type: String, default: '' },
|
||||
parentDoctype: { type: String, default: '' },
|
||||
})
|
||||
|
||||
const restrictedFieldTypes = [
|
||||
'Section Break',
|
||||
'Column Break',
|
||||
'Tab Break',
|
||||
'Table',
|
||||
'Table MultiSelect',
|
||||
'Image',
|
||||
]
|
||||
|
||||
const { getFields, getGridViewSettings, saveUserSettings } = getMeta(
|
||||
props.doctype,
|
||||
)
|
||||
|
||||
const show = defineModel({ type: Boolean })
|
||||
|
||||
const loading = ref(false)
|
||||
const error = ref(null)
|
||||
|
||||
const dirty = computed(() => {
|
||||
return JSON.stringify(fields.value) !== JSON.stringify(oldFields.value)
|
||||
})
|
||||
|
||||
const oldFields = computed(() => {
|
||||
let _fields =
|
||||
getFields({ restrictNoValueFields: false, restrictedFieldTypes }) || []
|
||||
if (!_fields.length) return []
|
||||
|
||||
let gridViewSettings = getGridViewSettings(props.parentDoctype)
|
||||
|
||||
if (gridViewSettings.length) {
|
||||
return gridViewSettings.map((field) => {
|
||||
let f = _fields.find((f) => f.fieldname === field.fieldname)
|
||||
if (f) {
|
||||
f.columns = field.columns
|
||||
return fieldObj(f)
|
||||
}
|
||||
})
|
||||
}
|
||||
return _fields?.filter((field) => field.in_list_view).map((f) => fieldObj(f))
|
||||
})
|
||||
|
||||
const fields = ref(JSON.parse(JSON.stringify(oldFields.value || [])))
|
||||
|
||||
const dropdownFields = computed(() => {
|
||||
const _fields =
|
||||
getFields({ restrictNoValueFields: false, restrictedFieldTypes }) || []
|
||||
if (!_fields.length) return []
|
||||
|
||||
return _fields
|
||||
.filter(
|
||||
(field) => !fields.value.find((f) => f.fieldname === field.fieldname),
|
||||
)
|
||||
.map((field) => ({ ...field, value: field.fieldname }))
|
||||
})
|
||||
|
||||
function reset() {
|
||||
fields.value = JSON.parse(JSON.stringify(oldFields.value || []))
|
||||
}
|
||||
|
||||
function addField(field) {
|
||||
fields.value.push(fieldObj(field))
|
||||
}
|
||||
|
||||
function removeField(field) {
|
||||
const index = fields.value.findIndex((f) => f.fieldname === field.fieldname)
|
||||
fields.value.splice(index, 1)
|
||||
}
|
||||
|
||||
function update() {
|
||||
loading.value = true
|
||||
|
||||
let updateFields = fields.value.map((field) => {
|
||||
return {
|
||||
fieldname: field.fieldname,
|
||||
columns: field.columns,
|
||||
}
|
||||
})
|
||||
|
||||
if (updateFields.length === 0) {
|
||||
error.value = __('At least one field is required')
|
||||
return
|
||||
}
|
||||
|
||||
saveUserSettings(props.parentDoctype, 'GridView', updateFields, () => {
|
||||
loading.value = false
|
||||
show.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function fieldObj(field) {
|
||||
return {
|
||||
label: field.label,
|
||||
fieldname: field.fieldname,
|
||||
fieldtype: field.fieldtype,
|
||||
options: field.options,
|
||||
in_list_view: field.in_list_view,
|
||||
columns: field.columns || 2,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user