0
0
Fork 0
This commit is contained in:
Joan Sanchez 2022-11-03 08:54:42 +01:00
commit 95158b5bae
26 changed files with 1288 additions and 1198 deletions

View File

@ -61,7 +61,7 @@ function responseError(error) {
router.push({ path: '/login' });
}
return Promise.resolve(error);
return Promise.reject(error);
}
axios.interceptors.response.use((response) => {

View File

@ -52,8 +52,7 @@ describe('App', () => {
}
};
await vm.responseError(response);
expect(vm.responseError(response)).rejects.toEqual(expect.objectContaining(response));
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining(
{
type: 'negative',
@ -73,8 +72,7 @@ describe('App', () => {
}
};
await vm.responseError(response);
expect(vm.responseError(response)).rejects.toEqual(expect.objectContaining(response));
expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining(
{
type: 'negative',

View File

@ -0,0 +1,60 @@
<script setup>
import { h, onMounted } from 'vue';
import axios from 'axios';
const $props = defineProps({
autoLoad: {
type: Boolean,
default: false,
},
url: {
type: String,
default: '',
},
filter: {
type: Object,
default: null,
},
where: {
type: Object,
default: null,
},
sortBy: {
type: String,
default: '',
},
limit: {
type: String,
default: '',
},
});
defineExpose({ fetch });
const emit = defineEmits(['onFetch']);
onMounted(async () => {
if ($props.autoLoad) {
await fetch();
}
});
async function fetch() {
const filter = Object.assign({}, $props.filter);
if ($props.where) filter.where = $props.where;
if ($props.sortBy) filter.order = $props.sortBy;
if ($props.limit) filter.limit = $props.limit;
const { data } = await axios.get($props.url, {
params: { filter },
});
emit('onFetch', data);
}
const render = () => {
return h('div', []);
};
</script>
<template>
<render />
</template>

View File

@ -0,0 +1,118 @@
<script setup>
import { onMounted, onUnmounted, computed, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import axios from 'axios';
import { useState } from 'src/composables/useState';
import { useValidator } from 'src/composables/useValidator';
import SkeletonForm from 'src/components/SkeletonForm.vue';
const quasar = useQuasar();
const { t } = useI18n();
const state = useState();
const { validate } = useValidator();
const $props = defineProps({
url: {
type: String,
default: '',
},
model: {
type: String,
default: '',
},
filter: {
type: Object,
default: null,
},
});
const emit = defineEmits(['onFetch']);
defineExpose({
save,
});
onMounted(async () => await fetch());
onUnmounted(() => {
state.unset($props.model);
});
const isLoading = ref(false);
const hasChanges = ref(false);
const formData = computed(() => state.get($props.model));
const originalData = ref();
async function fetch() {
const { data } = await axios.get($props.url, {
params: { filter: $props.filter },
});
state.set($props.model, data);
originalData.value = Object.assign({}, data);
watch(formData.value, () => (hasChanges.value = true));
emit('onFetch', state.get($props.model));
}
async function save() {
if (!hasChanges.value) {
return quasar.notify({
type: 'negative',
message: t('globals.noChanges'),
});
}
isLoading.value = true;
await axios.patch($props.url, formData.value);
originalData.value = formData.value;
hasChanges.value = false;
isLoading.value = false;
}
function reset() {
state.set($props.model, originalData.value);
hasChanges.value = false;
}
function filter(value, update, filterOptions) {
update(
() => {
const { options, filterFn } = filterOptions;
options.value = filterFn(options, value);
},
(ref) => {
ref.setOptionIndex(-1);
ref.moveOptionSelection(1, true);
}
);
}
</script>
<template>
<q-banner v-if="hasChanges" class="text-white bg-warning">
<q-icon name="warning" size="md" class="q-mr-md" />
<span>{{ t('globals.changesToSave') }}</span>
</q-banner>
<q-form v-if="formData" @submit="save" @reset="reset" class="q-pa-md">
<slot name="form" :data="formData" :validate="validate" :filter="filter"></slot>
<div class="q-mt-lg">
<slot name="actions">
<q-btn :label="t('globals.save')" type="submit" color="primary" />
<q-btn
:label="t('globals.reset')"
type="reset"
class="q-ml-sm"
color="primary"
flat
:disable="!hasChanges"
/>
</slot>
</div>
</q-form>
<skeleton-form v-if="!formData" />
<q-inner-loading :showing="isLoading" :label="t('globals.pleaseWait')" color="primary" />
</template>

View File

@ -1,27 +1,39 @@
<script setup>
import axios from 'axios';
import { onMounted, ref } from 'vue';
import { onMounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const $props = defineProps({
autoLoad: {
type: Boolean,
default: false,
},
url: {
type: String,
default: '',
},
data: {
type: Array,
default: null,
},
filter: {
type: Object,
default: null,
},
autoLoad: {
type: Boolean,
default: false,
where: {
type: Object,
default: null,
},
sortBy: {
type: String,
default: '',
},
limit: {
type: String,
default: '',
},
rowsPerPage: {
type: Number,
default: 10,
@ -33,7 +45,18 @@ const $props = defineProps({
});
defineEmits(['onNavigate']);
defineExpose({ fetch });
defineExpose({ refresh });
onMounted(() => {
if ($props.autoLoad) paginate();
});
watch(
() => $props.data,
() => {
rows.value = $props.data;
}
);
const isLoading = ref(false);
const hasMoreData = ref(false);
@ -42,18 +65,12 @@ const pagination = ref({
rowsPerPage: $props.rowsPerPage,
page: 1,
});
const rows = ref(null);
onMounted(() => {
if ($props.autoLoad) fetch();
else rows.value = [];
});
async function fetch() {
const { page, rowsPerPage, sortBy, descending } = pagination.value;
const { page, rowsPerPage, sortBy } = pagination.value;
isLoading.value = true;
if (!$props.url) return;
const filter = {
limit: rowsPerPage,
@ -62,12 +79,26 @@ async function fetch() {
Object.assign(filter, $props.filter);
if ($props.where) filter.where = $props.where;
if ($props.sortBy) filter.order = $props.sortBy;
if ($props.limit) filter.limit = $props.limit;
if (sortBy) filter.order = sortBy;
const { data } = await axios.get($props.url, {
params: { filter },
});
isLoading.value = false;
return data;
}
async function paginate() {
const { page, rowsPerPage, sortBy, descending } = pagination.value;
const data = await fetch();
if (!data) {
isLoading.value = false;
return;
@ -87,13 +118,31 @@ async function fetch() {
isLoading.value = false;
}
async function refresh() {
const { rowsPerPage } = pagination.value;
const data = await fetch();
if (!data) {
isLoading.value = false;
return;
}
hasMoreData.value = data.length === rowsPerPage;
if (!rows.value) rows.value = [];
rows.value = data;
isLoading.value = false;
}
async function onLoad(...params) {
const done = params[1];
if (!rows.value || rows.value.length === 0) return done(false);
if (!rows.value || rows.value.length === 0 || !$props.url) return done(false);
pagination.value.page = pagination.value.page + 1;
await fetch();
await paginate();
const endOfPages = !hasMoreData.value;
done(endOfPages);
@ -103,34 +152,7 @@ async function onLoad(...params) {
<template>
<q-infinite-scroll @load="onLoad" :offset="offset" class="column items-center">
<div v-if="rows" class="card-list q-gutter-y-md">
<q-card class="card" v-for="row of rows" :key="row.id">
<q-item v-ripple class="q-pa-none items-start cursor-pointer q-hoverable">
<q-item-section class="q-pa-md" @click="$emit('onNavigate', row.id)">
<slot name="header" :row="row">
<div class="text-h6">{{ row.name }}</div>
<q-item-label caption>#{{ row.id }}</q-item-label>
</slot>
<slot name="labels" :row="row"></slot>
</q-item-section>
<q-separator vertical />
<q-card-actions vertical class="justify-between">
<slot name="actions" :row="row">
<q-btn
flat
round
color="orange"
icon="arrow_circle_right"
@click="$emit('onNavigate', row.id)"
>
<q-tooltip>{{ t('components.smartCard.openCard') }}</q-tooltip>
</q-btn>
<q-btn flat round color="grey-7" icon="preview">
<q-tooltip>{{ t('components.smartCard.openSummary') }}</q-tooltip>
</q-btn>
</slot>
</q-card-actions>
</q-item>
</q-card>
<slot name="body" :rows="rows"></slot>
<div v-if="!rows.length && !isLoading" class="info-row q-pa-md text-center">
<h5>
{{ t('components.smartCard.noData') }}

View File

@ -1,4 +1,5 @@
<template>
<div class="q-pa-md">
<div class="row q-gutter-md q-mb-md">
<div class="col">
<q-skeleton type="QInput" square />
@ -27,4 +28,5 @@
<q-skeleton type="QBtn" />
<q-skeleton type="QBtn" />
</div>
</div>
</template>

View File

@ -1,6 +1,6 @@
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
import { createWrapper, axios } from 'app/tests/jest/jestHelpers';
import SmartCard from '../SmartCard.vue';
import Paginate from '../Paginate.vue';
const mockPush = jest.fn();
@ -11,7 +11,7 @@ jest.mock('vue-router', () => ({
}),
}));
describe('SmartCard', () => {
describe('Paginate', () => {
const expectedUrl = '/api/customers';
let vm;
beforeAll(() => {
@ -22,7 +22,7 @@ describe('SmartCard', () => {
rowsPerPage: 3
}
};
vm = createWrapper(SmartCard, options).vm;
vm = createWrapper(Paginate, options).vm;
jest.spyOn(axios, 'get').mockResolvedValue({
data: [
@ -39,8 +39,8 @@ describe('SmartCard', () => {
vm.hasMoreData = true;
})
describe('fetch()', () => {
it('should call to the fetch() method and set the data on the rows property', async () => {
describe('paginate()', () => {
it('should call to the paginate() method and set the data on the rows property', async () => {
const expectedOptions = {
params: {
filter: {
@ -51,13 +51,13 @@ describe('SmartCard', () => {
}
};
await vm.fetch();
await vm.paginate();
expect(axios.get).toHaveBeenCalledWith(expectedUrl, expectedOptions);
expect(vm.rows.length).toEqual(3);
});
it('should call to the fetch() method and then call it again to paginate', async () => {
it('should call to the paginate() method and then call it again to paginate', async () => {
const expectedOptions = {
params: {
filter: {
@ -68,7 +68,7 @@ describe('SmartCard', () => {
}
};
await vm.fetch();
await vm.paginate();
expect(axios.get).toHaveBeenCalledWith(expectedUrl, expectedOptions);
expect(vm.rows.length).toEqual(3);
@ -85,7 +85,7 @@ describe('SmartCard', () => {
vm.pagination.page = 2;
await vm.fetch();
await vm.paginate();
expect(axios.get).toHaveBeenCalledWith(expectedUrl, expectedOptionsPaginated);
expect(vm.rows.length).toEqual(6);

View File

@ -1,5 +1,7 @@
import { ref, computed } from 'vue';
const state = ref({});
const user = ref({
id: 0,
name: '',
@ -44,11 +46,27 @@ export function useState() {
roles.value = data;
}
function set(name, data) {
state.value[name] = ref(data);
}
function get(name) {
return state.value[name];
}
function unset(name) {
delete state.value[name];
}
return {
getUser,
setUser,
getRoles,
setRoles,
set,
get,
unset,
drawer
};
}

View File

@ -33,10 +33,6 @@ export function useValidator() {
return validations(validation)[validation.validation];
});
if (property === 'socialName') {
console.log(modelValidations[property])
}
return rules;
}

View File

@ -21,9 +21,11 @@ export default {
yes: 'Yes',
no: 'No',
noChanges: 'No changes to save',
changesToSave: 'You have changes pending to save',
confirmRemove: 'You are about to delete this row. Are you sure?',
rowAdded: 'Row added',
rowRemoved: 'Row removed'
rowRemoved: 'Row removed',
pleaseWait: 'Please wait...'
},
moduleIndex: {
allModules: 'All modules'
@ -150,7 +152,8 @@ export default {
list: 'List',
createTicket: 'Create ticket',
summary: 'Summary',
basicData: 'Basic Data'
basicData: 'Basic Data',
boxing: 'Boxing'
},
list: {
nickname: 'Nickname',
@ -197,8 +200,7 @@ export default {
state: 'State'
},
rmaList: {
code: 'Code',
newRma: 'New RMA...'
code: 'Code'
},
rma: {
user: 'User',

View File

@ -21,9 +21,11 @@ export default {
yes: 'Si',
no: 'No',
noChanges: 'Sin cambios que guardar',
changesToSave: 'Tienes cambios pendientes de guardar',
confirmRemove: 'Vas a eliminar este registro. ¿Continuar?',
rowAdded: 'Fila añadida',
rowRemoved: 'Fila eliminada'
rowRemoved: 'Fila eliminada',
pleaseWait: 'Por favor, espera...'
},
moduleIndex: {
allModules: 'Todos los módulos'
@ -149,7 +151,8 @@ export default {
list: 'Listado',
createTicket: 'Crear ticket',
summary: 'Resumen',
basicData: 'Datos básicos'
basicData: 'Datos básicos',
boxing: 'Encajado'
},
list: {
nickname: 'Alias',
@ -196,8 +199,7 @@ export default {
state: 'Estado'
},
rmaList: {
code: 'Código',
newRma: 'Nuevo RMA...'
code: 'Código'
},
rma: {
user: 'Usuario',

View File

@ -1,33 +1,18 @@
<script setup>
import { ref, onMounted, watch } from 'vue';
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import axios from 'axios';
import { useSession } from 'src/composables/useSession';
import { useValidator } from 'src/composables/useValidator';
import SkeletonForm from 'src/components/SkeletonForm.vue';
onMounted(() => {
fetch();
fetchWorkers();
fetchClaimStates();
});
import { useSession } from 'src/composables/useSession';
import FetchData from 'src/components/FetchData.vue';
import FormModel from 'src/components/FormModel.vue';
const route = useRoute();
const quasar = useQuasar();
const { t } = useI18n();
const { validate } = useValidator();
const session = useSession();
const token = session.getToken();
const claim = ref(null);
const claimCopy = ref(null);
const hasChanges = ref(false);
function fetch() {
const id = route.params.id;
const filter = {
const claimFilter = {
include: [
{
relation: 'client',
@ -36,117 +21,80 @@ function fetch() {
},
},
],
};
const options = { params: { filter } };
axios.get(`Claims/${id}`, options).then(({ data }) => {
claim.value = data;
claimCopy.value = Object.assign({}, data);
watch(claim.value, () => (hasChanges.value = true));
});
}
};
const workers = ref([]);
const workersCopy = ref([]);
function fetchWorkers() {
const filter = {
where: {
role: 'salesPerson',
},
};
const options = { params: { filter } };
axios.get(`Workers/activeWithRole`, options).then(({ data }) => {
workers.value = data;
workersCopy.value = data;
});
}
const claimStates = ref([]);
const claimStatesCopy = ref([]);
function fetchClaimStates() {
axios.get(`ClaimStates`).then(({ data }) => {
function setWorkers(data) {
workers.value = data;
workersCopy.value = data;
}
function setClaimStates(data) {
claimStates.value = data;
claimStatesCopy.value = data;
});
}
function filter(value, update, options, originalOptions, filter) {
update(
() => {
if (value === '') {
options.value = originalOptions.value;
return;
}
options.value = options.value.filter(filter);
},
(ref) => {
ref.setOptionIndex(-1);
ref.moveOptionSelection(1, true);
}
);
}
function filterWorkers(value, update) {
const workerFilter = {
options: workers,
filterFn: (options, value) => {
const search = value.toLowerCase();
filter(value, update, workers, workersCopy, (row) => {
if (value === '') return workersCopy.value;
return options.value.filter((row) => {
const id = row.id;
const name = row.name.toLowerCase();
const idMatch = id == search;
const nameMatch = name.indexOf(search) > -1;
const idMatches = id == search;
const nameMatches = name.indexOf(search) > -1;
return idMatch || nameMatch;
return idMatches || nameMatches;
});
}
},
};
function filterStates(value, update) {
const statesFilter = {
options: claimStates,
filterFn: (options, value) => {
const search = value.toLowerCase();
filter(value, update, claimStates, claimStatesCopy, (row) => {
if (value === '') return claimStatesCopy.value;
return options.value.filter((row) => {
const description = row.description.toLowerCase();
return description.indexOf(search) > -1;
});
}
function save() {
const id = route.params.id;
const formData = claim.value;
if (!hasChanges.value) {
return quasar.notify({
type: 'negative',
message: t('globals.noChanges'),
});
}
axios.patch(`Claims/${id}`, formData).then((hasChanges.value = false));
}
function onReset() {
claim.value = claimCopy.value;
hasChanges.value = false;
}
},
};
</script>
<template>
<q-page class="q-pa-md">
<fetch-data
url="Workers/activeWithInheritedRole"
:filter="{ where: { role: 'salesPerson' } }"
@on-fetch="setWorkers"
auto-load
/>
<fetch-data url="ClaimStates" @on-fetch="setClaimStates" auto-load />
<div class="container">
<q-card class="q-pa-md">
<skeleton-form v-if="!claim" />
<q-form v-if="claim" @submit="save" @reset="onReset" greedy>
<q-card>
<form-model :url="`Claims/${route.params.id}`" :filter="claimFilter" model="claim">
<template #form="{ data, validate, filter }">
<div class="row q-gutter-md q-mb-md">
<div class="col">
<q-input v-model="claim.client.name" :label="t('claim.basicData.customer')" disable />
<q-input v-model="data.client.name" :label="t('claim.basicData.customer')" disable />
</div>
<div class="col">
<q-input v-model="claim.created" mask="####-##-##" fill-mask="_" autofocus>
<q-input v-model="data.created" mask="####-##-##" fill-mask="_" autofocus>
<template #append>
<q-icon name="event" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-date v-model="claim.created" mask="YYYY-MM-DD">
<q-date v-model="data.created" mask="YYYY-MM-DD">
<div class="row items-center justify-end">
<q-btn v-close-popup label="Close" color="primary" flat />
</div>
@ -160,7 +108,7 @@ function onReset() {
<div class="row q-gutter-md q-mb-md">
<div class="col">
<q-select
v-model="claim.workerFk"
v-model="data.workerFk"
:options="workers"
option-value="id"
option-label="name"
@ -168,15 +116,15 @@ function onReset() {
:label="t('claim.basicData.assignedTo')"
map-options
use-input
@filter="filterWorkers"
@filter="(value, update) => filter(value, update, workerFilter)"
:rules="validate('claim.claimStateFk')"
:input-debounce="0"
>
<template #before>
<q-avatar color="orange">
<q-img
v-if="claim.workerFk"
:src="`/api/Images/user/160x160/${claim.workerFk}/download?access_token=${token}`"
v-if="data.workerFk"
:src="`/api/Images/user/160x160/${data.workerFk}/download?access_token=${token}`"
spinner-color="white"
/>
</q-avatar>
@ -185,7 +133,7 @@ function onReset() {
</div>
<div class="col">
<q-select
v-model="claim.claimStateFk"
v-model="data.claimStateFk"
:options="claimStates"
option-value="id"
option-label="description"
@ -193,7 +141,7 @@ function onReset() {
:label="t('claim.basicData.state')"
map-options
use-input
@filter="filterStates"
@filter="(value, update) => filter(value, update, statesFilter)"
:rules="validate('claim.claimStateFk')"
:input-debounce="0"
>
@ -203,14 +151,14 @@ function onReset() {
<div class="row q-gutter-md q-mb-md">
<div class="col">
<q-input
v-model="claim.packages"
v-model="data.packages"
:label="t('claim.basicData.packages')"
:rules="validate('claim.packages')"
/>
</div>
<div class="col">
<q-input
v-model="claim.rma"
v-model="data.rma"
:label="t('claim.basicData.returnOfMaterial')"
:rules="validate('claim.rma')"
/>
@ -218,17 +166,13 @@ function onReset() {
</div>
<div class="row q-gutter-md q-mb-md">
<div class="col">
<q-checkbox v-model="claim.hasToPickUp" :label="t('claim.basicData.picked')" />
<q-checkbox v-model="data.hasToPickUp" :label="t('claim.basicData.picked')" />
</div>
</div>
<div>
<q-btn :label="t('globals.save')" type="submit" color="primary" />
<q-btn :label="t('globals.reset')" type="reset" class="q-ml-sm" color="primary" flat />
</div>
</q-form>
</template>
</form-model>
</q-card>
</div>
</q-page>
</template>
<style lang="scss" scoped>

View File

@ -163,7 +163,9 @@ function stateColor(code) {
</q-scroll-area>
</q-drawer>
<q-page-container>
<router-view v-if="claim.id" :claim="claim"></router-view>
<q-page class="q-pa-md">
<router-view></router-view>
</q-page>
</q-page-container>
</template>
@ -189,7 +191,7 @@ function stateColor(code) {
}
#descriptor-skeleton .q-card__actions {
justify-content: space-around;
justify-content: space-between;
}
}
</style>

View File

@ -1,24 +1,24 @@
<script setup>
import { onMounted, ref } from 'vue';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import { useRoute } from 'vue-router';
import axios from 'axios';
import SmartCard from 'src/components/SmartCard.vue';
import Paginate from 'src/components/Paginate.vue';
import FetchData from 'src/components/FetchData.vue';
import { toDate } from 'src/filters';
onMounted(() => fetch());
const $props = defineProps({
claim: {
type: Object,
required: true,
},
});
const quasar = useQuasar();
const route = useRoute();
const { t } = useI18n();
const claim = ref([]);
const fetcher = ref();
const filter = {
include: {
relation: 'rmas',
scope: {
include: {
relation: 'worker',
scope: {
@ -27,27 +27,24 @@ const filter = {
},
},
},
where: {
code: $props.claim.rma,
order: 'created DESC',
},
},
};
function fetch() {
//console.log($props.claim);
}
function addRow() {
async function addRow() {
const formData = {
code: $props.claim.rma,
code: claim.value.rma,
};
axios.post(`ClaimRmas`, formData).then(() => {
await axios.post(`ClaimRmas`, formData);
await fetcher.value.fetch();
quasar.notify({
type: 'positive',
message: t('globals.rowAdded'),
icon: 'check',
});
});
}
const confirmShown = ref(false);
@ -57,9 +54,11 @@ function confirmRemove(id) {
rmaId.value = id;
}
function remove() {
async function remove() {
const id = rmaId.value;
axios.delete(`ClaimRmas/${id}`).then(() => {
await axios.delete(`ClaimRmas/${id}`);
await fetcher.value.fetch();
confirmShown.value = false;
quasar.notify({
@ -67,7 +66,6 @@ function remove() {
message: t('globals.rowRemoved'),
icon: 'check',
});
});
}
function hide() {
@ -75,7 +73,14 @@ function hide() {
}
</script>
<template>
<q-page class="q-pa-md sticky">
<fetch-data
ref="fetcher"
:url="`Claims/${route.params.id}`"
:filter="filter"
@on-fetch="($data) => (claim = $data)"
auto-load
/>
<div class="sticky-page">
<q-page-sticky expand position="top">
<q-toolbar class="bg-grey-9">
<q-space />
@ -85,28 +90,41 @@ function hide() {
</q-toolbar>
</q-page-sticky>
<smart-card ref="card" url="/ClaimRmas" :filter="filter" sort-by="id DESC" auto-load>
<template #header="{ row }">
<q-item-label caption>{{ t('claim.rma.user') }}</q-item-label>
<q-item-label>{{ row.worker.user.name }}</q-item-label>
</template>
<template #labels="{ row }">
<paginate :data="claim.rmas">
<template #body="{ rows }">
<q-card class="card">
<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.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>{{ toDate(row.created, { timeStyle: 'medium' }) }}</q-item-label>
<q-item-label>
{{ toDate(row.created, { timeStyle: 'medium' }) }}
</q-item-label>
</q-item-section>
</q-item>
</q-list>
</template>
<template #actions="{ row }">
</q-item-section>
<q-card-actions vertical class="justify-between">
<q-btn flat round color="orange" icon="vn:bin" @click="confirmRemove(row.id)">
<q-tooltip>{{ t('globals.remove') }}</q-tooltip>
</q-btn>
</q-card-actions>
</q-item>
<q-separator />
</template>
</smart-card>
</q-page>
</q-card>
</template>
</paginate>
</div>
<q-dialog v-model="confirmShown" persistent @hide="hide">
<q-card>
<q-card-section class="row items-center">
@ -126,7 +144,7 @@ function hide() {
.q-toolbar {
background-color: $grey-9;
}
.sticky {
.sticky-page {
padding-top: 66px;
}

View File

@ -90,7 +90,6 @@ function stateColor(code) {
</script>
<template>
<q-page class="q-pa-md">
<div class="summary container">
<q-card>
<skeleton-summary v-if="!claim" />
@ -158,7 +157,6 @@ function stateColor(code) {
</template>
</q-card>
</div>
</q-page>
</template>
<style lang="scss" scoped>
@ -194,4 +192,8 @@ function stateColor(code) {
}
}
}
.q-dialog .summary {
max-width: 1200px;
}
</style>

View File

@ -1,52 +0,0 @@
<script setup>
import { reactive, watch } from 'vue'
const customer = reactive({
name: '',
});
watch(() => customer.name, () => {
console.log('customer.name changed');
});
</script>
<template>
<q-page class="q-pa-md">
<q-card class="q-pa-md">
<q-form @submit="onSubmit" @reset="onReset" class="q-gutter-md">
<q-input
filled
v-model="customer.name"
label="Your name *"
hint="Name and surname"
lazy-rules
:rules="[val => val && val.length > 0 || 'Please type something']"
/>
<q-input
filled
type="number"
v-model="age"
label="Your age *"
lazy-rules
:rules="[
val => val !== null && val !== '' || 'Please type your age',
val => val > 0 && val < 100 || 'Please type a real age'
]"
/>
<div>
<q-btn label="Submit" type="submit" color="primary" />
<q-btn label="Reset" type="reset" color="primary" flat class="q-ml-sm" />
</div>
</q-form>
</q-card>
</q-page>
</template>
<style lang="scss" scoped>
.card {
width: 100%;
max-width: 60em;
}
</style>

View File

@ -2,7 +2,7 @@
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import SmartCard from 'src/components/SmartCard.vue';
import Paginate from 'src/components/Paginate.vue';
import { toDate } from 'src/filters/index';
import ClaimSummary from './Card/ClaimSummary.vue';
@ -50,8 +50,13 @@ function showPreview(id) {
<template>
<q-page class="q-pa-md">
<smart-card url="/Claims" :filter="filter" sort-by="id DESC" @on-navigate="navigate" auto-load>
<template #labels="{ row }">
<paginate url="/Claims" :filter="filter" sort-by="id DESC" auto-load>
<template #body="{ rows }">
<q-card class="card" v-for="row of rows" :key="row.id">
<q-item class="q-pa-none items-start cursor-pointer q-hoverable" v-ripple clickable>
<q-item-section class="q-pa-md" @click="navigate(row.id)">
<div class="text-h6">{{ row.name }}</div>
<q-item-label caption>#{{ row.id }}</q-item-label>
<q-list>
<q-item class="q-pa-none">
<q-item-section>
@ -78,8 +83,9 @@ function showPreview(id) {
</q-item-section>
</q-item>
</q-list>
</template>
<template #actions="{ row }">
</q-item-section>
<q-separator vertical />
<q-card-actions vertical class="justify-between">
<q-btn color="grey-7" round flat icon="more_vert">
<q-tooltip>{{ t('customer.list.moreOptions') }}</q-tooltip>
<q-menu cover auto-close>
@ -106,8 +112,11 @@ function showPreview(id) {
<q-btn flat round color="grey-7" icon="preview" @click="showPreview(row.id)">
<q-tooltip>{{ t('components.smartCard.openSummary') }}</q-tooltip>
</q-btn>
</q-card-actions>
</q-item>
</q-card>
</template>
</smart-card>
</paginate>
</q-page>
<q-dialog v-model="preview.shown">
<claim-summary :claim-id="preview.data.claimId" />

View File

@ -3,7 +3,7 @@ import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import axios from 'axios';
import SmartCard from 'src/components/SmartCard.vue';
import Paginate from 'src/components/Paginate.vue';
const quasar = useQuasar();
const { t } = useI18n();
@ -26,7 +26,7 @@ function submit() {
crated: new Date(),
};
})
.then(() => card.value.fetch());
.then(() => card.value.refresh());
}
const confirmShown = ref(false);
@ -38,7 +38,9 @@ function confirm(id) {
function remove() {
const id = rmaId.value;
axios.delete(`ClaimRmas/${id}`).then(() => {
axios
.delete(`ClaimRmas/${id}`)
.then(() => {
confirmShown.value = false;
quasar.notify({
@ -46,7 +48,8 @@ function remove() {
message: 'Entry deleted',
icon: 'check',
});
});
})
.then(() => card.value.refresh());
}
function hide() {
@ -59,14 +62,18 @@ function hide() {
<q-page-sticky expand position="top" :offset="[16, 16]">
<q-card class="card q-pa-md">
<q-form @submit="submit">
<q-input v-model="newRma.code" :label="t('claim.rmaList.newRma')" class="q-mb-md" />
<div class="text-caption">$(0) entries</div>
<q-input v-model="newRma.code" :label="t('claim.rmaList.code')" class="q-mb-md" autofocus />
<!-- <div class="text-caption">$(0) entries</div> -->
</q-form>
</q-card>
</q-page-sticky>
<smart-card ref="card" url="/ClaimRmas" sort-by="id DESC" auto-load>
<template #labels="{ row }">
<paginate ref="card" url="/ClaimRmas" sort-by="id DESC" auto-load>
<template #body="{ rows }">
<q-card class="card">
<template v-for="row of rows" :key="row.id">
<q-item class="q-pa-none items-start">
<q-item-section class="q-pa-md" @click="navigate(row.id)">
<q-list>
<q-item class="q-pa-none">
<q-item-section>
@ -75,13 +82,18 @@ function hide() {
</q-item-section>
</q-item>
</q-list>
</template>
<template #actions="{ row }">
</q-item-section>
<q-card-actions vertical class="justify-between">
<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>
</q-item>
<q-separator />
</template>
</smart-card>
</q-card>
</template>
</paginate>
</q-page>
<q-dialog v-model="confirmShown" persistent @hide="hide">

View File

@ -1,135 +1,64 @@
<script setup>
import { ref, onMounted, watch } from 'vue';
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import axios from 'axios';
import { useSession } from 'src/composables/useSession';
import { useValidator } from 'src/composables/useValidator';
import SkeletonForm from 'src/components/SkeletonForm.vue';
onMounted(() => {
fetch();
fetchWorkers();
fetchBusinessTypes();
fetchContactChannels();
});
import { useSession } from 'src/composables/useSession';
import FetchData from 'src/components/FetchData.vue';
import FormModel from 'src/components/FormModel.vue';
const route = useRoute();
const quasar = useQuasar();
const { t } = useI18n();
const { validate } = useValidator();
const session = useSession();
const token = session.getToken();
const customer = ref(null);
const customerCopy = ref(null);
const hasChanges = ref(false);
function fetch() {
const id = route.params.id;
const filter = {
include: [],
};
const options = { params: { filter } };
axios.get(`Clients/${id}`, options).then(({ data }) => {
customer.value = data;
customerCopy.value = Object.assign({}, data);
watch(customer.value, () => (hasChanges.value = true));
});
}
const businessTypes = ref([]);
function fetchBusinessTypes() {
axios.get(`BusinessTypes`).then(({ data }) => {
businessTypes.value = data;
});
}
const contactChannels = ref([]);
function fetchContactChannels() {
axios.get(`ContactChannels`).then(({ data }) => {
contactChannels.value = data;
});
}
const workers = ref([]);
const workersCopy = ref([]);
function fetchWorkers() {
const filter = {
where: {
role: 'salesPerson',
},
};
const options = { params: { filter } };
axios.get(`Workers/activeWithRole`, options).then(({ data }) => {
const businessTypes = ref([]);
const contactChannels = ref([]);
function setWorkers(data) {
workers.value = data;
workersCopy.value = data;
});
}
function filter(value, update, options, originalOptions, filter) {
update(
() => {
if (value === '') {
options.value = originalOptions.value;
return;
}
options.value = options.value.filter(filter);
},
(ref) => {
ref.setOptionIndex(-1);
ref.moveOptionSelection(1, true);
}
);
}
function filterWorkers(value, update) {
const filterOptions = {
options: workers,
filterFn: (options, value) => {
const search = value.toLowerCase();
filter(value, update, workers, workersCopy, (row) => {
if (value === '') return workersCopy.value;
return options.value.filter((row) => {
const id = row.id;
const name = row.name.toLowerCase();
const idMatch = id == search;
const nameMatch = name.indexOf(search) > -1;
const idMatches = id == search;
const nameMatches = name.indexOf(search) > -1;
return idMatch || nameMatch;
return idMatches || nameMatches;
});
}
function save() {
const id = route.params.id;
const formData = customer.value;
if (!hasChanges.value) {
return quasar.notify({
type: 'negative',
message: t('globals.noChanges'),
});
}
axios.patch(`Clients/${id}`, formData).then((hasChanges.value = false));
}
function onReset() {
customer.value = customerCopy.value;
hasChanges.value = false;
}
},
};
</script>
<template>
<q-page class="q-pa-md">
<fetch-data
url="Workers/activeWithInheritedRole"
:filter="{ where: { role: 'salesPerson' } }"
@on-fetch="setWorkers"
auto-load
/>
<fetch-data url="ContactChannels" @on-fetch="($data) => (contactChannels = $data)" auto-load />
<fetch-data url="BusinessTypes" @on-fetch="($data) => (businessTypes = $data)" auto-load />
<div class="container">
<q-card class="q-pa-md">
<skeleton-form v-if="!customer" />
<q-form v-if="customer" @submit="save" @reset="onReset" greedy>
<q-card>
<form-model :url="`Clients/${route.params.id}`" model="customer">
<template #form="{ data, validate, filter }">
<div class="row q-gutter-md q-mb-md">
<div class="col">
<q-input
v-model="customer.socialName"
v-model="data.socialName"
:label="t('customer.basicData.socialName')"
:rules="validate('client.socialName')"
autofocus
@ -137,7 +66,7 @@ function onReset() {
</div>
<div class="col">
<q-select
v-model="customer.businessTypeFk"
v-model="data.businessTypeFk"
:options="businessTypes"
option-value="code"
option-label="description"
@ -152,7 +81,7 @@ function onReset() {
<div class="row q-gutter-md q-mb-md">
<div class="col">
<q-input
v-model="customer.contact"
v-model="data.contact"
:label="t('customer.basicData.contact')"
:rules="validate('client.contact')"
clearable
@ -160,7 +89,7 @@ function onReset() {
</div>
<div class="col">
<q-input
v-model="customer.email"
v-model="data.email"
type="email"
:label="t('customer.basicData.email')"
:rules="validate('client.email')"
@ -171,7 +100,7 @@ function onReset() {
<div class="row q-gutter-md q-mb-md">
<div class="col">
<q-input
v-model="customer.phone"
v-model="data.phone"
:label="t('customer.basicData.phone')"
:rules="validate('client.phone')"
clearable
@ -179,7 +108,7 @@ function onReset() {
</div>
<div class="col">
<q-input
v-model="customer.mobile"
v-model="data.mobile"
:label="t('customer.basicData.mobile')"
:rules="validate('client.mobile')"
clearable
@ -189,7 +118,7 @@ function onReset() {
<div class="row q-gutter-md q-mb-md">
<div class="col">
<q-select
v-model="customer.salesPersonFk"
v-model="data.salesPersonFk"
:options="workers"
option-value="id"
option-label="name"
@ -197,15 +126,15 @@ function onReset() {
:label="t('customer.basicData.salesPerson')"
map-options
use-input
@filter="filterWorkers"
@filter="(value, update) => filter(value, update, filterOptions)"
:rules="validate('client.salesPersonFk')"
:input-debounce="0"
>
<template #before>
<q-avatar color="orange">
<q-img
v-if="customer.salesPersonFk"
:src="`/api/Images/user/160x160/${customer.salesPersonFk}/download?access_token=${token}`"
v-if="data.salesPersonFk"
:src="`/api/Images/user/160x160/${data.salesPersonFk}/download?access_token=${token}`"
spinner-color="white"
/>
</q-avatar>
@ -214,7 +143,7 @@ function onReset() {
</div>
<div class="col">
<q-select
v-model="customer.contactChannelFk"
v-model="data.contactChannelFk"
:options="contactChannels"
option-value="id"
option-label="name"
@ -226,14 +155,10 @@ function onReset() {
/>
</div>
</div>
<div>
<q-btn :label="t('globals.save')" type="submit" color="primary" />
<q-btn :label="t('globals.reset')" type="reset" class="q-ml-sm" color="primary" flat />
</div>
</q-form>
</template>
</form-model>
</q-card>
</div>
</q-page>
</template>
<style lang="scss" scoped>

View File

@ -174,7 +174,9 @@ async function fetch() {
</q-scroll-area>
</q-drawer>
<q-page-container>
<q-page class="q-pa-md">
<router-view></router-view>
</q-page>
</q-page-container>
</template>
@ -192,5 +194,9 @@ async function fetch() {
.q-card__actions {
justify-content: center;
}
#descriptor-skeleton .q-card__actions {
justify-content: space-between;
}
}
</style>

View File

@ -29,9 +29,7 @@ function fetch() {
}
const balanceDue = computed(() => {
const [defaulter] = customer.value.defaulters;
return defaulter.amount;
return customer.value.defaulters.length && customer.value.defaulters[0].amount;
});
const balanceDueWarning = computed(() => (balanceDue.value ? 'negative' : ''));
@ -64,7 +62,6 @@ const creditWarning = computed(() => {
</script>
<template>
<q-page class="q-pa-md">
<div class="summary container">
<q-card>
<skeleton-summary v-if="!customer" />
@ -75,7 +72,10 @@ const creditWarning = computed(() => {
<q-list>
<q-item-label header class="text-h6">
{{ t('customer.summary.basicData') }}
<router-link :to="{ name: 'CustomerBasicData' }">
<router-link
:to="{ name: 'CustomerBasicData', params: { id: entityId } }"
target="_blank"
>
<q-icon name="open_in_new" />
</router-link>
</q-item-label>
@ -98,7 +98,7 @@ const creditWarning = computed(() => {
<q-item-label>{{ customer.contact }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item v-if="customer.salesPersonUser">
<q-item-section>
<q-item-label caption>{{ t('customer.summary.salesPerson') }}</q-item-label>
<q-item-label>{{ customer.salesPersonUser.name }}</q-item-label>
@ -122,7 +122,7 @@ const creditWarning = computed(() => {
<q-item-label>{{ customer.email }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item v-if="customer.contactChannel">
<q-item-section>
<q-item-label caption>{{ t('customer.summary.contactChannel') }}</q-item-label>
<q-item-label>{{ customer.contactChannel.name }}</q-item-label>
@ -153,13 +153,13 @@ const creditWarning = computed(() => {
<q-item-label>{{ customer.postcode }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item v-if="customer.province">
<q-item-section>
<q-item-label caption>{{ t('customer.summary.province') }}</q-item-label>
<q-item-label>{{ customer.province.name }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item v-if="customer.country">
<q-item-section>
<q-item-label caption>{{ t('customer.summary.country') }}</q-item-label>
<q-item-label>{{ customer.country.country }}</q-item-label>
@ -249,11 +249,7 @@ const creditWarning = computed(() => {
</q-item-section>
</q-item>
<q-item dense>
<q-checkbox
v-model="customer.hasLcr"
:label="t('customer.summary.hasLcr')"
disable
/>
<q-checkbox v-model="customer.hasLcr" :label="t('customer.summary.hasLcr')" disable />
</q-item>
<q-item dense>
<q-checkbox
@ -271,7 +267,7 @@ const creditWarning = computed(() => {
</q-item>
</q-list>
</div>
<div class="col">
<div class="col" v-if="customer.defaultAddress">
<q-list>
<q-item-label header class="text-h6">
{{ t('customer.summary.consignee') }}
@ -296,7 +292,7 @@ const creditWarning = computed(() => {
</q-item>
</q-list>
</div>
<div class="col">
<div class="col" v-if="customer.account">
<q-list>
<q-item-label header class="text-h6">
{{ t('customer.summary.webAccess') }}
@ -333,11 +329,11 @@ const creditWarning = computed(() => {
<q-item-label>{{ toCurrency(customer.mana.mana) }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item v-if="customer.claimsRatio">
<q-item-section>
<q-item-label caption>{{
t('customer.summary.priceIncreasingRate')
}}</q-item-label>
<q-item-label caption>
{{ t('customer.summary.priceIncreasingRate') }}
</q-item-label>
<q-item-label>{{ toPercentage(priceIncreasingRate) }}</q-item-label>
</q-item-section>
</q-item>
@ -347,7 +343,7 @@ const creditWarning = computed(() => {
<q-item-label>{{ toCurrency(customer.averageInvoiced.invoiced) }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item v-if="customer.claimsRatio">
<q-item-section>
<q-item-label caption>{{ t('customer.summary.claimRate') }}</q-item-label>
<q-item-label>{{ toPercentage(claimRate) }}</q-item-label>
@ -360,7 +356,7 @@ const creditWarning = computed(() => {
<q-item-label header class="text-h6">
{{ t('customer.summary.financialData') }}
</q-item-label>
<q-item>
<q-item v-if="customer.debt">
<q-item-section>
<q-item-label caption>{{ t('customer.summary.risk') }}</q-item-label>
<q-item-label :class="debtWarning">
@ -386,7 +382,7 @@ const creditWarning = computed(() => {
</q-icon>
</q-item-section>
</q-item>
<q-item>
<q-item v-if="customer.creditInsurance">
<q-item-section>
<q-item-label caption>{{ t('customer.summary.securedCredit') }}</q-item-label>
<q-item-label>{{ toCurrency(customer.creditInsurance) }}</q-item-label>
@ -408,7 +404,7 @@ const creditWarning = computed(() => {
</q-icon>
</q-item-section>
</q-item>
<q-item>
<q-item v-if="customer.defaulters">
<q-item-section>
<q-item-label caption>{{ t('customer.summary.balanceDue') }}</q-item-label>
<q-item-label :class="balanceDueWarning">
@ -433,9 +429,7 @@ const creditWarning = computed(() => {
</template>
</q-card>
</div>
</q-page>
</template>
<style lang="scss" scoped>
.container {
display: flex;
@ -491,4 +485,8 @@ const creditWarning = computed(() => {
}
}
}
.q-dialog .summary {
max-width: 1200px;
}
</style>

View File

@ -2,7 +2,7 @@
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import SmartCard from 'src/components/SmartCard.vue';
import Paginate from 'src/components/Paginate.vue';
import CustomerSummary from './Card/CustomerSummary.vue';
const router = useRouter();
@ -26,8 +26,13 @@ function showPreview(id) {
<template>
<q-page class="q-pa-md">
<smart-card url="/Clients" sort-by="id DESC" @on-navigate="navigate" auto-load>
<template #labels="{ row }">
<paginate url="/Clients" sort-by="id DESC" auto-load>
<template #body="{ rows }">
<q-card class="card" v-for="row of rows" :key="row.id">
<q-item class="q-pa-none items-start cursor-pointer q-hoverable" v-ripple clickable>
<q-item-section class="q-pa-md" @click="navigate(row.id)">
<div class="text-h6">{{ row.name }}</div>
<q-item-label caption>#{{ row.id }}</q-item-label>
<q-list>
<q-item class="q-pa-none">
<q-item-section>
@ -42,8 +47,9 @@ function showPreview(id) {
</q-item-section>
</q-item>
</q-list>
</template>
<template #actions="{ row }">
</q-item-section>
<q-separator vertical />
<q-card-actions vertical class="justify-between">
<q-btn color="grey-7" round flat icon="more_vert">
<q-tooltip>{{ t('customer.list.moreOptions') }}</q-tooltip>
<q-menu cover auto-close>
@ -73,8 +79,11 @@ function showPreview(id) {
<q-btn flat round color="grey-7" icon="vn:ticket">
<q-tooltip>{{ t('customer.list.customerOrders') }}</q-tooltip>
</q-btn>
</q-card-actions>
</q-item>
</q-card>
</template>
</smart-card>
</paginate>
</q-page>
<q-dialog v-model="preview.shown">
<customer-summary :customer-id="preview.data.customerId" />

View File

@ -79,8 +79,7 @@ async function getVideoList(expeditionId, timed) {
</script>
<template>
<q-layout view="hhh lpr ffr" class="fit">
<q-drawer show-if-above side="right" bordered>
<q-drawer show-if-above side="right">
<q-scroll-area class="fit">
<q-list bordered separator style="max-width: 318px">
<q-item v-if="lastExpedition && videoList.length">
@ -144,8 +143,6 @@ async function getVideoList(expeditionId, timed) {
</q-scroll-area>
</q-drawer>
<q-page-container>
<q-page>
<q-card>
<q-carousel animated v-model="slide" height="max-content">
<q-carousel-slide v-for="video in videoList" :key="video.value" :name="video.value">
@ -153,7 +150,4 @@ async function getVideoList(expeditionId, timed) {
</q-carousel-slide>
</q-carousel>
</q-card>
</q-page>
</q-page-container>
</q-layout>
</template>

View File

@ -147,17 +147,19 @@ function stateColor(state) {
<q-separator />
<q-list>
<!-- <q-item :to="{ name: 'TicketBasicData' }" clickable v-ripple>
<q-item :to="{ name: 'TicketBoxing' }" clickable v-ripple>
<q-item-section avatar>
<q-icon name="vn:settings" />
<q-icon name="vn:package" />
</q-item-section>
<q-item-section>{{ t('ticket.pageTitles.basicData') }}</q-item-section>
</q-item> -->
<q-item-section>{{ t('ticket.pageTitles.boxing') }}</q-item-section>
</q-item>
</q-list>
</q-scroll-area> </q-drawer
>-->
</q-scroll-area>
</q-drawer>
<q-page-container>
<q-page class="q-pa-md">
<router-view></router-view>
</q-page>
</q-page-container>
</template>
@ -181,5 +183,9 @@ function stateColor(state) {
.q-card__actions {
justify-content: center;
}
#descriptor-skeleton .q-card__actions {
justify-content: space-between;
}
}
</style>

View File

@ -2,9 +2,9 @@
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import SmartCard from 'src/components/SmartCard.vue';
import Paginate from 'src/components/Paginate.vue';
import { toDate, toCurrency } from 'src/filters/index';
// import CustomerSummary from './Card/CustomerSummary.vue';
// import TicketSummary from './Card/TicketSummary.vue';
const router = useRouter();
const { t } = useI18n();
@ -62,8 +62,13 @@ function showPreview(id) {
<template>
<q-page class="q-pa-md">
<smart-card url="/Tickets" :filter="filter" sort-by="id DESC" @on-navigate="navigate" auto-load>
<template #labels="{ row }">
<paginate url="/Tickets" :filter="filter" sort-by="id DESC" auto-load>
<template #body="{ rows }">
<q-card class="card" v-for="row of rows" :key="row.id">
<q-item class="q-pa-none items-start cursor-pointer q-hoverable" v-ripple clickable>
<q-item-section class="q-pa-md" @click="navigate(row.id)">
<div class="text-h6">{{ row.name }}</div>
<q-item-label caption>#{{ row.id }}</q-item-label>
<q-list>
<q-item class="q-pa-none">
<q-item-section>
@ -100,8 +105,9 @@ function showPreview(id) {
</q-item-section>
</q-item>
</q-list>
</template>
<template #actions="{ row }">
</q-item-section>
<q-separator vertical />
<q-card-actions vertical class="justify-between">
<q-btn color="grey-7" round flat icon="more_vert">
<q-tooltip>{{ t('customer.list.moreOptions') }}</q-tooltip>
<q-menu cover auto-close>
@ -131,8 +137,11 @@ function showPreview(id) {
<q-btn flat round color="grey-7" icon="vn:ticket">
<q-tooltip>{{ t('customer.list.customerOrders') }}</q-tooltip>
</q-btn>
</q-card-actions>
</q-item>
</q-card>
</template>
</smart-card>
</paginate>
</q-page>
<!-- <q-dialog v-model="preview.shown">
<customer-summary :customer-id="preview.data.customerId" />

View File

@ -34,15 +34,6 @@ export default {
roles: ['claimManager']
},
component: () => import('src/pages/Claim/ClaimRmaList.vue'),
},
{
name: 'ClaimCreate',
path: 'create',
meta: {
title: 'createClaim',
icon: 'vn:addperson',
},
component: () => import('src/pages/Claim/ClaimCreate.vue'),
}
]
},
@ -76,8 +67,7 @@ export default {
title: 'rma',
roles: ['claimManager']
},
component: () => import('src/pages/Claim/Card/ClaimRma.vue'),
props: { claim: true }
component: () => import('src/pages/Claim/Card/ClaimRma.vue')
}
]
},