4673 - Claim module & validations
gitea/salix-front/pipeline/head There was a failure building this commit
Details
gitea/salix-front/pipeline/head There was a failure building this commit
Details
This commit is contained in:
parent
165075aaef
commit
cf515818c7
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
|
|
|
@ -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 />
|
||||
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
import toLowerCase from './toLowerCase';
|
||||
import toDate from './toDate';
|
||||
|
||||
export default {
|
||||
export {
|
||||
toLowerCase,
|
||||
toDate
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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: {
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'),
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
};
|
|
@ -38,6 +38,7 @@ export default {
|
|||
]
|
||||
},
|
||||
{
|
||||
name: 'CustomerCard',
|
||||
path: ':id',
|
||||
component: () => import('src/pages/Customer/Card/CustomerCard.vue'),
|
||||
redirect: { name: 'CustomerBasicData' },
|
||||
|
|
|
@ -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'
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue