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
|
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-framework
|
||||||
framework: {
|
framework: {
|
||||||
config: {},
|
config: {
|
||||||
|
brand: {
|
||||||
|
primary: 'orange'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// iconSet: 'material-icons', // Quasar icon set
|
// iconSet: 'material-icons', // Quasar icon set
|
||||||
// lang: 'en-US', // Quasar language pack
|
// lang: 'en-US', // Quasar language pack
|
||||||
|
|
|
@ -21,12 +21,8 @@ async function onToggleFavoriteModule(moduleName, event) {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-expansion-item
|
|
||||||
:default-opened="true"
|
|
||||||
:label="t('globals.favoriteModules')"
|
|
||||||
v-if="navigation.favorites.value.length"
|
|
||||||
>
|
|
||||||
<q-list padding>
|
<q-list padding>
|
||||||
|
<q-item-label header>{{ t('globals.favoriteModules') }}</q-item-label>
|
||||||
<template v-for="module in navigation.favorites.value" :key="module.title">
|
<template v-for="module in navigation.favorites.value" :key="module.title">
|
||||||
<div class="module" v-if="!module.children">
|
<div class="module" v-if="!module.children">
|
||||||
<q-item
|
<q-item
|
||||||
|
@ -86,7 +82,6 @@ async function onToggleFavoriteModule(moduleName, event) {
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-expansion-item>
|
|
||||||
|
|
||||||
<q-separator />
|
<q-separator />
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,10 @@ const $props = defineProps({
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
filter: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
autoLoad: {
|
autoLoad: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
@ -54,6 +58,8 @@ async function fetch() {
|
||||||
skip: rowsPerPage * (page - 1),
|
skip: rowsPerPage * (page - 1),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Object.assign(filter, $props.filter);
|
||||||
|
|
||||||
if (sortBy) filter.order = sortBy;
|
if (sortBy) filter.order = sortBy;
|
||||||
|
|
||||||
const { data } = await axios.get($props.url, {
|
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 toLowerCase from './toLowerCase';
|
||||||
|
import toDate from './toDate';
|
||||||
|
|
||||||
export default {
|
export {
|
||||||
toLowerCase,
|
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'
|
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: {
|
components: {
|
||||||
topbar: {},
|
topbar: {},
|
||||||
userPanel: {
|
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>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
|
||||||
import { useState } from 'src/composables/useState';
|
import { useState } from 'src/composables/useState';
|
||||||
import LeftMenu from 'src/components/LeftMenu.vue';
|
import LeftMenu from 'src/components/LeftMenu.vue';
|
||||||
|
|
||||||
const state = useState();
|
const state = useState();
|
||||||
const miniState = ref(true);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-drawer
|
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
|
||||||
v-model="state.drawer.value"
|
|
||||||
show-if-above
|
|
||||||
:mini="miniState"
|
|
||||||
@mouseover="miniState = false"
|
|
||||||
@mouseout="miniState = true"
|
|
||||||
mini-to-overlay
|
|
||||||
:width="256"
|
|
||||||
:breakpoint="500"
|
|
||||||
>
|
|
||||||
<q-scroll-area class="fit text-grey-8">
|
<q-scroll-area class="fit text-grey-8">
|
||||||
<LeftMenu />
|
<LeftMenu />
|
||||||
</q-scroll-area>
|
</q-scroll-area>
|
||||||
|
|
|
@ -1,27 +1,16 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { ref } from 'vue';
|
|
||||||
import { useState } from 'src/composables/useState';
|
import { useState } from 'src/composables/useState';
|
||||||
import LeftMenu from 'src/components/LeftMenu.vue';
|
import LeftMenu from 'src/components/LeftMenu.vue';
|
||||||
import { useNavigation } from 'src/composables/useNavigation';
|
import { useNavigation } from 'src/composables/useNavigation';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const state = useState();
|
const state = useState();
|
||||||
const miniState = ref(true);
|
|
||||||
const modules = useNavigation();
|
const modules = useNavigation();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-drawer
|
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
|
||||||
v-model="state.drawer.value"
|
|
||||||
show-if-above
|
|
||||||
:mini="miniState"
|
|
||||||
@mouseover="miniState = false"
|
|
||||||
@mouseout="miniState = true"
|
|
||||||
mini-to-overlay
|
|
||||||
:width="256"
|
|
||||||
:breakpoint="500"
|
|
||||||
>
|
|
||||||
<q-scroll-area class="fit text-grey-8">
|
<q-scroll-area class="fit text-grey-8">
|
||||||
<LeftMenu />
|
<LeftMenu />
|
||||||
</q-scroll-area>
|
</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',
|
path: ':id',
|
||||||
component: () => import('src/pages/Customer/Card/CustomerCard.vue'),
|
component: () => import('src/pages/Customer/Card/CustomerCard.vue'),
|
||||||
redirect: { name: 'CustomerBasicData' },
|
redirect: { name: 'CustomerBasicData' },
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { RouterView } from 'vue-router';
|
import { RouterView } from 'vue-router';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
path: '/ticket',
|
|
||||||
name: 'Ticket',
|
name: 'Ticket',
|
||||||
|
path: '/ticket',
|
||||||
meta: {
|
meta: {
|
||||||
roles: ['developer'],
|
roles: ['developer'],
|
||||||
title: 'tickets',
|
title: 'tickets',
|
||||||
|
@ -12,14 +12,14 @@ export default {
|
||||||
redirect: { name: 'TicketMain' },
|
redirect: { name: 'TicketMain' },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
|
||||||
name: 'TicketMain',
|
name: 'TicketMain',
|
||||||
|
path: '',
|
||||||
component: () => import('src/pages/Ticket/TicketMain.vue'),
|
component: () => import('src/pages/Ticket/TicketMain.vue'),
|
||||||
redirect: { name: 'TicketList' },
|
redirect: { name: 'TicketList' },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'list',
|
|
||||||
name: 'TicketList',
|
name: 'TicketList',
|
||||||
|
path: 'list',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'list',
|
title: 'list',
|
||||||
icon: 'view_list',
|
icon: 'view_list',
|
||||||
|
@ -27,8 +27,8 @@ export default {
|
||||||
component: () => import('src/pages/Ticket/TicketList.vue'),
|
component: () => import('src/pages/Ticket/TicketList.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'create',
|
|
||||||
name: 'TicketCreate',
|
name: 'TicketCreate',
|
||||||
|
path: 'create',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'createTicket',
|
title: 'createTicket',
|
||||||
icon: 'vn:ticketAdd',
|
icon: 'vn:ticketAdd',
|
||||||
|
@ -40,13 +40,14 @@ export default {
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name: 'TicketCard',
|
||||||
path: ':id',
|
path: ':id',
|
||||||
component: () => import('src/pages/Ticket/Card/TicketCard.vue'),
|
component: () => import('src/pages/Ticket/Card/TicketCard.vue'),
|
||||||
redirect: { name: 'TicketBasicData' },
|
redirect: { name: 'TicketBasicData' },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'basic-data',
|
|
||||||
name: 'TicketBasicData',
|
name: 'TicketBasicData',
|
||||||
|
path: 'basic-data',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'basicData'
|
title: 'basicData'
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import customer from './modules/customer';
|
import customer from './modules/customer';
|
||||||
import ticket from './modules/ticket';
|
import ticket from './modules/ticket';
|
||||||
|
import claim from './modules/claim';
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
|
@ -23,6 +24,7 @@ const routes = [
|
||||||
// Module routes
|
// Module routes
|
||||||
customer,
|
customer,
|
||||||
ticket,
|
ticket,
|
||||||
|
claim,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue