refs #5673 feat(FormModel): basciFilter, feat: modul top toolbar
gitea/salix-front/pipeline/head This commit looks good Details

This commit is contained in:
Alex Moreno 2023-08-07 12:00:42 +02:00
parent 35ac3afe81
commit 0cdd090daf
8 changed files with 136 additions and 163 deletions

View File

@ -41,20 +41,10 @@ const $props = defineProps({
type: Object, type: Object,
default: null, default: null,
}, },
}); defaultActions: {
type: Boolean,
const emit = defineEmits(['onFetch']); default: true,
},
defineExpose({
save,
insert,
remove,
});
onMounted(async () => await fetch());
onUnmounted(() => {
state.unset($props.model);
}); });
const isLoading = ref(false); const isLoading = ref(false);
@ -62,12 +52,38 @@ const hasChanges = ref(false);
const formData = computed(() => state.get($props.model)); const formData = computed(() => state.get($props.model));
const originalData = ref(); const originalData = ref();
const formUrl = computed(() => $props.url); const formUrl = computed(() => $props.url);
const stActionsExist = ref(false);
const emit = defineEmits(['onFetch']);
defineExpose({
save,
insert,
remove,
onSubmit,
reset,
hasChanges,
});
onMounted(async () => {
await fetch();
stActionsExist.value = !!document.querySelector('#st-actions');
});
onUnmounted(() => {
state.unset($props.model);
});
async function fetch() { async function fetch() {
const { data } = await axios.get($props.url, { const { data } = await axios.get($props.url, {
params: { filter: $props.filter }, params: { filter: $props.filter },
}); });
if (Array.isArray(data)) {
let $index = 0;
data.map((d) => (d.$index = $index++));
}
state.set($props.model, data); state.set($props.model, data);
originalData.value = JSON.parse(JSON.stringify(data)); originalData.value = JSON.parse(JSON.stringify(data));
@ -84,7 +100,11 @@ async function save() {
}); });
} }
isLoading.value = true; isLoading.value = true;
try {
await axios.patch($props.urlUpdate || $props.url, formData.value); await axios.patch($props.urlUpdate || $props.url, formData.value);
} catch {
return (isLoading.value = false);
}
originalData.value = JSON.parse(JSON.stringify(formData.value)); originalData.value = JSON.parse(JSON.stringify(formData.value));
hasChanges.value = false; hasChanges.value = false;
@ -93,6 +113,7 @@ async function save() {
function reset() { function reset() {
state.set($props.model, originalData.value); state.set($props.model, originalData.value);
watch(formData.value, () => (hasChanges.value = true));
hasChanges.value = false; hasChanges.value = false;
} }
// eslint-disable-next-line vue/no-dupe-keys // eslint-disable-next-line vue/no-dupe-keys
@ -118,28 +139,25 @@ async function crudSave() {
}); });
} }
isLoading.value = true; isLoading.value = true;
const changes = getChanges(); await saveChanges();
await saveChanges(changes);
if (changes.creates?.length) await fetch();
} }
async function saveChanges(changes) { async function saveChanges() {
const changes = getChanges();
try { try {
await axios.post($props.urlUpdate || $props.url + '/crud', changes); await axios.post($props.urlUpdate || $props.url + '/crud', changes);
} catch (e) { } catch (e) {
quasar.notify({ return (isLoading.value = false);
type: 'negative',
message: 'Some fields are invalid',
});
} }
originalData.value = JSON.parse(JSON.stringify(formData.value)); originalData.value = JSON.parse(JSON.stringify(formData.value));
hasChanges.value = false; hasChanges.value = false;
isLoading.value = false; isLoading.value = false;
if (changes.creates?.length) await fetch();
} }
async function insert() { async function insert() {
const $index = formData.value.length + 1; const $index = formData.value[formData.value.length - 1].$index + 1;
formData.value.push(Object.assign({ $index }, $props.dataRequired)); formData.value.push(Object.assign({ $index }, $props.dataRequired));
hasChanges.value = true; hasChanges.value = true;
} }
@ -156,6 +174,7 @@ async function insert() {
// } // }
async function remove(data) { async function remove(data) {
console.log(data);
if (!data.length) if (!data.length)
return quasar.notify({ return quasar.notify({
type: 'warning', type: 'warning',
@ -253,25 +272,29 @@ function isEmpty(obj) {
</QBanner> </QBanner>
<QForm v-if="formData" @submit="onSubmit" @reset="reset" class="q-pa-md"> <QForm v-if="formData" @submit="onSubmit" @reset="reset" class="q-pa-md">
<slot name="form" :data="formData" :validate="validate" :filter="filter"></slot> <slot name="form" :data="formData" :validate="validate" :filter="filter"></slot>
<div class="q-mt-lg"> </QForm>
<slot name="actions"> <Teleport to="#st-actions" v-if="stActionsExist">
<div class="row q-gutter-x-sm">
<slot name="moreActions" />
<div v-if="$props.defaultActions">
<QBtn <QBtn
:label="t('globals.save')" :label="t('globals.save')"
type="submit"
color="primary" color="primary"
icon="save"
@click="onSubmit"
:disable="!hasChanges" :disable="!hasChanges"
/> />
<QBtn <QBtn
:label="t('globals.reset')" :label="t('globals.reset')"
type="reset"
class="q-ml-sm"
color="primary" color="primary"
class="q-ml-sm"
flat flat
@click="reset"
:disable="!hasChanges" :disable="!hasChanges"
/> />
</slot>
</div> </div>
</QForm> </div>
</Teleport>
<SkeletonForm v-if="!formData" /> <SkeletonForm v-if="!formData" />
<QInnerLoading <QInnerLoading
:showing="isLoading" :showing="isLoading"

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, watch, toRefs } from 'vue'; import { ref, toRefs } from 'vue';
const emit = defineEmits(['update:modelValue', 'update:options']); const emit = defineEmits(['update:modelValue', 'update:options']);
const $props = defineProps({ const $props = defineProps({
@ -18,43 +18,42 @@ const $props = defineProps({
}); });
const updateValue = (newValue) => emit('update:modelValue', newValue); const updateValue = (newValue) => emit('update:modelValue', newValue);
const { modelValue, options, optionLabel } = toRefs($props); const { modelValue, optionLabel, options } = toRefs($props);
const myOptions = ref(JSON.parse(JSON.stringify(options.value)));
const myOptionsOriginal = ref(JSON.parse(JSON.stringify(options.value)));
function filterFn(val, update) { const filter = (val, options) => {
update( const search = val.toLowerCase();
() => {
const needle = val.toLowerCase(); if (val === '') return options;
// $props.options.value = options.value.filter( return options.filter((row) => {
// (v) => v[$props.optionLabel.value].toLowerCase().indexOf(needle) > -1 const id = row.id;
// ); const name = row[$props.optionLabel].toLowerCase();
// updateOptions(
// $props.options.filter( const idMatches = id == search;
// (v) => v[$props.optionLabel].toLowerCase().indexOf(needle) > -1 const nameMatches = name.indexOf(search) > -1;
// )
// ); return idMatches || nameMatches;
const filteredOptions = options.value.filter( });
(v) => v[optionLabel.value].toLowerCase().indexOf(needle) > -1 };
);
emit('update:options', filteredOptions); const filterHandler = (val, update) => {
}, update(() => {
(ref) => { myOptions.value = filter(val, myOptionsOriginal.value);
ref.setOptionIndex(-1); });
ref.moveOptionSelection(1, true); };
}
);
}
</script> </script>
<template> <template>
<QSelect <QSelect
v-model="modelValue" v-model="modelValue"
@update:model-value="updateValue" @update:model-value="updateValue"
:options="options" :options="myOptions"
:option-label="optionLabel" :option-label="optionLabel"
v-bind="$attrs" v-bind="$attrs"
emit-value emit-value
map-options map-options
use-input use-input
@filter="(value, update) => filterFn(value, update)" @filter="filterHandler"
/> />
</template> </template>

View File

@ -64,7 +64,13 @@ onMounted(async () => {
</QScrollArea> </QScrollArea>
</QDrawer> </QDrawer>
<QPageContainer> <QPageContainer>
<div id="sub-toolbar"></div> <QToolbar id="sub-toolbar" class="bg-dark text-white justify-end">
<div id="st-data">
{{ route.meta?.title && t(`claim.pageTitles.${route.meta.title}`) }}
</div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<QPage class="q-pa-md"> <QPage class="q-pa-md">
<RouterView></RouterView> <RouterView></RouterView>
</QPage> </QPage>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
@ -126,16 +126,25 @@ const columns = computed(() => [
url="ClaimDevelopments" url="ClaimDevelopments"
:crud="true" :crud="true"
:filter="developmentsFilter" :filter="developmentsFilter"
model="claim" model="claimDevelopment"
ref="claimDevelopmentForm" ref="claimDevelopmentForm"
:data-required="{ claimFk: route.params.id }" :data-required="{ claimFk: route.params.id }"
> >
<template #moreActions>
<QBtn
:label="t('globals.remove')"
color="primary"
icon="delete"
@click="claimDevelopmentForm.remove(selected)"
:disable="!selected.length"
/>
</template>
<template #form="{ data }"> <template #form="{ data }">
<QTable <QTable
:columns="columns" :columns="columns"
:rows="data" :rows="data"
:pagination="{ rowsPerPage: 0 }" :pagination="{ rowsPerPage: 0 }"
row-key="id" row-key="$index"
selection="multiple" selection="multiple"
hide-pagination hide-pagination
v-model:selected="selected" v-model:selected="selected"
@ -147,12 +156,6 @@ const columns = computed(() => [
:label="col.label" :label="col.label"
v-model="row[col.model]" v-model="row[col.model]"
:options="col.options" :options="col.options"
@update:options="
(newOptions) => {
console.log(newOptions);
col.options = newOptions;
}
"
:option-value="col.optionValue" :option-value="col.optionValue"
:option-label="col.optionLabel" :option-label="col.optionLabel"
/> />
@ -172,12 +175,6 @@ const columns = computed(() => [
:label="col.label" :label="col.label"
v-model="props.row[col.model]" v-model="props.row[col.model]"
:options="col.options" :options="col.options"
@update:options="
(newOptions) => {
console.log(newOptions);
col.options = newOptions;
}
"
:option-value="col.optionValue" :option-value="col.optionValue"
:option-label="col.optionLabel" :option-label="col.optionLabel"
dense dense
@ -193,17 +190,6 @@ const columns = computed(() => [
</FormModel> </FormModel>
</div> </div>
</div> </div>
<Teleport to="#sub-toolbar">
<QToolbar class="bg-dark text-white justify-end">
<div class="row q-gutter-x-sm">
<QBtn
color="primary"
icon="delete"
@click="claimDevelopmentForm.remove(selected)"
/>
</div>
</QToolbar>
</Teleport>
<QPageSticky position="bottom-right" :offset="[25, 25]"> <QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn fab color="primary" icon="add" @click="claimDevelopmentForm.insert()" /> <QBtn fab color="primary" icon="add" @click="claimDevelopmentForm.insert()" />
</QPageSticky> </QPageSticky>

View File

@ -4,7 +4,6 @@ import { ref, computed } from 'vue';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore';
import { useSession } from 'composables/useSession'; import { useSession } from 'composables/useSession';
import VnConfirm from 'components/ui/VnConfirm.vue'; import VnConfirm from 'components/ui/VnConfirm.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
@ -12,7 +11,6 @@ import FetchData from 'components/FetchData.vue';
const router = useRouter(); const router = useRouter();
const quasar = useQuasar(); const quasar = useQuasar();
const { t } = useI18n(); const { t } = useI18n();
const stateStore = useStateStore();
const session = useSession(); const session = useSession();
const token = session.getToken(); const token = session.getToken();
@ -237,19 +235,9 @@ function onDrag() {
</div> </div>
</div> </div>
<Teleport <QPageSticky position="bottom-right" :offset="[25, 25]">
v-if="stateStore.isHeaderMounted() && !quasar.platform.is.mobile"
to="#actions-prepend"
>
<div class="row q-gutter-x-sm">
<label for="fileInput"> <label for="fileInput">
<QBtn <QBtn fab @click="inputFile.nativeEl.click()" icon="add" color="primary">
@click="inputFile.nativeEl.click()"
icon="add"
color="primary"
dense
rounded
>
<QInput <QInput
ref="inputFile" ref="inputFile"
type="file" type="file"
@ -261,35 +249,6 @@ function onDrag() {
<QTooltip bottom> {{ t('globals.add') }} </QTooltip> <QTooltip bottom> {{ t('globals.add') }} </QTooltip>
</QBtn> </QBtn>
</label> </label>
<QSeparator vertical />
</div>
</Teleport>
<QPageSticky
v-if="quasar.platform.is.mobile"
position="bottom"
:offset="[0, 0]"
expand
>
<QToolbar class="bg-primary text-white q-pa-none">
<QTabs class="full-width" align="justify" inline-label narrow-indicator>
<QTab
@click="inputFile.nativeEl.click()"
icon="add_circle"
:label="t('globals.add')"
>
<QInput
ref="inputFile"
type="file"
style="display: none"
multiple
v-model="files"
@update:model-value="create()"
/>
<QTooltip bottom> {{ t('globals.add') }} </QTooltip>
</QTab>
</QTabs>
</QToolbar>
</QPageSticky> </QPageSticky>
<!-- MULTIMEDIA DIALOG START--> <!-- MULTIMEDIA DIALOG START-->

View File

@ -5,7 +5,6 @@ import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { useStateStore } from 'stores/useStateStore';
import VnPaginate from 'src/components/ui/VnPaginate.vue'; import VnPaginate from 'src/components/ui/VnPaginate.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnConfirm from 'src/components/ui/VnConfirm.vue'; import VnConfirm from 'src/components/ui/VnConfirm.vue';
@ -15,7 +14,6 @@ import { toDate } from 'src/filters';
const quasar = useQuasar(); const quasar = useQuasar();
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const stateStore = useStateStore();
const arrayData = useArrayData('ClaimRma'); const arrayData = useArrayData('ClaimRma');
const claim = ref(); const claim = ref();
@ -147,29 +145,20 @@ async function remove({ id }) {
</div> </div>
</div> </div>
<Teleport <QPageSticky position="bottom-right" :offset="[25, 25]">
v-if="stateStore.isHeaderMounted() && !quasar.platform.is.mobile" <label for="fileInput">
to="#actions-prepend" <QBtn fab @click="inputFile.nativeEl.click()" icon="add" color="primary">
> <QInput
<div class="row q-gutter-x-sm"> ref="inputFile"
<QBtn @click="addRow()" icon="add" color="primary" dense rounded> type="file"
style="display: none"
multiple
v-model="files"
@update:model-value="create()"
/>
<QTooltip bottom> {{ t('globals.add') }} </QTooltip> <QTooltip bottom> {{ t('globals.add') }} </QTooltip>
</QBtn> </QBtn>
<QSeparator vertical /> </label>
</div>
</Teleport>
<QPageSticky
v-if="quasar.platform.is.mobile"
position="bottom"
:offset="[0, 0]"
expand
>
<QToolbar class="bg-primary text-white q-pa-none">
<QTabs class="full-width" align="justify" inline-label narrow-indicator>
<QTab @click="addRow()" icon="add_circle" :label="t('globals.add')" />
</QTabs>
</QToolbar>
</QPageSticky> </QPageSticky>
</template> </template>

View File

@ -1,11 +1,14 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useRoute } from 'vue-router';
import CustomerDescriptor from './CustomerDescriptor.vue'; import CustomerDescriptor from './CustomerDescriptor.vue';
import LeftMenu from 'components/LeftMenu.vue'; import LeftMenu from 'components/LeftMenu.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
const stateStore = useStateStore(); const stateStore = useStateStore();
const route = useRoute();
console.log(route);
const { t } = useI18n(); const { t } = useI18n();
</script> </script>
<template> <template>
@ -25,6 +28,13 @@ const { t } = useI18n();
</QScrollArea> </QScrollArea>
</QDrawer> </QDrawer>
<QPageContainer> <QPageContainer>
<QToolbar id="sub-toolbar" class="bg-dark text-white justify-end">
<div id="st-data">
{{ route.meta?.title && t(`customer.pageTitles.${route.meta.title}`) }}
</div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<QPage class="q-pa-md"> <QPage class="q-pa-md">
<RouterView></RouterView> <RouterView></RouterView>
</QPage> </QPage>

View File

@ -107,6 +107,7 @@ export default {
meta: { meta: {
title: 'development', title: 'development',
icon: 'vn:traceability', icon: 'vn:traceability',
roles: ['claimManager'],
}, },
component: () => import('src/pages/Claim/Card/ClaimDevelopment.vue'), component: () => import('src/pages/Claim/Card/ClaimDevelopment.vue'),
}, },