feat: refs #6825 VnComponent mix component and attrs Form to create new row

This commit is contained in:
Alex Moreno 2024-05-17 13:00:57 +02:00
parent 598d116848
commit d00801b066
11 changed files with 233 additions and 88 deletions

View File

@ -2,17 +2,12 @@
<span v-for="toComponent of componentArray" :key="toComponent.field"> <span v-for="toComponent of componentArray" :key="toComponent.field">
<component <component
v-if="toComponent?.component" v-if="toComponent?.component"
:is=" :is="mix(toComponent).component"
(components && components[toComponent.component]) ?? toComponent.component v-bind="mix(toComponent).attrs"
" v-on="mix(toComponent).event"
v-bind="
typeof toComponent.attrs == 'function'
? toComponent.attrs(value)
: toComponent.attrs
"
@click="toComponent.event && toComponent.event(value)"
v-model="model" v-model="model"
/> />
<!-- @click="toComponent.event && toComponent.event(value)" -->
</span> </span>
</template> </template>
<script setup> <script setup>
@ -40,4 +35,24 @@ const componentArray = computed(() => {
if (typeof $props.prop === 'object') return [$props.prop]; if (typeof $props.prop === 'object') return [$props.prop];
return $props.prop; return $props.prop;
}); });
function mix(toComponent) {
const { component, attrs, event } = toComponent;
const customComponent = $props.components[component];
return {
component: customComponent?.component ?? component,
attrs: {
...toValueAttrs(attrs),
...toValueAttrs(customComponent?.attrs),
...toComponent,
...toValueAttrs(customComponent?.forceAttrs),
},
event: event ?? customComponent?.event,
};
}
function toValueAttrs(attrs) {
if (!attrs) return;
return typeof attrs == 'function' ? attrs($props.value) : attrs;
}
</script> </script>

View File

@ -2,7 +2,12 @@
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
const emit = defineEmits(['update:modelValue', 'update:options', 'keyup.enter']); const emit = defineEmits([
'update:modelValue',
'update:options',
'keyup.enter',
'remove',
]);
const $props = defineProps({ const $props = defineProps({
modelValue: { modelValue: {
@ -43,6 +48,7 @@ const onEnterPress = () => {
const handleValue = (val = null) => { const handleValue = (val = null) => {
value.value = val; value.value = val;
emit('remove');
}; };
const focus = () => { const focus = () => {

View File

@ -22,6 +22,10 @@ const $props = defineProps({
type: String, type: String,
default: 'id', default: 'id',
}, },
optionFilter: {
type: String,
default: null,
},
url: { url: {
type: String, type: String,
default: '', default: '',
@ -59,7 +63,7 @@ const $props = defineProps({
const { t } = useI18n(); const { t } = useI18n();
const requiredFieldRule = (val) => val ?? t('globals.fieldRequired'); const requiredFieldRule = (val) => val ?? t('globals.fieldRequired');
const { optionLabel, optionValue, options, modelValue } = toRefs($props); const { optionLabel, optionValue, optionFilter, options, modelValue } = toRefs($props);
const myOptions = ref([]); const myOptions = ref([]);
const myOptionsOriginal = ref([]); const myOptionsOriginal = ref([]);
const vnSelectRef = ref(); const vnSelectRef = ref();
@ -109,7 +113,7 @@ async function fetchFilter(val) {
const { fields, sortBy, limit } = $props; const { fields, sortBy, limit } = $props;
let key = optionLabel.value; let key = optionLabel.value;
if (new RegExp(/\d/g).test(val)) key = optionValue.value; if (new RegExp(/\d/g).test(val)) key = optionFilter.value ?? optionValue.value;
const where = { ...{ [key]: { like: `%${val}%` } }, ...$props.where }; const where = { ...{ [key]: { like: `%${val}%` } }, ...$props.where };
const fetchOptions = { where, order: sortBy, limit }; const fetchOptions = { where, order: sortBy, limit };

View File

@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import FormModelPopup from 'components/FormModelPopup.vue';
import VnPaginate from 'components/ui/VnPaginate.vue'; import VnPaginate from 'components/ui/VnPaginate.vue';
import VnFilterPanel from 'components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'components/ui/VnFilterPanel.vue';
import VnLv from 'components/ui/VnLv.vue'; import VnLv from 'components/ui/VnLv.vue';
@ -189,7 +190,7 @@ const rowClickFunction = computed(() => {
:column="col" :column="col"
:show-title="true" :show-title="true"
:data-key="$attrs['data-key']" :data-key="$attrs['data-key']"
v-model="params[col.field]" v-model="params[col.columnFilter?.field ?? col.field]"
/> />
</QTh> </QTh>
</template> </template>
@ -197,7 +198,12 @@ const rowClickFunction = computed(() => {
<template #body-cell="{ col, row }"> <template #body-cell="{ col, row }">
<!-- Columns --> <!-- Columns -->
<QTd auto-width :class="`text-${col.align ?? 'left'}`"> <QTd auto-width :class="`text-${col.align ?? 'left'}`">
<VnTableColumn :column="col" :row="row" /> <VnTableColumn
:column="col"
:row="row"
:is-editable="false"
v-model="row[col.field]"
/>
</QTd> </QTd>
</template> </template>
<template #item="{ row, colsMap }"> <template #item="{ row, colsMap }">
@ -308,12 +314,24 @@ const rowClickFunction = computed(() => {
</QTooltip> --> </QTooltip> -->
</QPageSticky> </QPageSticky>
<QDialog v-model="showForm" transition-show="scale" transition-hide="scale"> <QDialog v-model="showForm" transition-show="scale" transition-hide="scale">
<VnTableCreate <FormModelPopup
:columns="cardTemplate.create" v-bind="{ ...$attrs, ...create }"
:model="$attrs['data-key'] + 'Create'" :model="$attrs['data-key'] + 'Create'"
:create="create" >
<template #form-inputs="{ data }">
<div class="grid-create">
<VnTableColumn
v-for="column of cardTemplate.create"
:key="column.field"
:column="column"
:row="{}"
default="input"
v-model="data[column.field]"
/> />
<!-- v-bind="$attrs.create" --> <slot name="more-create-dialog" :data="data" />
</div>
</template>
</FormModelPopup>
</QDialog> </QDialog>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -381,6 +399,14 @@ const rowClickFunction = computed(() => {
margin: 0 auto; margin: 0 auto;
} }
.grid-create {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, max-content));
max-width: 100%;
grid-gap: 20px;
margin: 0 auto;
}
.flex-one { .flex-one {
display: flex; display: flex;
flex-flow: row wrap; flex-flow: row wrap;

View File

@ -4,8 +4,8 @@
:prop="col.before" :prop="col.before"
:components="components" :components="components"
:value="value" :value="value"
v-model="model"
/> />
{{ col?.component }}
<VnComponent <VnComponent
v-if="col.component" v-if="col.component"
:prop="col" :prop="col"
@ -13,13 +13,13 @@
:value="value" :value="value"
v-model="model" v-model="model"
/> />
<!-- ?? [defaultComponent] -->
<span v-else>{{ dashIfEmpty(row[col.field]) }}</span> <span v-else>{{ dashIfEmpty(row[col.field]) }}</span>
<VnComponent <VnComponent
v-if="col.after" v-if="col.after"
:prop="col.after" :prop="col.after"
:components="components" :components="components"
:value="value" :value="value"
v-model="model"
/> />
</template> </template>
<script setup> <script setup>
@ -45,29 +45,85 @@ const $props = defineProps({
}, },
default: { default: {
type: [Object, String], type: [Object, String],
required: false, default: null,
},
componentProp: {
type: String,
default: null,
},
isEditable: {
type: Boolean,
default: true,
},
components: {
type: Object,
default: null, default: null,
}, },
}); });
const components = { const defaultComponents = {
input: markRaw(VnInput), input: {
number: markRaw(VnInput), component: markRaw(VnInput),
date: markRaw(VnInputDate), attrs: {
checkbox: markRaw(QCheckbox), disable: !$props.isEditable,
select: markRaw(VnSelect), },
icon: markRaw(QIcon), },
number: {
component: markRaw(VnInput),
attrs: {
disable: !$props.isEditable,
},
},
date: {
component: markRaw(VnInputDate),
attrs: {
disable: !$props.isEditable,
},
},
checkbox: {
component: markRaw(QCheckbox),
attrs: (prop) => {
return {
disable: !$props.isEditable,
// 'true-value': 1,
// 'false-value': 0,
// 'model-value': Boolean(prop),
};
},
forceAttrs: {
label: null,
},
},
select: {
component: markRaw(VnSelect),
attrs: {
disable: !$props.isEditable,
},
},
icon: {
component: markRaw(QIcon),
},
}; };
const value = computed(() => const value = computed(() => {
$props.column.format ? $props.column.format($props.row) : $props.row return $props.column.format ? $props.column.format($props.row) : $props.row;
); });
const col = computed(() => { const col = computed(() => {
const newColumn = { ...$props.column }; let newColumn = { ...$props.column };
console.log('newColumn: ', newColumn); const specific = newColumn[$props.componentProp];
console.log('newColumn.componen: ', newColumn.component); if (specific) {
newColumn = {
...newColumn,
...specific,
...specific.attrs,
...specific.forceAttrs,
};
}
if ($props.default && !newColumn.component) newColumn.component = $props.default; if ($props.default && !newColumn.component) newColumn.component = $props.default;
return newColumn; return newColumn;
}); });
const components = computed(() => $props.components ?? defaultComponents);
</script> </script>

View File

@ -19,12 +19,7 @@ const $props = defineProps({
<VnTableColumn <VnTableColumn
v-for="column of $props.columns" v-for="column of $props.columns"
:key="column.field" :key="column.field"
:column="{ :column="column"
...(column ?? column.create),
...{
attrs: column,
},
}"
:row="{}" :row="{}"
default="input" default="input"
v-model="data[column.field]" v-model="data[column.field]"

View File

@ -11,24 +11,15 @@
class="row no-wrap" class="row no-wrap"
> >
<hr class="q-ma-none divisor-line" v-if="showTitle" /> <hr class="q-ma-none divisor-line" v-if="showTitle" />
<component <span class="full-width">
v-if="columnFilter?.component" <VnTableColumn
:is="isBasicComponent?.component ?? columnFilter.component" :column="$props.column"
:row="{}"
default="input"
v-model="model" v-model="model"
v-bind="{ ...isBasicComponent?.attrs, ...columnFilter.attrs }" :components="components"
v-on="isBasicComponent?.event ?? columnFilter.event" :component-prop="`columnFilter`"
dense
/> />
<span v-else class="full-width">
<QInput
v-model="model"
dense
class="q-px-sm q-pb-xs q-pt-none"
:label="showTitle ? '' : column.label"
@keyup.enter="addFilter"
>
<template #append> </template>
</QInput>
</span> </span>
</div> </div>
<div v-else-if="showTitle" style="height: 45px"><!-- fixme! --></div> <div v-else-if="showTitle" style="height: 45px"><!-- fixme! --></div>
@ -42,6 +33,7 @@ import { useArrayData } from 'composables/useArrayData';
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import VnInput from 'components/common/VnInput.vue'; import VnInput from 'components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import VnTableColumn from 'components/common/VnTableColumn.vue';
const $props = defineProps({ const $props = defineProps({
column: { column: {
@ -60,16 +52,23 @@ const $props = defineProps({
const model = defineModel(); const model = defineModel();
const arrayData = useArrayData($props.dataKey); const arrayData = useArrayData($props.dataKey);
const columnFilter = computed(() => $props.column?.columnFilter); const columnFilter = computed(() => $props.column?.columnFilter);
const isBasicComponent = computed(
() => components[$props.column?.columnFilter?.component]
);
const updateEvent = { 'update:modelValue': addFilter }; const updateEvent = { 'update:modelValue': addFilter };
const enterEvent = { 'keyup.enter': () => addFilter(model.value) }; const enterEvent = {
'keyup.enter': () => addFilter(model.value),
remove: () => addFilter(null),
};
const components = { const components = {
input: { input: {
component: markRaw(VnInput), component: markRaw(VnInput),
event: enterEvent, event: enterEvent,
attrs: {
class: 'q-px-sm q-pb-xs q-pt-none',
dense: true,
},
forceAttrs: {
label: $props.showTitle ? '' : $props.column.label,
},
}, },
number: { number: {
component: markRaw(VnInput), component: markRaw(VnInput),
@ -82,12 +81,18 @@ const components = {
checkbox: { checkbox: {
component: markRaw(QCheckbox), component: markRaw(QCheckbox),
event: updateEvent, event: updateEvent,
forceAttrs: {
label: $props.showTitle ? '' : $props.column.label,
},
}, },
select: { select: {
component: markRaw(VnSelect), component: markRaw(VnSelect),
event: updateEvent, event: updateEvent,
attrs: { attrs: {
class: 'full-width q-px-sm', class: 'q-px-md q-pb-xs q-pt-none',
dense: true,
},
forceAttrs: {
label: $props.showTitle ? '' : $props.column.label, label: $props.showTitle ? '' : $props.column.label,
}, },
}, },
@ -109,7 +114,6 @@ const components = {
async function addFilter(value) { async function addFilter(value) {
if (value && typeof value === 'object') value = model.value; if (value && typeof value === 'object') value = model.value;
console.log('VALUE: ', value);
value = value == '' ? null : value; value = value == '' ? null : value;
let field = columnFilter.value?.field ?? $props.column.field; let field = columnFilter.value?.field ?? $props.column.field;
const toFilter = { [field]: value }; const toFilter = { [field]: value };

View File

@ -58,6 +58,7 @@ onMounted(() => {
if (params.value) userParams.value = JSON.parse(JSON.stringify(params.value)); if (params.value) userParams.value = JSON.parse(JSON.stringify(params.value));
if (Object.keys(store.userParams).length > 0) { if (Object.keys(store.userParams).length > 0) {
userParams.value = JSON.parse(JSON.stringify(store.userParams)); userParams.value = JSON.parse(JSON.stringify(store.userParams));
params.value = { ...params.value, ...userParams.value };
} }
emit('init', { params: userParams.value }); emit('init', { params: userParams.value });
}); });
@ -147,7 +148,6 @@ async function clearFilters() {
} }
const tagsList = computed(() => { const tagsList = computed(() => {
console.log('userParams.value: ', userParams.value);
return Object.entries(userParams.value) return Object.entries(userParams.value)
.filter(([key, value]) => value && !(props.hiddenTags || []).includes(key)) .filter(([key, value]) => value && !(props.hiddenTags || []).includes(key))
.map(([key, value]) => ({ .map(([key, value]) => ({
@ -166,8 +166,6 @@ const customTags = computed(() =>
async function remove(key) { async function remove(key) {
userParams.value[key] = null; userParams.value[key] = null;
params.value[key] = undefined; params.value[key] = undefined;
console.log('key: ', key);
console.log('params: ', params.value);
await arrayData.applyFilter({ params: userParams.value }); await arrayData.applyFilter({ params: userParams.value });
emit('remove', key); emit('remove', key);
} }

View File

@ -270,7 +270,8 @@ customer:
extendedList: extendedList:
tableVisibleColumns: tableVisibleColumns:
id: Identifier id: Identifier
name: Name name: Comercial name
socialName: Business name
fi: Tax number fi: Tax number
salesPersonFk: Salesperson salesPersonFk: Salesperson
credit: Credit credit: Credit

View File

@ -268,7 +268,8 @@ customer:
extendedList: extendedList:
tableVisibleColumns: tableVisibleColumns:
id: Identificador id: Identificador
name: Nombre name: Nombre Comercial
socialName: Razón social
fi: NIF / CIF fi: NIF / CIF
salesPersonFk: Comercial salesPersonFk: Comercial
credit: Crédito credit: Crédito

View File

@ -3,6 +3,7 @@ import { ref, computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import VnTable from 'components/common/VnTable.vue'; import VnTable from 'components/common/VnTable.vue';
import VnLocation from 'src/components/common/VnLocation.vue';
import CustomerSummary from '../Card/CustomerSummary.vue'; import CustomerSummary from '../Card/CustomerSummary.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
@ -30,6 +31,7 @@ const selectedCustomerId = ref(0);
const selectedSalesPersonId = ref(0); const selectedSalesPersonId = ref(0);
const allColumnNames = ref([]); const allColumnNames = ref([]);
const visibleColumns = ref([]); const visibleColumns = ref([]);
const postcodesOptions = ref([]);
const tableColumnComponents = { const tableColumnComponents = {
customerStatus: { customerStatus: {
@ -278,8 +280,8 @@ const columns = computed(() => [
condition: () => true, condition: () => true,
}, },
columnFilter: { columnFilter: {
field: 'search',
component: 'select', component: 'select',
field: 'search',
attrs: { attrs: {
url: 'Clients', url: 'Clients',
fields: ['id', 'name'], fields: ['id', 'name'],
@ -294,33 +296,32 @@ const columns = computed(() => [
isTitle: true, isTitle: true,
create: true, create: true,
}, },
{
align: 'left',
field: 'socialName',
label: t('customer.extendedList.tableVisibleColumns.socialName'),
isTitle: true,
create: true,
},
{ {
align: 'left', align: 'left',
field: 'fi', field: 'fi',
label: t('customer.extendedList.tableVisibleColumns.fi'), label: t('customer.extendedList.tableVisibleColumns.fi'),
name: 'fi', name: 'fi',
create: true,
}, },
{ {
align: 'left', align: 'left',
field: 'salesPersonFk', field: 'salesPersonFk',
label: t('customer.extendedList.tableVisibleColumns.salesPersonFk'), label: t('customer.extendedList.tableVisibleColumns.salesPersonFk'),
name: 'salesPersonFk', name: 'salesPersonFk',
columnFilter: {
component: 'select', component: 'select',
attrs: { attrs: {
url: 'Workers/activeWithInheritedRole', url: 'Workers/activeWithInheritedRole',
fields: ['id', 'name'], fields: ['id', 'name'],
where: { role: 'salesPerson' }, where: { role: 'salesPerson' },
}, },
}, create: true,
create: {
component: 'select',
attrs: {
url: 'Workers/activeWithInheritedRole',
fields: ['id', 'name'],
where: { role: 'salesPerson' },
},
},
}, },
{ {
align: 'left', align: 'left',
@ -359,6 +360,7 @@ const columns = computed(() => [
field: 'street', field: 'street',
label: t('customer.extendedList.tableVisibleColumns.street'), label: t('customer.extendedList.tableVisibleColumns.street'),
name: 'street', name: 'street',
create: true,
}, },
{ {
align: 'left', align: 'left',
@ -400,9 +402,16 @@ const columns = computed(() => [
}, },
{ {
align: 'left', align: 'left',
field: 'businessType', field: 'businessTypeFk',
label: t('customer.extendedList.tableVisibleColumns.businessTypeFk'), label: t('customer.extendedList.tableVisibleColumns.businessTypeFk'),
name: 'businessTypeFk', name: 'businessTypeFk',
create: true,
component: 'select',
attrs: {
url: 'BusinessTypes',
optionLabel: 'description',
optionValue: 'code',
},
}, },
{ {
align: 'left', align: 'left',
@ -450,6 +459,8 @@ const columns = computed(() => [
field: 'isEqualizated', field: 'isEqualizated',
label: t('customer.extendedList.tableVisibleColumns.isEqualizated'), label: t('customer.extendedList.tableVisibleColumns.isEqualizated'),
name: 'isEqualizated', name: 'isEqualizated',
component: 'checkbox',
created: true,
}, },
{ {
align: 'left', align: 'left',
@ -473,6 +484,7 @@ const columns = computed(() => [
field: 'hasToInvoiceByAddress', field: 'hasToInvoiceByAddress',
label: t('customer.extendedList.tableVisibleColumns.hasToInvoiceByAddress'), label: t('customer.extendedList.tableVisibleColumns.hasToInvoiceByAddress'),
name: 'hasToInvoiceByAddress', name: 'hasToInvoiceByAddress',
component: 'checkbox',
}, },
{ {
align: 'left', align: 'left',
@ -530,6 +542,14 @@ const navigateToTravelId = (id) => router.push({ path: `/customer/${id}` });
const selectCustomerId = (id) => (selectedCustomerId.value = id); const selectCustomerId = (id) => (selectedCustomerId.value = id);
const selectSalesPersonId = (id) => (selectedSalesPersonId.value = id); const selectSalesPersonId = (id) => (selectedSalesPersonId.value = id);
function handleLocation(data, location) {
const { town, code, provinceFk, countryFk } = location ?? {};
data.postcode = code;
data.city = town;
data.provinceFk = provinceFk;
data.countryFk = countryFk;
}
</script> </script>
<template> <template>
@ -569,10 +589,29 @@ const selectSalesPersonId = (id) => (selectedSalesPersonId.value = id);
}" }"
order="id DESC" order="id DESC"
:columns="columns" :columns="columns"
default-mode="card" default-mode="table"
redirect="customer"
auto-load auto-load
> >
<template #more-create-dialog="{ data }">
<QInput :label="t('Email')" clearable type="email" v-model="data.email">
<template #append>
<QIcon name="info" class="cursor-info">
<QTooltip max-width="400px">{{
t('customer.basicData.youCanSaveMultipleEmails')
}}</QTooltip>
</QIcon>
</template>
</QInput>
<QInput v-model="data.userName" :label="t('Web user')" />
<VnLocation
:roles-allowed-to-create="['deliveryAssistant']"
:options="postcodesOptions"
v-model="data.location"
@update:model-value="(location) => handleLocation(data, location)"
>
</VnLocation>
</template>
<!-- redirect="customer" -->
<!-- <!--
default-mode="table" default-mode="table"
<template #body-cell="{ col, value }"> <template #body-cell="{ col, value }">