2024-12-29 16:26:03 +05:30

344 lines
10 KiB
Vue

<template>
<div class="flex flex-col text-base">
<div v-if="label" class="mb-1.5 text-sm text-ink-gray-5">
{{ __(label) }}
</div>
<div class="rounded border border-outline-gray-modals">
<!-- Header -->
<div
class="grid-header flex items-center rounded-t-[7px] bg-surface-gray-2 text-ink-gray-5 truncate"
>
<div
class="inline-flex items-center justify-center border-r border-outline-gray-2 h-8 p-2 w-12"
>
<Checkbox
class="cursor-pointer duration-300"
:modelValue="allRowsSelected"
@click.stop="toggleSelectAllRows($event.target.checked)"
/>
</div>
<div
class="inline-flex items-center justify-center border-r border-outline-gray-2 py-2 px-1 w-12"
>
{{ __('No') }}
</div>
<div
class="grid w-full truncate"
:style="{ gridTemplateColumns: gridTemplateColumns }"
>
<div
v-if="fields?.length"
class="border-r border-outline-gray-2 p-2 truncate"
v-for="field in fields"
:key="field.name"
:title="field.label"
>
{{ __(field.label) }}
</div>
</div>
<div class="w-12" />
</div>
<!-- Rows -->
<template v-if="rows.length">
<Draggable class="w-full" v-model="rows" group="rows" item-key="name">
<template #item="{ element: row, index }">
<div
class="grid-row flex cursor-pointer items-center border-b border-outline-gray-modals bg-surface-modals last:rounded-b last:border-b-0"
>
<div
class="inline-flex h-9.5 items-center justify-center border-r border-outline-gray-modals p-2 w-12"
>
<Checkbox
class="cursor-pointer duration-300"
:modelValue="selectedRows.has(row.name)"
@click.stop="toggleSelectRow(row)"
/>
</div>
<div
class="flex h-9.5 items-center justify-center border-r border-outline-gray-modals py-2 px-1 text-sm text-ink-gray-8 w-12"
>
{{ index + 1 }}
</div>
<div
class="grid w-full h-9.5"
:style="{ gridTemplateColumns: gridTemplateColumns }"
>
<div
v-if="fields?.length"
class="border-r border-outline-gray-modals h-full"
v-for="field in fields"
:key="field.name"
>
<Link
v-if="field.type === 'Link'"
class="text-sm text-ink-gray-8"
v-model="row[field.name]"
:doctype="field.options"
:filters="field.filters"
/>
<div
v-else-if="field.type === 'Check'"
class="flex h-full justify-center items-center"
>
<Checkbox
class="cursor-pointer duration-300"
v-model="row[field.name]"
/>
</div>
<DatePicker
v-else-if="field.type === 'Date'"
v-model="row[field.name]"
icon-left=""
variant="outline"
:formatter="(date) => getFormat(date, '', true)"
input-class="border-none text-sm text-ink-gray-8"
/>
<DateTimePicker
v-else-if="field.type === 'Datetime'"
v-model="row[field.name]"
icon-left=""
variant="outline"
:formatter="(date) => getFormat(date, '', true, true)"
input-class="border-none text-sm text-ink-gray-8"
/>
<FormControl
v-else-if="
['Small Text', 'Text', 'Long Text', 'Code'].includes(
field.type,
)
"
rows="1"
type="textarea"
variant="outline"
v-model="row[field.name]"
/>
<FormControl
v-else-if="['Int'].includes(field.type)"
type="number"
variant="outline"
v-model="row[field.name]"
/>
<FormControl
v-else-if="field.type === 'Select'"
class="text-sm text-ink-gray-8"
type="select"
variant="outline"
v-model="row[field.name]"
:options="field.options"
/>
<FormControl
v-else
class="text-sm text-ink-gray-8"
type="text"
variant="outline"
v-model="row[field.name]"
:options="field.options"
/>
</div>
</div>
<div class="edit-row w-12">
<Button
class="flex w-full items-center justify-center rounded border-0"
variant="outline"
@click="showRowList[index] = true"
>
<EditIcon class="h-4 w-4 text-ink-gray-7" />
</Button>
</div>
<GridRowModal
v-if="showRowList[index]"
v-model="showRowList[index]"
v-model:showGridRowFieldsModal="showGridRowFieldsModal"
:index="index"
:data="row"
:doctype="doctype"
/>
</div>
</template>
</Draggable>
</template>
<div
v-else
class="flex flex-col items-center rounded p-5 text-sm text-ink-gray-5"
>
{{ __('No Data') }}
</div>
</div>
<div v-if="fields?.length" class="mt-2 flex flex-row gap-2">
<Button
v-if="showDeleteBtn"
:label="__('Delete')"
variant="solid"
theme="red"
@click="deleteRows"
/>
<Button :label="__('Add Row')" @click="addRow" />
</div>
</div>
<GridRowFieldsModal
v-if="showGridRowFieldsModal"
v-model="showGridRowFieldsModal"
:doctype="doctype"
/>
</template>
<script setup lang="ts">
import GridRowFieldsModal from '@/components/Controls/GridRowFieldsModal.vue'
import GridRowModal from '@/components/Controls/GridRowModal.vue'
import EditIcon from '@/components/Icons/EditIcon.vue'
import Link from '@/components/Controls/Link.vue'
import { GridRow } from '@/types/controls'
import { getRandom, getFormat } from '@/utils'
import { getMeta } from '@/stores/meta'
import {
FeatherIcon,
FormControl,
Checkbox,
DateTimePicker,
DatePicker,
} from 'frappe-ui'
import Draggable from 'vuedraggable'
import { ref, reactive, computed, PropType } from 'vue'
const props = defineProps<{
label?: string
doctype: string
}>()
const { getGridSettings, getFields } = getMeta(props.doctype)
const rows = defineModel({
type: Array as PropType<GridRow[]>,
default: () => [],
})
const showRowList = ref(new Array(rows.value.length).fill(false))
const selectedRows = reactive(new Set<string>())
const showGridRowFieldsModal = ref(false)
const fields = computed(() => {
let gridSettings = getGridSettings()
let gridFields = getFields()
if (gridSettings.length) {
let d = gridSettings.map((gs) =>
getFieldObj(gridFields.find((f) => f.fieldname === gs.fieldname)),
)
return d
}
return gridFields?.map((f) => getFieldObj(f)) || []
})
function getFieldObj(field) {
return {
label: field.label,
name: field.fieldname,
type: field.fieldtype,
options: field.options,
in_list_view: field.in_list_view,
}
}
const gridTemplateColumns = computed(() => {
if (!fields.value?.length) return '1fr'
// for the checkbox & sr no. columns
let gridSettings = getGridSettings()
if (gridSettings.length) {
return gridSettings.map((gs) => `minmax(0, ${gs.columns || 2}fr)`).join(' ')
}
return fields.value.map((col) => `minmax(0, ${col.width || 2}fr)`).join(' ')
})
const allRowsSelected = computed(() => {
if (!rows.value.length) return false
return rows.value.length === selectedRows.size
})
const showDeleteBtn = computed(() => selectedRows.size > 0)
const toggleSelectAllRows = (iSelected: boolean) => {
if (iSelected) {
rows.value.forEach((row: GridRow) => selectedRows.add(row.name))
} else {
selectedRows.clear()
}
}
const toggleSelectRow = (row: GridRow) => {
if (selectedRows.has(row.name)) {
selectedRows.delete(row.name)
} else {
selectedRows.add(row.name)
}
}
const addRow = () => {
const newRow = {} as GridRow
fields.value?.forEach((field) => {
if (field.type === 'Check') newRow[field.name] = false
else newRow[field.name] = ''
})
newRow.name = getRandom(10)
showRowList.value.push(false)
newRow['__islocal'] = true
rows.value.push(newRow)
}
const deleteRows = () => {
rows.value = rows.value.filter((row) => !selectedRows.has(row.name))
showRowList.value.pop()
selectedRows.clear()
}
</script>
<style scoped>
/* For Input fields */
:deep(.grid-row input:not([type='checkbox'])),
:deep(.grid-row textarea) {
border: none;
border-radius: 0;
height: 38px;
}
:deep(.grid-row input:focus),
:deep(.grid-row input:hover),
:deep(.grid-row textarea:focus),
:deep(.grid-row textarea:hover) {
box-shadow: none;
}
:deep(.grid-row input:focus-within) :deep(.grid-row textarea:focus-within) {
border: 1px solid var(--outline-gray-2);
}
/* For select field */
:deep(.grid-row select) {
border: none;
border-radius: 0;
height: 38px;
}
/* For Autocomplete */
:deep(.grid-row button) {
border: none;
border-radius: 0;
background-color: var(--surface-white);
height: 38px;
}
:deep(.grid-row .edit-row button) {
border-bottom-right-radius: 7px;
}
:deep(.grid-row button:focus) :deep(.grid-row button:hover) {
box-shadow: none;
background-color: var(--surface-white);
}
:deep(.grid-row button:focus-within) {
border: 1px solid var(--outline-gray-2);
}
</style>