4673 - Claim module & validations
gitea/salix-front/pipeline/head There was a failure building this commit Details

This commit is contained in:
Joan Sanchez 2022-10-17 16:23:19 +02:00
parent 165075aaef
commit cf515818c7
19 changed files with 21388 additions and 128 deletions

20577
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -99,7 +99,11 @@ module.exports = configure(function (ctx) {
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-framework
framework: {
config: {},
config: {
brand: {
primary: 'orange'
}
},
// iconSet: 'material-icons', // Quasar icon set
// lang: 'en-US', // Quasar language pack

View File

@ -21,24 +21,41 @@ async function onToggleFavoriteModule(moduleName, event) {
</script>
<template>
<q-expansion-item
:default-opened="true"
:label="t('globals.favoriteModules')"
v-if="navigation.favorites.value.length"
>
<q-list padding>
<template v-for="module in navigation.favorites.value" :key="module.title">
<div class="module" v-if="!module.children">
<q-item
clickable
v-ripple
active-class="text-orange"
:key="module.title"
:to="{ name: module.stateName }"
v-if="!module.roles || !module.roles.length || hasAny(module.roles)"
>
<q-item-section avatar :if="module.icon">
<q-icon :name="module.icon" />
<q-list padding>
<q-item-label header>{{ t('globals.favoriteModules') }}</q-item-label>
<template v-for="module in navigation.favorites.value" :key="module.title">
<div class="module" v-if="!module.children">
<q-item
clickable
v-ripple
active-class="text-orange"
:key="module.title"
:to="{ name: module.stateName }"
v-if="!module.roles || !module.roles.length || hasAny(module.roles)"
>
<q-item-section avatar :if="module.icon">
<q-icon :name="module.icon" />
</q-item-section>
<q-item-section>{{ t(`${module.name}.pageTitles.${module.title}`) }}</q-item-section>
<q-item-section side>
<div @click="onToggleFavoriteModule(module.name, $event)" class="row items-center">
<q-icon name="vn:pin_off"></q-icon>
</div>
</q-item-section>
</q-item>
</div>
<template v-if="module.children">
<q-expansion-item
class="module"
active-class="text-orange"
:label="t(`${module.name}.pageTitles.${module.title}`)"
v-if="!module.roles || !module.roles.length || hasAny(module.roles)"
:to="{ name: module.stateName }"
>
<template #header>
<q-item-section avatar>
<q-icon :name="module.icon"></q-icon>
</q-item-section>
<q-item-section>{{ t(`${module.name}.pageTitles.${module.title}`) }}</q-item-section>
<q-item-section side>
@ -46,47 +63,25 @@ async function onToggleFavoriteModule(moduleName, event) {
<q-icon name="vn:pin_off"></q-icon>
</div>
</q-item-section>
</q-item>
</div>
<template v-if="module.children">
<q-expansion-item
class="module"
active-class="text-orange"
:label="t(`${module.name}.pageTitles.${module.title}`)"
v-if="!module.roles || !module.roles.length || hasAny(module.roles)"
:to="{ name: module.stateName }"
>
<template #header>
<q-item-section avatar>
<q-icon :name="module.icon"></q-icon>
</template>
<template v-for="section in module.children" :key="section.title">
<q-item
clickable
v-ripple
active-class="text-orange"
:to="{ name: section.stateName }"
v-if="!section.roles || !section.roles.length || hasAny(section.roles)"
>
<q-item-section avatar :if="section.icon">
<q-icon :name="section.icon" />
</q-item-section>
<q-item-section>{{ t(`${module.name}.pageTitles.${module.title}`) }}</q-item-section>
<q-item-section side>
<div @click="onToggleFavoriteModule(module.name, $event)" class="row items-center">
<q-icon name="vn:pin_off"></q-icon>
</div>
</q-item-section>
</template>
<template v-for="section in module.children" :key="section.title">
<q-item
clickable
v-ripple
active-class="text-orange"
:to="{ name: section.stateName }"
v-if="!section.roles || !section.roles.length || hasAny(section.roles)"
>
<q-item-section avatar :if="section.icon">
<q-icon :name="section.icon" />
</q-item-section>
<q-item-section>{{ t(`${module.name}.pageTitles.${section.title}`) }}</q-item-section>
</q-item>
</template>
</q-expansion-item>
</template>
<q-item-section>{{ t(`${module.name}.pageTitles.${section.title}`) }}</q-item-section>
</q-item>
</template>
</q-expansion-item>
</template>
</q-list>
</q-expansion-item>
</template>
</q-list>
<q-separator />

View File

@ -10,6 +10,10 @@ const $props = defineProps({
type: String,
default: '',
},
filter: {
type: Object,
default: null,
},
autoLoad: {
type: Boolean,
default: false,
@ -54,6 +58,8 @@ async function fetch() {
skip: rowsPerPage * (page - 1),
};
Object.assign(filter, $props.filter);
if (sortBy) filter.order = sortBy;
const { data } = await axios.get($props.url, {

112
src/core/lib/validator.js Normal file
View File

@ -0,0 +1,112 @@
import * as validator from 'validator';
export const validators = {
presence: ($translate, value) => {
if (validator.isEmpty(value ? String(value) : ''))
throw new Error(_($translate, `Value can't be empty`));
},
absence: ($translate, value) => {
if (!validator.isEmpty(value))
throw new Error(_($translate, `Value should be empty`));
},
length: ($translate, value, conf) => {
let options = {
min: conf.min || conf.is,
max: conf.max || conf.is
};
let val = value ? String(value) : '';
if (!validator.isLength(val, options)) {
if (conf.is) {
throw new Error(_($translate,
`Value should be %s characters long`, [conf.is]));
} else if (conf.min && conf.max) {
throw new Error(_($translate,
`Value should have a length between %s and %s`, [conf.min, conf.max]));
} else if (conf.min) {
throw new Error(_($translate,
`Value should have at least %s characters`, [conf.min]));
} else {
throw new Error(_($translate,
`Value should have at most %s characters`, [conf.max]));
}
}
},
numericality: ($translate, value, conf) => {
if (conf.int) {
if (!validator.isInt(value))
throw new Error(_($translate, `Value should be integer`));
} else if (!validator.isNumeric(value))
throw new Error(_($translate, `Value should be a number`));
},
inclusion: ($translate, value, conf) => {
if (!validator.isIn(value, conf.in))
throw new Error(_($translate, `Invalid value`));
},
exclusion: ($translate, value, conf) => {
if (validator.isIn(value, conf.in))
throw new Error(_($translate, `Invalid value`));
},
format: ($translate, value, conf) => {
if (!validator.matches(value, conf.with))
throw new Error(_($translate, `Invalid value`));
},
custom: ($translate, value, conf) => {
if (!conf.bindedFunction(value))
throw new Error(_($translate, `Invalid value`));
}
};
/**
* Checks if value satisfies a set of validations.
*
* @param {*} value The value
* @param {Array} validations Array with validations
*/
export function validateAll($translate, value, validations) {
for (let conf of validations)
validate($translate, value, conf);
}
/**
* Checks if value satisfies a validation.
*
* @param {*} value The value
* @param {Object} conf The validation configuration
*/
export function validate($translate, value, conf) {
let validator = validators[conf.validation];
try {
let isEmpty = value == null || value === '';
if (isEmpty)
checkNull($translate, value, conf);
if (validator && (!isEmpty || conf.validation == 'presence'))
validator($translate, value, conf);
} catch (e) {
let message = conf.message ? conf.message : e.message;
throw new Error(_($translate, message));
}
}
/**
* Checks if value satisfies a blank or not null validation.
*
* @param {*} value The value
* @param {Object} conf The validation configuration
*/
export function checkNull($translate, value, conf) {
if (conf.allowBlank === false && value === '')
throw new Error(_($translate, `Value can't be blank`));
else if (conf.allowNull === false && value == null)
throw new Error(_($translate, `Value can't be null`));
}
export function _($translate, text, params = []) {
text = $translate.instant(text);
for (let i = 0; i < params.length; i++) {
text = text.replace('%s', params[i]);
}
return text;
}

View File

@ -1,5 +1,7 @@
import toLowerCase from './toLowerCase';
import toDate from './toDate';
export default {
export {
toLowerCase,
toDate
};

10
src/filters/toDate.js Normal file
View File

@ -0,0 +1,10 @@
import { useI18n } from 'vue-i18n';
export default function (value) {
if (!value) return;
const { locale } = useI18n();
const date = new Date(value);
return new Intl.DateTimeFormat(locale.value).format(date)
}

View File

@ -58,6 +58,20 @@ export default {
basicData: 'Basic Data'
}
},
claim: {
pageTitles: {
claims: 'Claims',
list: 'List',
createClaim: 'Create claim',
basicData: 'Basic Data'
},
list: {
customer: 'Customer',
assigned: 'Assigned',
created: 'Created',
state: 'State'
}
},
components: {
topbar: {},
userPanel: {

View File

@ -0,0 +1,234 @@
<script setup>
import { ref, onMounted } from 'vue';
import { useQuasar } from 'quasar';
import { useRoute } from 'vue-router';
import axios from 'axios';
import { useSession } from 'src/composables/useSession';
// import { validators } from 'src/core/lib/validator';
onMounted(() => {
fetch();
fetchWorkers();
fetchClaimStates();
fetchModels();
});
const route = useRoute();
const quasar = useQuasar();
const session = useSession();
const token = session.getToken();
const claim = ref(null);
function fetch() {
const id = route.params.id;
const filter = {
include: [
{
relation: 'client',
scope: {
fields: ['name'],
},
},
],
};
const options = { params: { filter } };
axios.get(`Claims/${id}`, options).then((response) => {
// const { data } = response;
// data.created = new Date(data.created);
claim.value = response.data;
});
}
const workers = ref([]);
function fetchWorkers() {
axios.get(`Workers`).then((response) => (workers.value = response.data));
}
function filterFn(val, update) {
console.log(val, update);
// if (val === '') {
// update(() => {
// workers.value = workersCopy.value;
// });
// return;
// }
// update(() => {
// const needle = val.toLowerCase();
// workers.value = workers.value.filter((v) => v.firstName.toLowerCase().indexOf(needle) > -1);
// });
}
const claimStates = ref([]);
function fetchClaimStates() {
axios.get(`ClaimStates`).then((response) => (claimStates.value = response.data));
}
const models = ref([]);
function fetchModels() {
axios.get(`Schemas/ModelInfo`).then((response) => (models.value = response.data));
}
function save() {
const id = route.params.id;
const formData = claim.value;
axios
.patch(`Claims/${id}`, formData)
.then(() =>
quasar.notify({
type: 'positive',
message: 'Data saved!',
})
)
.catch((error) =>
quasar.notify({
type: 'negative',
message: error.description,
})
);
}
const validations = function (message) {
return {
presence: (value) => (value !== '' && value != null) || message,
};
};
function validator(modelRule) {
if (!models.value || !modelRule) return;
const modelInfo = models.value;
const rule = modelRule.split('.');
const model = rule[0];
const property = rule[1];
const modelName = model.charAt(0).toUpperCase() + model.slice(1);
if (!modelInfo[modelName]) return;
const modelValidations = modelInfo[modelName].validations;
if (!modelValidations[property]) return;
const rules = modelValidations[property].map((validation) => {
return validations(validation.message)[validation.validation];
});
//console.log(rules);
// const rules2 = [(value) => value.length > 10 || 'Less than 10'];
return rules;
}
</script>
<template>
<q-page class="q-pa-md">
<div class="container">
<q-card class="q-pa-md">
<q-form v-if="claim" @submit="save">
<div class="row q-gutter-md q-mb-md">
<div class="col">
<q-input v-model="claim.client.name" label="Client" disable />
</div>
<div class="col">
<q-input v-model="claim.created" mask="date" :rules="['date']">
<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">
<div class="row items-center justify-end">
<q-btn v-close-popup label="Close" color="primary" flat />
</div>
</q-date>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<!-- <q-input v-model="claim.created" type="date" label="Created" />
{{ claim.created }} -->
</div>
</div>
<div class="row q-gutter-md q-mb-md">
<div class="col">
<q-select
v-model="claim.workerFk"
:options="workers"
option-value="id"
option-label="firstName"
emit-value
label="Assigned"
map-options
clearable
use-input
@filter="filterFn"
>
<template #before>
<q-avatar color="orange">
<q-img
:if="claim.workerFk"
:src="`/api/Images/user/160x160/${claim.workerFk}/download?access_token=${token}`"
spinner-color="white"
/>
</q-avatar>
</template>
<template #selected-item="scope">
{{ scope.opt.firstName }} {{ scope.opt.lastName }}
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section
>{{ scope.opt.firstName }} {{ scope.opt.lastName }}</q-item-section
>
</q-item>
</template>
</q-select>
</div>
<div class="col">
<q-select
v-model="claim.claimStateFk"
:options="claimStates"
option-value="id"
option-label="description"
emit-value
label="State"
map-options
clearable
use-input
>
</q-select>
</div>
</div>
<div class="row q-gutter-md q-mb-md">
<div class="col">
<q-input v-model="claim.packages" label="Packages" :rules="validator('claim.packages')" />
</div>
</div>
<div class="row q-gutter-md q-mb-md">
<div class="col">
<q-checkbox v-model="claim.hasToPickUp" label="Picked" />
</div>
</div>
<div>
<q-btn label="Save" type="submit" color="primary" />
<q-btn label="Reset" type="reset" color="primary" flat class="q-ml-sm" />
</div>
</q-form>
</q-card>
</div>
</q-page>
</template>
<style lang="scss" scoped>
.container {
display: flex;
justify-content: center;
}
.q-card {
max-width: 800px;
}
</style>

View File

@ -0,0 +1,180 @@
<script setup>
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
// import { useI18n } from 'vue-i18n';
import axios from 'axios';
import { useState } from 'src/composables/useState';
import { toDate } from 'src/filters/index';
const route = useRoute();
// const { t } = useI18n();
const state = useState();
onMounted(async () => {
await fetch();
});
const claim = ref({});
async function fetch() {
const entityId = route.params.id;
const filter = {
include: [
{ relation: 'client' },
{ relation: 'claimState' },
{
relation: 'claimState',
},
{
relation: 'worker',
scope: {
include: { relation: 'user' },
},
},
],
};
const options = { params: { filter } };
const { data } = await axios.get(`Claims/${entityId}`, options);
if (data) claim.value = data;
}
function stateColor(code) {
if (code === 'pending') return 'green';
if (code === 'managed') return 'orange';
if (code === 'resolved') return 'red';
}
</script>
<template>
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
<q-scroll-area class="fit text-grey-8 descriptor">
<div class="header bg-orange q-pa-sm">
<router-link :to="{ path: '/claim/list' }">
<q-btn round flat dense size="md" icon="view_list" color="white">
<q-tooltip>Claim list</q-tooltip>
</q-btn>
</router-link>
<router-link :to="{ path: '/claim/list' }">
<q-btn round flat dense size="md" icon="launch" color="white">
<q-tooltip>Claim preview</q-tooltip>
</q-btn>
</router-link>
<q-btn round flat dense size="md" icon="more_vert" color="white">
<q-tooltip>More options</q-tooltip>
<q-menu>
<q-list>
<q-item clickable v-ripple>Option 1</q-item>
<q-item clickable v-ripple>Option 2</q-item>
</q-list>
</q-menu>
</q-btn>
</div>
<div v-if="claim.client" class="q-py-sm">
<h5>{{ claim.client.name }}</h5>
<q-list>
<q-item>
<q-item-section>
<q-item-label caption>Claim ID</q-item-label>
<q-item-label>#{{ claim.id }}</q-item-label>
</q-item-section>
<q-item-section>
<q-item-label caption>Created</q-item-label>
<q-item-label>{{ toDate(claim.created) }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>Assigned</q-item-label>
<q-item-label>{{ claim.worker.user.name }}</q-item-label>
</q-item-section>
<q-item-section>
<q-item-label caption>State</q-item-label>
<q-item-label>
<q-chip :color="stateColor(claim.claimState.code)" dense>
{{ claim.claimState.description }}
</q-chip>
</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>Ticket ID</q-item-label>
<q-item-label>{{ claim.ticketFk }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
<q-card-actions>
<q-btn
size="md"
icon="vn:client"
color="primary"
:to="{ name: 'CustomerCard', params: { id: claim.clientFk } }"
>
<q-tooltip>Client preview</q-tooltip>
</q-btn>
<q-btn
size="md"
icon="vn:ticket"
color="primary"
:to="{ name: 'TicketCard', params: { id: claim.ticketFk } }"
>
<q-tooltip>Claimed ticket</q-tooltip>
</q-btn>
</q-card-actions>
</div>
<!-- Skeleton -->
<div id="descriptor-skeleton" v-if="!claim.client">
<div class="col q-pl-sm q-pa-sm">
<q-skeleton type="text" square height="45px" />
<q-skeleton type="text" square height="18px" />
<q-skeleton type="text" square height="18px" />
<q-skeleton type="text" square height="18px" />
</div>
<q-card-actions>
<q-skeleton size="40px" />
<q-skeleton size="40px" />
<q-skeleton size="40px" />
<q-skeleton size="40px" />
<q-skeleton size="40px" />
</q-card-actions>
</div>
<q-separator />
<q-list>
<q-item :to="{ name: 'ClaimBasicData' }" clickable v-ripple active-class="text-orange">
<q-item-section avatar>
<q-icon name="person" />
</q-item-section>
<q-item-section>Basic data</q-item-section>
</q-item>
</q-list>
</q-scroll-area>
</q-drawer>
<q-page-container>
<router-view></router-view>
</q-page-container>
</template>
<style lang="scss" scoped>
.descriptor {
h5 {
margin: 0 15px;
}
.header {
display: flex;
justify-content: space-between;
}
.q-card__actions {
justify-content: center;
}
#descriptor-skeleton .q-card__actions {
justify-content: space-around;
}
}
</style>

View File

@ -0,0 +1,52 @@
<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

@ -0,0 +1,99 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import SmartCard from 'src/components/SmartCard.vue';
import { toDate } from 'src/filters/index';
const router = useRouter();
const { t } = useI18n();
const filter = {
include: [
{
relation: 'client',
},
{
relation: 'claimState',
},
{
relation: 'worker',
scope: {
include: { relation: 'user' },
},
},
],
};
function stateColor(code) {
if (code === 'pending') return 'green';
if (code === 'managed') return 'orange';
if (code === 'resolved') return 'red';
}
function navigate(id) {
router.push({ path: `/claim/${id}` });
}
</script>
<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 }">
<q-list>
<q-item class="q-pa-none">
<q-item-section>
<q-item-label caption>{{ t('claim.list.customer') }}</q-item-label>
<q-item-label>{{ row.client.name }}</q-item-label>
</q-item-section>
<q-item-section>
<q-item-label caption>{{ t('claim.list.assigned') }}</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.list.created') }}</q-item-label>
<q-item-label>{{ toDate(row.created) }}</q-item-label>
</q-item-section>
<q-item-section>
<q-item-label caption>{{ t('claim.list.state') }}</q-item-label>
<q-item-label>
<q-chip :color="stateColor(row.claimState.code)" dense>{{
row.claimState.description
}}</q-chip>
</q-item-label>
</q-item-section>
</q-item>
</q-list>
</template>
<template #actions="{ row }">
<q-btn color="grey-7" round flat icon="more_vert">
<q-tooltip>{{ t('customer.list.moreOptions') }}</q-tooltip>
<q-menu cover auto-close>
<q-list>
<q-item clickable>
<q-item-section avatar>
<q-icon name="add" />
</q-item-section>
<q-item-section>Add a note</q-item-section>
</q-item>
<q-item clickable>
<q-item-section avatar>
<q-icon name="logs" />
</q-item-section>
<q-item-section>Display claim logs</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
<q-btn flat round color="orange" icon="arrow_circle_right" @click="navigate(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>
</template>
</smart-card>
</q-page>
</template>

View File

@ -0,0 +1,17 @@
<script setup>
import { useState } from 'src/composables/useState';
import LeftMenu from 'src/components/LeftMenu.vue';
const state = useState();
</script>
<template>
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
<q-scroll-area class="fit text-grey-8">
<LeftMenu />
</q-scroll-area>
</q-drawer>
<q-page-container>
<router-view></router-view>
</q-page-container>
</template>

View File

@ -1,23 +1,12 @@
<script setup>
import { ref } from 'vue';
import { useState } from 'src/composables/useState';
import LeftMenu from 'src/components/LeftMenu.vue';
const state = useState();
const miniState = ref(true);
</script>
<template>
<q-drawer
v-model="state.drawer.value"
show-if-above
:mini="miniState"
@mouseover="miniState = false"
@mouseout="miniState = true"
mini-to-overlay
:width="256"
:breakpoint="500"
>
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
<q-scroll-area class="fit text-grey-8">
<LeftMenu />
</q-scroll-area>

View File

@ -1,27 +1,16 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { ref } from 'vue';
import { useState } from 'src/composables/useState';
import LeftMenu from 'src/components/LeftMenu.vue';
import { useNavigation } from 'src/composables/useNavigation';
const { t } = useI18n();
const state = useState();
const miniState = ref(true);
const modules = useNavigation();
</script>
<template>
<q-drawer
v-model="state.drawer.value"
show-if-above
:mini="miniState"
@mouseover="miniState = false"
@mouseout="miniState = true"
mini-to-overlay
:width="256"
:breakpoint="500"
>
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
<q-scroll-area class="fit text-grey-8">
<LeftMenu />
</q-scroll-area>

View File

@ -0,0 +1,56 @@
import { RouterView } from 'vue-router';
export default {
name: 'Claim',
path: '/claim',
meta: {
roles: ['developer'],
title: 'claims',
icon: 'vn:claims'
},
component: RouterView,
redirect: { name: 'ClaimMain' },
children: [
{
name: 'ClaimMain',
path: '',
component: () => import('src/pages/Claim/ClaimMain.vue'),
redirect: { name: 'ClaimList' },
children: [
{
name: 'ClaimList',
path: 'list',
meta: {
title: 'list',
icon: 'view_list',
},
component: () => import('src/pages/Claim/ClaimList.vue'),
},
{
name: 'ClaimCreate',
path: 'create',
meta: {
title: 'createClaim',
icon: 'vn:addperson',
},
component: () => import('src/pages/Claim/ClaimCreate.vue'),
},
]
},
{
path: ':id',
component: () => import('src/pages/Claim/Card/ClaimCard.vue'),
redirect: { name: 'ClaimBasicData' },
children: [
{
name: 'ClaimBasicData',
path: 'basic-data',
meta: {
title: 'basicData'
},
component: () => import('src/pages/Claim/Card/ClaimBasicData.vue'),
}
]
},
]
};

View File

@ -38,6 +38,7 @@ export default {
]
},
{
name: 'CustomerCard',
path: ':id',
component: () => import('src/pages/Customer/Card/CustomerCard.vue'),
redirect: { name: 'CustomerBasicData' },

View File

@ -1,8 +1,8 @@
import { RouterView } from 'vue-router';
export default {
path: '/ticket',
name: 'Ticket',
path: '/ticket',
meta: {
roles: ['developer'],
title: 'tickets',
@ -12,14 +12,14 @@ export default {
redirect: { name: 'TicketMain' },
children: [
{
path: '',
name: 'TicketMain',
path: '',
component: () => import('src/pages/Ticket/TicketMain.vue'),
redirect: { name: 'TicketList' },
children: [
{
path: 'list',
name: 'TicketList',
path: 'list',
meta: {
title: 'list',
icon: 'view_list',
@ -27,8 +27,8 @@ export default {
component: () => import('src/pages/Ticket/TicketList.vue'),
},
{
path: 'create',
name: 'TicketCreate',
path: 'create',
meta: {
title: 'createTicket',
icon: 'vn:ticketAdd',
@ -40,13 +40,14 @@ export default {
]
},
{
name: 'TicketCard',
path: ':id',
component: () => import('src/pages/Ticket/Card/TicketCard.vue'),
redirect: { name: 'TicketBasicData' },
children: [
{
path: 'basic-data',
name: 'TicketBasicData',
path: 'basic-data',
meta: {
title: 'basicData'
},

View File

@ -1,5 +1,6 @@
import customer from './modules/customer';
import ticket from './modules/ticket';
import claim from './modules/claim';
const routes = [
{
@ -23,6 +24,7 @@ const routes = [
// Module routes
customer,
ticket,
claim,
],
},
{