0
0
Fork 0

Claim RMA refactor

This commit is contained in:
Joan Sanchez 2023-03-01 16:20:28 +01:00
parent 2aa9f7d365
commit 55a9df0010
20 changed files with 239 additions and 196 deletions

View File

@ -1,5 +1,5 @@
<script setup>
import { onMounted, ref, watch } from 'vue';
import { onMounted, onUnmounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useArrayData } from 'composables/useArrayData';
@ -61,8 +61,8 @@ const arrayData = useArrayData(props.dataKey, {
});
const store = arrayData.store;
onMounted(async () => {
if (props.autoLoad) await fetch();
onMounted(() => {
if (props.autoLoad) fetch();
});
watch(
@ -106,8 +106,10 @@ async function paginate() {
}
async function onLoad(...params) {
if (!store.data) return;
const done = params[1];
if (!store.data || store.data.length === 0 || !props.url) return done(false);
if (store.data.length === 0 || !props.url) return done(false);
pagination.value.page = pagination.value.page + 1;
@ -119,22 +121,16 @@ async function onLoad(...params) {
</script>
<template>
<q-infinite-scroll @load="onLoad" :offset="offset" class="column items-center">
<div v-if="store" class="card-list q-gutter-y-md">
<slot name="body" :rows="store.data"></slot>
<div class="column items-center">
<div
v-if="!store.data.length && !isLoading"
v-if="store.data && store.data.length === 0 && !isLoading"
class="info-row q-pa-md text-center"
>
<h5>
{{ t('components.smartCard.noData') }}
</h5>
</div>
<div v-if="isLoading" class="info-row q-pa-md text-center">
<q-spinner color="orange" size="md" />
</div>
</div>
<div v-if="!store" class="card-list q-gutter-y-md">
<div v-if="props.autoLoad && !store.data" class="card-list q-gutter-y-md">
<q-card class="card" v-for="$index in $props.limit" :key="$index">
<q-item v-ripple class="q-pa-none items-start cursor-pointer q-hoverable">
<q-item-section class="q-pa-md">
@ -153,6 +149,19 @@ async function onLoad(...params) {
</q-item>
</q-card>
</div>
</div>
<q-infinite-scroll
v-if="store.data"
@load="onLoad"
:offset="offset"
class="column items-center"
>
<div v-if="store" class="card-list q-gutter-y-md">
<slot name="body" :rows="store.data"></slot>
<div v-if="isLoading" class="info-row q-pa-md text-center">
<q-spinner color="orange" size="md" />
</div>
</div>
</q-infinite-scroll>
</template>

View File

@ -13,6 +13,11 @@ const props = defineProps({
required: false,
default: 'Search',
},
info: {
type: String,
required: false,
default: '',
},
redirect: {
type: Boolean,
required: false,
@ -73,12 +78,20 @@ async function search() {
@click="searchText = ''"
class="cursor-pointer"
/>
<q-icon v-if="props.info" name="info" class="cursor-info">
<q-tooltip>{{ props.info }}</q-tooltip>
</q-icon>
</template>
</q-input>
</q-form>
</template>
<style lang="scss">
.cursor-info {
cursor: help;
}
#searchbar .q-field {
min-width: 350px;
}

View File

@ -1,4 +1,4 @@
import { onMounted, ref } from 'vue';
import { onMounted, ref, computed } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import axios from 'axios';
import { useArrayDataStore } from 'stores/useArrayDataStore';
@ -47,9 +47,7 @@ export function useArrayData(key, userOptions) {
};
Object.assign(filter, store.userFilter);
Object.assign(filter, store.filter);
store.filter = filter;
Object.assign(store.filter, filter);
const params = {
filter: JSON.stringify(filter),
@ -67,6 +65,7 @@ export function useArrayData(key, userOptions) {
hasMoreData.value = response.data.length === limit;
if (append === true) {
if (!store.data) store.data = [];
for (const row of response.data) store.data.push(row);
}
@ -79,8 +78,7 @@ export function useArrayData(key, userOptions) {
canceller = null;
}
function clear() {
function destroy() {
if (arrayDataStore.get(key)) {
arrayDataStore.clear(key);
}
@ -110,7 +108,7 @@ export function useArrayData(key, userOptions) {
async function loadMore() {
if (!hasMoreData.value) return;
store.skip = store.limit * (page.value - 1);
store.skip = store.limit * page.value;
page.value += 1;
await fetch({ append: true });
@ -134,15 +132,18 @@ export function useArrayData(key, userOptions) {
});
}
const totalRows = computed(() => store.data && store.data.length | 0);
return {
fetch,
applyFilter,
addFilter,
refresh,
clear,
destroy,
loadMore,
store,
hasMoreData,
totalRows,
updateStateParams,
};
}

View File

@ -3,7 +3,7 @@ import { useI18n } from 'vue-i18n';
export default function (value, options = {}) {
if (!value) return;
if (!options.dateStyle) {
if (!options.dateStyle && !options.timeStyle) {
options.day = '2-digit';
options.month = '2-digit';
options.year = 'numeric';

View File

@ -16,7 +16,6 @@ const stateStore = useStateStore();
v-model="stateStore.rightDrawer"
side="right"
:width="256"
:breakpoint="1000"
:persistent="false"
>
<q-scroll-area class="fit text-grey-8">

View File

@ -1,19 +1,23 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useState } from 'composables/useState';
import { useStateStore } from 'stores/useStateStore';
import ClaimDescriptor from './ClaimDescriptor.vue';
import LeftMenu from 'components/LeftMenu.vue';
import TeleportSlot from 'components/ui/TeleportSlot.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
const state = useState();
const stateStore = useStateStore();
const { t } = useI18n();
</script>
<template>
<teleport-slot to="#searchbar">
<VnSearchbar data-key="ClaimList" :label="t('Search by claim id or name')" />
<VnSearchbar
data-key="ClaimList"
:label="t('Search claim')"
:info="t('You can search by claim id or customer name')"
/>
</teleport-slot>
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
<q-drawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<q-scroll-area class="fit">
<claim-descriptor />
<q-separator />
@ -29,5 +33,6 @@ const { t } = useI18n();
<i18n>
es:
Search by claim id or name: Buscar por id o nombre de la reclamación
Search claim: Buscar reclamación
You can search by claim id or customer name: Puedes buscar por id de la reclamación o nombre del cliente
</i18n>

View File

@ -1,25 +1,33 @@
<script setup>
import axios from 'axios';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import { useRoute } from 'vue-router';
import axios from 'axios';
import { useArrayData } from 'src/composables/useArrayData';
import Paginate from 'src/components/PaginateData.vue';
import FetchData from 'components/FetchData.vue';
import TeleportSlot from 'components/ui/TeleportSlot.vue';
import VnConfirm from 'src/components/ui/VnConfirm.vue';
import { toDate } from 'src/filters';
const quasar = useQuasar();
const route = useRoute();
const { t } = useI18n();
const arrayData = useArrayData('ClaimRma');
const claim = ref([]);
const claim = ref();
const fetcher = ref();
const claimFilter = {
fields: ['rma'],
};
async function onFetch(data) {
claim.value = data;
const filter = {
include: {
relation: 'rmas',
scope: {
include: {
relation: 'worker',
scope: {
@ -29,17 +37,21 @@ const filter = {
},
},
order: 'created DESC',
},
where: {
code: claim.value.rma,
},
};
arrayData.applyFilter({ filter });
}
async function addRow() {
const formData = {
code: claim.value.rma,
};
await axios.post(`ClaimRmas`, formData);
await fetcher.value.fetch();
await arrayData.refresh();
quasar.notify({
type: 'positive',
@ -48,19 +60,17 @@ async function addRow() {
});
}
const confirmShown = ref(false);
const rmaId = ref(null);
function confirmRemove(id) {
confirmShown.value = true;
rmaId.value = id;
quasar
.dialog({
component: VnConfirm,
})
.onOk(() => remove(id));
}
async function remove() {
const id = rmaId.value;
async function remove(id) {
await axios.delete(`ClaimRmas/${id}`);
await fetcher.value.fetch();
confirmShown.value = false;
await arrayData.refresh();
quasar.notify({
type: 'positive',
@ -68,41 +78,36 @@ async function remove() {
icon: 'check',
});
}
function hide() {
rmaId.value = null;
}
</script>
<template>
<fetch-data
ref="fetcher"
:url="`Claims/${route.params.id}`"
:filter="filter"
@on-fetch="(data) => (claim = data)"
:filter="claimFilter"
@on-fetch="onFetch"
auto-load
/>
<paginate :data="claim.rmas">
<paginate data-key="ClaimRma" url="ClaimRmas">
<template #body="{ rows }">
<q-card class="card">
<template v-for="row of rows" :key="row.id">
<template v-for="(row, index) of rows" :key="row.id">
<q-item class="q-pa-none items-start">
<q-item-section class="q-pa-md">
<q-list>
<q-item class="q-pa-none">
<q-item-section>
<q-item-label caption>{{
t('claim.rma.user')
}}</q-item-label>
<q-item-label>{{
row.worker.user.name
}}</q-item-label>
<q-item-label caption>
{{ t('claim.rma.user') }}
</q-item-label>
<q-item-label>
{{ row.worker.user.name }}
</q-item-label>
</q-item-section>
</q-item>
<q-item class="q-pa-none">
<q-item-section>
<q-item-label caption>{{
t('claim.rma.created')
}}</q-item-label>
<q-item-label caption>
{{ t('claim.rma.created') }}
</q-item-label>
<q-item-label>
{{
toDate(row.created, {
@ -126,31 +131,12 @@ function hide() {
</q-btn>
</q-card-actions>
</q-item>
<q-separator />
<q-separator v-if="index !== rows.length - 1" />
</template>
</q-card>
</template>
</paginate>
<q-dialog v-model="confirmShown" persistent @hide="hide">
<q-card>
<q-card-section class="row items-center">
<q-avatar icon="warning" color="primary" text-color="white" />
<span class="q-ml-sm">{{ t('globals.confirmRemove') }}</span>
</q-card-section>
<q-card-actions align="right">
<q-btn
flat
:label="t('globals.no')"
color="primary"
v-close-popup
autofocus
/>
<q-btn flat :label="t('globals.yes')" color="primary" @click="remove()" />
</q-card-actions>
</q-card>
</q-dialog>
<teleport-slot v-if="!quasar.platform.is.mobile" to="#header-actions">
<div class="row q-gutter-x-sm">
<q-btn @click="addRow()" icon="add" color="primary" dense rounded>

View File

@ -17,8 +17,17 @@ const router = useRouter();
const quasar = useQuasar();
const { t } = useI18n();
onMounted(() => stateStore.toggleRightDrawer());
onUnmounted(() => stateStore.toggleRightDrawer());
onMounted(() => {
if (!stateStore.rightDrawer) {
stateStore.toggleRightDrawer();
}
});
onUnmounted(() => {
if (stateStore.rightDrawer) {
stateStore.toggleRightDrawer();
}
});
function stateColor(code) {
if (code === 'pending') return 'green';
@ -42,13 +51,17 @@ function viewSummary(id) {
<template>
<teleport-slot to="#searchbar">
<VnSearchbar data-key="ClaimList" :label="t('Search by claim id or name')" />
<VnSearchbar
data-key="ClaimList"
:label="t('Search claim')"
:info="t('You can search by claim id or customer name')"
/>
</teleport-slot>
<teleport-slot to="#rightPanel">
<ClaimFilter data-key="ClaimList" />
</teleport-slot>
<q-page class="q-pa-md">
<paginate data-key="ClaimList" url="Claims/filter" sort-by="id DESC" auto-load>
<paginate data-key="ClaimList" url="Claims/filter" order="id DESC" auto-load>
<template #body="{ rows }">
<q-card class="card" v-for="row of rows" :key="row.id">
<q-item
@ -164,5 +177,6 @@ function viewSummary(id) {
<i18n>
es:
Search by claim id or name: Buscar por id o nombre de la reclamación
Search claim: Buscar reclamación
You can search by claim id or customer name: Puedes buscar por id de la reclamación o nombre del cliente
</i18n>

View File

@ -1,12 +1,12 @@
<script setup>
import { useState } from 'src/composables/useState';
import { useStateStore } from 'stores/useStateStore';
import LeftMenu from 'components/LeftMenu.vue';
const state = useState();
const stateStore = useStateStore();
</script>
<template>
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="800">
<q-drawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<q-scroll-area class="fit text-grey-8">
<LeftMenu />
</q-scroll-area>

View File

@ -4,16 +4,15 @@ import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import axios from 'axios';
import Paginate from 'src/components/PaginateData.vue';
import { useArrayData } from 'src/composables/useArrayData';
import VnConfirm from 'src/components/ui/VnConfirm.vue';
const quasar = useQuasar();
const { t } = useI18n();
const rmas = ref([]);
const card = ref(null);
function onFetch(data) {
rmas.value = data.value;
}
const arrayData = useArrayData('ClaimRmaList');
const isLoading = ref(false);
const input = ref();
const newRma = ref({
code: '',
@ -24,46 +23,38 @@ function onInputUpdate(value) {
newRma.value.code = value.toUpperCase();
}
function submit() {
async function submit() {
const formData = newRma.value;
if (formData.code === '') return;
axios
.post('ClaimRmas', formData)
.then(() => {
isLoading.value = true;
await axios.post('ClaimRmas', formData);
await arrayData.refresh();
isLoading.value = false;
input.value.$el.focus();
newRma.value = {
code: '',
crated: new Date(),
created: new Date(),
};
})
.then(() => card.value.refresh());
}
const confirmShown = ref(false);
const rmaId = ref(null);
function confirm(id) {
confirmShown.value = true;
rmaId.value = id;
quasar
.dialog({
component: VnConfirm,
})
.onOk(() => remove(id));
}
function remove() {
const id = rmaId.value;
axios
.delete(`ClaimRmas/${id}`)
.then(() => {
confirmShown.value = false;
async function remove(id) {
await axios.delete(`ClaimRmas/${id}`);
await arrayData.refresh();
quasar.notify({
type: 'positive',
message: 'Entry deleted',
message: t('globals.rowRemoved'),
icon: 'check',
});
})
.then(() => card.value.refresh());
}
function hide() {
rmaId.value = null;
}
</script>
@ -73,34 +64,74 @@ function hide() {
<q-card class="card q-pa-md">
<q-form @submit="submit">
<q-input
ref="input"
v-model="newRma.code"
:label="t('claim.rmaList.code')"
@update:model-value="onInputUpdate"
class="q-mb-md"
:readonly="isLoading"
:loading="isLoading"
autofocus
/>
<div class="text-caption">{{ rmas.length }} {{ t('claim.rmaList.records') }}</div>
<div class="text-caption">
{{ arrayData.totalRows }} {{ t('claim.rmaList.records') }}
</div>
</q-form>
</q-card>
</q-page-sticky>
<paginate ref="card" url="/ClaimRmas" @on-fetch="onFetch" sort-by="id DESC" auto-load>
<paginate
data-key="ClaimRmaList"
url="ClaimRmas"
order="id DESC"
:offset="50"
auto-load
>
<template #body="{ rows }">
<q-card class="card">
<template v-for="row of rows" :key="row.code">
<template v-if="isLoading">
<q-item class="q-pa-none items-start">
<q-item-section class="q-pa-md">
<q-list>
<q-item class="q-pa-none">
<q-item-section>
<q-item-label caption>{{ t('claim.rmaList.code') }}</q-item-label>
<q-item-label caption>
<q-skeleton />
</q-item-label>
<q-item-label
><q-skeleton type="text"
/></q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-item-section>
<q-card-actions vertical class="justify-between">
<q-skeleton type="circle" class="q-mb-md" size="40px" />
</q-card-actions>
</q-item>
<q-separator />
</template>
<template v-for="row of rows" :key="row.id">
<q-item class="q-pa-none items-start">
<q-item-section class="q-pa-md">
<q-list>
<q-item class="q-pa-none">
<q-item-section>
<q-item-label caption>{{
t('claim.rmaList.code')
}}</q-item-label>
<q-item-label>{{ row.code }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-item-section>
<q-card-actions vertical class="justify-between">
<q-btn flat round color="primary" icon="vn:bin" @click="confirm(row.id)">
<q-btn
flat
round
color="primary"
icon="vn:bin"
@click="confirm(row.id)"
>
<q-tooltip>{{ t('globals.remove') }}</q-tooltip>
</q-btn>
</q-card-actions>
@ -111,20 +142,6 @@ function hide() {
</template>
</paginate>
</q-page>
<q-dialog v-model="confirmShown" persistent @hide="hide">
<q-card>
<q-card-section class="row items-center">
<q-avatar icon="warning" color="primary" text-color="white" />
<span class="q-ml-sm">{{ t('globals.confirmRemove') }}</span>
</q-card-section>
<q-card-actions align="right">
<q-btn flat :label="t('globals.no')" color="primary" v-close-popup autofocus />
<q-btn flat :label="t('globals.yes')" color="primary" @click="remove()" />
</q-card-actions>
</q-card>
</q-dialog>
</template>
<style lang="scss" scoped>

View File

@ -1,12 +1,12 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useState } from 'src/composables/useState';
import { useStateStore } from 'stores/useStateStore';
import CustomerDescriptor from './CustomerDescriptor.vue';
import LeftMenu from 'components/LeftMenu.vue';
import TeleportSlot from 'components/ui/TeleportSlot.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
const state = useState();
const stateStore = useStateStore();
const { t } = useI18n();
</script>
<template>
@ -16,7 +16,11 @@ const { t } = useI18n();
:label="t('Search by customer id or name')"
/>
</teleport-slot>
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
<q-drawer
v-model="stateStore.leftDrawer"
show-if-above
:width="256"
>
<q-scroll-area class="fit">
<CustomerDescriptor />
<q-separator />

View File

@ -10,13 +10,13 @@ import TeleportSlot from 'components/ui/TeleportSlot.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import CustomerFilter from './CustomerFilter.vue';
const state = useStateStore();
const stateStore = useStateStore();
const router = useRouter();
const quasar = useQuasar();
const { t } = useI18n();
onMounted(() => state.toggleRightDrawer());
onUnmounted(() => state.toggleRightDrawer());
onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false));
function navigate(id) {
router.push({ path: `/customer/${id}` });

View File

@ -6,12 +6,7 @@ const stateStore = useStateStore();
</script>
<template>
<q-drawer
v-model="stateStore.leftDrawer"
show-if-above
:width="256"
:breakpoint="800"
>
<q-drawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<q-scroll-area class="fit text-grey-8">
<LeftMenu />
</q-scroll-area>

View File

@ -1,12 +1,12 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useState } from 'composables/useState';
import { useStateStore } from 'stores/useStateStore';
import InvoiceOutDescriptor from './InvoiceOutDescriptor.vue';
import LeftMenu from 'components/LeftMenu.vue';
import TeleportSlot from 'components/ui/TeleportSlot.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue';
const state = useState();
const stateStore = useStateStore();
const { t } = useI18n();
</script>
<template>
@ -16,7 +16,7 @@ const { t } = useI18n();
:label="t('Search by invoice reference')"
/>
</teleport-slot>
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
<q-drawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<q-scroll-area class="fit">
<InvoiceOutDescriptor />
<q-separator />

View File

@ -11,13 +11,13 @@ import TeleportSlot from 'components/ui/TeleportSlot.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import InvoiceOutFilter from './InvoiceOutFilter.vue';
const state = useStateStore();
const stateStore = useStateStore();
const router = useRouter();
const quasar = useQuasar();
const { t } = useI18n();
onMounted(() => state.toggleRightDrawer());
onUnmounted(() => state.toggleRightDrawer());
onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false));
function navigate(id) {
router.push({ path: `/invoiceOut/${id}` });
@ -47,7 +47,7 @@ function viewSummary(id) {
<paginate
data-key="InvoiceOutList"
url="InvoiceOuts/filter"
sort-by="issued DESC, id DESC"
order="issued DESC, id DESC"
auto-load
>
<template #body="{ rows }">

View File

@ -1,12 +1,12 @@
<script setup>
import { useState } from 'src/composables/useState';
import { useStateStore } from 'stores/useStateStore';
import LeftMenu from 'src/components/LeftMenu.vue';
const state = useState();
const stateStore = useStateStore();
</script>
<template>
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="800">
<q-drawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<q-scroll-area class="fit text-grey-8">
<LeftMenu />
</q-scroll-area>

View File

@ -1,19 +1,19 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useState } from 'src/composables/useState';
import { useStateStore } from 'stores/useStateStore';
import TicketDescriptor from './TicketDescriptor.vue';
import LeftMenu from 'components/LeftMenu.vue';
import TeleportSlot from 'components/ui/TeleportSlot.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
const state = useState();
const stateStore = useStateStore();
const { t } = useI18n();
</script>
<template>
<teleport-slot to="#searchbar">
<VnSearchbar data-key="TicketList" :label="t('Search by ticket id or alias')" />
</teleport-slot>
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
<q-drawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<q-scroll-area class="fit">
<TicketDescriptor />
<q-separator />

View File

@ -17,8 +17,8 @@ const quasar = useQuasar();
const { t } = useI18n();
const stateStore = useStateStore();
onMounted(() => stateStore.toggleRightDrawer());
onUnmounted(() => stateStore.toggleRightDrawer());
onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false));
const filter = {
include: [
@ -82,7 +82,7 @@ function viewSummary(id) {
data-key="TicketList"
url="Tickets/filter"
:filter="filter"
sort-by="id DESC"
order="id DESC"
auto-load
>
<template #body="{ rows }">

View File

@ -1,12 +1,12 @@
<script setup>
import { useState } from 'src/composables/useState';
import { useStateStore } from 'stores/useStateStore';
import LeftMenu from 'components/LeftMenu.vue';
const state = useState();
const stateStore = useStateStore();
</script>
<template>
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="800">
<q-drawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<q-scroll-area class="fit text-grey-8">
<LeftMenu />
</q-scroll-area>

View File

@ -17,7 +17,7 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => {
limit: 10,
skip: 0,
order: '',
data: ref([])
data: ref(),
};
}
@ -28,6 +28,6 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => {
return {
get,
set,
clear
clear,
};
});