Merge branch 'dev' into 4797-workerNotificationManager
gitea/salix-front/pipeline/head This commit looks good
Details
gitea/salix-front/pipeline/head This commit looks good
Details
This commit is contained in:
commit
9993344d70
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "salix-front",
|
||||
"version": "23.42.01",
|
||||
"version": "23.48.01",
|
||||
"description": "Salix frontend",
|
||||
"productName": "Salix",
|
||||
"author": "Verdnatura",
|
||||
|
|
|
@ -122,9 +122,24 @@ watch(formUrl, async () => {
|
|||
<QIcon name="warning" size="md" class="q-mr-md" />
|
||||
<span>{{ t('globals.changesToSave') }}</span>
|
||||
</QBanner>
|
||||
<QForm v-if="formData" @submit="save" @reset="reset" class="q-pa-md">
|
||||
<slot name="form" :data="formData" :validate="validate" :filter="filter"></slot>
|
||||
<div class="column items-center">
|
||||
<QForm
|
||||
v-if="formData"
|
||||
@submit="save"
|
||||
@reset="reset"
|
||||
class="q-pa-md"
|
||||
id="formModel"
|
||||
>
|
||||
<QCard>
|
||||
<slot
|
||||
name="form"
|
||||
:data="formData"
|
||||
:validate="validate"
|
||||
:filter="filter"
|
||||
/>
|
||||
</QCard>
|
||||
</QForm>
|
||||
</div>
|
||||
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
|
||||
<div v-if="$props.defaultActions">
|
||||
<QBtnGroup push class="q-gutter-x-sm">
|
||||
|
@ -156,3 +171,12 @@ watch(formUrl, async () => {
|
|||
color="primary"
|
||||
/>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
#formModel {
|
||||
max-width: 800px;
|
||||
width: 100%;
|
||||
}
|
||||
.q-card {
|
||||
padding: 32px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -7,6 +7,7 @@ import { useStateStore } from 'stores/useStateStore';
|
|||
import { useQuasar } from 'quasar';
|
||||
import PinnedModules from './PinnedModules.vue';
|
||||
import UserPanel from 'components/UserPanel.vue';
|
||||
import VnBreadcrumbs from './common/VnBreadcrumbs.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const session = useSession();
|
||||
|
@ -57,10 +58,7 @@ const pinnedModulesRef = ref();
|
|||
</QTooltip>
|
||||
</QBtn>
|
||||
</RouterLink>
|
||||
<QToolbarTitle shrink class="text-weight-bold" v-if="$q.screen.gt.sm">
|
||||
{{ appName }}
|
||||
<QBadge label="Beta" align="top" />
|
||||
</QToolbarTitle>
|
||||
<VnBreadcrumbs v-if="$q.screen.gt.sm" />
|
||||
<QSpace />
|
||||
<div id="searchbar" class="searchbar"></div>
|
||||
<QSpace />
|
||||
|
@ -112,6 +110,7 @@ const pinnedModulesRef = ref();
|
|||
<div id="actions-append"></div>
|
||||
</div>
|
||||
</QToolbar>
|
||||
<VnBreadcrumbs v-if="$q.screen.lt.md" class="q-ml-md" />
|
||||
</QHeader>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
<script setup>
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ref, watchEffect } from 'vue';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useCamelCase } from 'src/composables/useCamelCase';
|
||||
|
||||
const router = useRouter();
|
||||
const quasar = useQuasar();
|
||||
const { t } = useI18n();
|
||||
|
||||
let matched = ref([]);
|
||||
let breadcrumbs = ref([]);
|
||||
let root = ref(null);
|
||||
|
||||
watchEffect(() => {
|
||||
matched.value = router.currentRoute.value.matched.filter(
|
||||
(matched) => Object.keys(matched.meta).length
|
||||
);
|
||||
breadcrumbs.value.length = 0;
|
||||
|
||||
if (matched.value[0].name != 'Dashboard') {
|
||||
root.value = useCamelCase(matched.value[0].path.substring(1).toLowerCase());
|
||||
|
||||
for (let index in matched.value)
|
||||
breadcrumbs.value.push(getBreadcrumb(matched.value[index]));
|
||||
|
||||
breadcrumbs.value[breadcrumbs.value.length - 1].path = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
function getBreadcrumb(param) {
|
||||
const breadcrumb = {
|
||||
icon: param.meta.icon,
|
||||
path: param.path,
|
||||
root: root.value,
|
||||
};
|
||||
|
||||
if (quasar.screen.gt.sm) {
|
||||
breadcrumb.name = param.name;
|
||||
breadcrumb.title = useCamelCase(param.meta.title);
|
||||
}
|
||||
|
||||
return breadcrumb;
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<QBreadcrumbs v-if="breadcrumbs.length && $q.screen.gt.sm" class="q-pa-xs">
|
||||
<QBreadcrumbsEl
|
||||
v-for="(breadcrumb, index) of breadcrumbs"
|
||||
:key="index"
|
||||
:icon="breadcrumb.icon"
|
||||
:label="t(`${breadcrumb.root}.pageTitles.${breadcrumb.title}`)"
|
||||
:to="breadcrumb.path"
|
||||
/>
|
||||
</QBreadcrumbs>
|
||||
<QBreadcrumbs v-else class="q-pa-xs">
|
||||
<QBreadcrumbsEl
|
||||
v-for="(breadcrumb, index) of breadcrumbs"
|
||||
:key="index"
|
||||
:icon="breadcrumb.icon"
|
||||
:to="breadcrumb.path"
|
||||
/>
|
||||
</QBreadcrumbs>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
.q-breadcrumbs {
|
||||
&__el,
|
||||
> div {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
@media (max-width: $breakpoint-md) {
|
||||
.q-breadcrumbs {
|
||||
overflow: hidden;
|
||||
|
||||
&__el:not(:first-child):not(:last-child) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<div id="row">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scopped>
|
||||
@media screen and (max-width: 800px) {
|
||||
#row {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -105,7 +105,11 @@ async function search() {
|
|||
class="cursor-pointer"
|
||||
/>
|
||||
|
||||
<QIcon v-if="props.info" name="info" class="cursor-info">
|
||||
<QIcon
|
||||
v-if="props.info && $q.screen.gt.xs"
|
||||
name="info"
|
||||
class="cursor-info"
|
||||
>
|
||||
<QTooltip>{{ props.info }}</QTooltip>
|
||||
</QIcon>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export function useCamelCase(value) {
|
||||
return value.replace(/[-_](.)/g, (_, char) => char.toUpperCase());
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export function useFirstUpper(str) {
|
||||
return str && str.charAt(0).toUpperCase() + str.substr(1);
|
||||
}
|
|
@ -6,6 +6,7 @@ import { useI18n } from 'vue-i18n';
|
|||
import { useSession } from 'src/composables/useSession';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import FormModel from 'components/FormModel.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
@ -90,9 +91,6 @@ const statesFilter = {
|
|||
auto-load
|
||||
/>
|
||||
<FetchData url="ClaimStates" @on-fetch="setClaimStates" auto-load />
|
||||
|
||||
<div class="column items-center">
|
||||
<QCard>
|
||||
<FormModel
|
||||
:url="`Claims/${route.params.id}`"
|
||||
:url-update="`Claims/updateClaim/${route.params.id}`"
|
||||
|
@ -100,7 +98,7 @@ const statesFilter = {
|
|||
model="claim"
|
||||
>
|
||||
<template #form="{ data, validate, filter }">
|
||||
<div class="row q-gutter-md q-mb-md">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<QInput
|
||||
v-model="data.client.name"
|
||||
|
@ -122,10 +120,7 @@ const statesFilter = {
|
|||
transition-show="scale"
|
||||
transition-hide="scale"
|
||||
>
|
||||
<QDate
|
||||
v-model="data.created"
|
||||
mask="YYYY-MM-DD"
|
||||
>
|
||||
<QDate v-model="data.created" mask="YYYY-MM-DD">
|
||||
<div class="row items-center justify-end">
|
||||
<QBtn
|
||||
v-close-popup
|
||||
|
@ -140,8 +135,8 @@ const statesFilter = {
|
|||
</template>
|
||||
</QInput>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-gutter-md q-mb-md">
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<QSelect
|
||||
v-model="data.workerFk"
|
||||
|
@ -152,9 +147,7 @@ const statesFilter = {
|
|||
:label="t('claim.basicData.assignedTo')"
|
||||
map-options
|
||||
use-input
|
||||
@filter="
|
||||
(value, update) => filter(value, update, workerFilter)
|
||||
"
|
||||
@filter="(value, update) => filter(value, update, workerFilter)"
|
||||
:rules="validate('claim.claimStateFk')"
|
||||
:input-debounce="0"
|
||||
>
|
||||
|
@ -179,16 +172,14 @@ const statesFilter = {
|
|||
:label="t('claim.basicData.state')"
|
||||
map-options
|
||||
use-input
|
||||
@filter="
|
||||
(value, update) => filter(value, update, statesFilter)
|
||||
"
|
||||
@filter="(value, update) => filter(value, update, statesFilter)"
|
||||
:rules="validate('claim.claimStateFk')"
|
||||
:input-debounce="0"
|
||||
>
|
||||
</QSelect>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-gutter-md q-mb-md">
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<QInput
|
||||
v-model.number="data.packages"
|
||||
|
@ -204,24 +195,15 @@ const statesFilter = {
|
|||
:rules="validate('claim.rma')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-gutter-md q-mb-md">
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<QCheckbox
|
||||
v-model="data.hasToPickUp"
|
||||
:label="t('claim.basicData.picked')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormModel>
|
||||
</QCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.q-card {
|
||||
width: 100%;
|
||||
max-width: 60em;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -6,6 +6,7 @@ import { useI18n } from 'vue-i18n';
|
|||
import { useSession } from 'src/composables/useSession';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import FormModel from 'components/FormModel.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
@ -58,11 +59,10 @@ const filterOptions = {
|
|||
@on-fetch="(data) => (businessTypes = data)"
|
||||
auto-load
|
||||
/>
|
||||
<div class="column items-center">
|
||||
<QCard>
|
||||
|
||||
<FormModel :url="`Clients/${route.params.id}`" model="customer">
|
||||
<template #form="{ data, validate, filter }">
|
||||
<div class="row q-gutter-md q-mb-md">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<QInput
|
||||
v-model="data.socialName"
|
||||
|
@ -84,8 +84,8 @@ const filterOptions = {
|
|||
:input-debounce="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-gutter-md q-mb-md">
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<QInput
|
||||
v-model="data.contact"
|
||||
|
@ -103,8 +103,8 @@ const filterOptions = {
|
|||
clearable
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-gutter-md q-mb-md">
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<QInput
|
||||
v-model="data.phone"
|
||||
|
@ -121,8 +121,8 @@ const filterOptions = {
|
|||
clearable
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-gutter-md q-mb-md">
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<QSelect
|
||||
v-model="data.salesPersonFk"
|
||||
|
@ -133,10 +133,7 @@ const filterOptions = {
|
|||
:label="t('customer.basicData.salesPerson')"
|
||||
map-options
|
||||
use-input
|
||||
@filter="
|
||||
(value, update) =>
|
||||
filter(value, update, filterOptions)
|
||||
"
|
||||
@filter="(value, update) => filter(value, update, filterOptions)"
|
||||
:rules="validate('client.salesPersonFk')"
|
||||
:input-debounce="0"
|
||||
>
|
||||
|
@ -164,15 +161,7 @@ const filterOptions = {
|
|||
:input-debounce="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormModel>
|
||||
</QCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.q-card {
|
||||
width: 800px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -8,6 +8,7 @@ import CardDescriptor from 'components/ui/CardDescriptor.vue';
|
|||
import TicketDescriptorMenu from './TicketDescriptorMenu.vue';
|
||||
import VnLv from 'src/components/ui/VnLv.vue';
|
||||
import useCardDescription from 'src/composables/useCardDescription';
|
||||
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
|
|
|
@ -87,7 +87,7 @@ function showSmsDialog(template, customData) {
|
|||
componentProps: {
|
||||
phone: phone,
|
||||
template: template,
|
||||
locale: client.user.lang,
|
||||
locale: client?.user?.lang ?? 'default_locale',
|
||||
data: data,
|
||||
promise: sendSms,
|
||||
},
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { useRoute } from 'vue-router';
|
||||
import LeftMenu from 'components/LeftMenu.vue';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
<template>
|
||||
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
|
||||
<QScrollArea class="fit">
|
||||
<QSeparator />
|
||||
<LeftMenu source="card" />
|
||||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<QPageContainer>
|
||||
<QPage>
|
||||
<div class="q-pa-md"><RouterView></RouterView></div>
|
||||
</QPage>
|
||||
</QPageContainer>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Search customer: Buscar cliente
|
||||
You can search by customer id or name: Puedes buscar por id o nombre del cliente
|
||||
</i18n>
|
|
@ -21,13 +21,13 @@ export default {
|
|||
redirect: { name: 'CmrList' },
|
||||
children: [
|
||||
{
|
||||
path: 'cmr/list',
|
||||
path: 'cmr',
|
||||
name: 'CmrList',
|
||||
meta: {
|
||||
title: 'cmrsList',
|
||||
icon: 'fact_check',
|
||||
},
|
||||
component: () => import('src/pages/Route/Cmr/CmrList.vue')
|
||||
component: () => import('src/pages/Route/Cmr/CmrList.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -27,7 +27,7 @@ export default {
|
|||
title: 'wagonsList',
|
||||
icon: 'vn:trolley',
|
||||
},
|
||||
component: () => import('src/pages/Wagon/WagonList.vue')
|
||||
component: () => import('src/pages/Wagon/WagonList.vue'),
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
|
@ -36,7 +36,7 @@ export default {
|
|||
title: 'wagonCreate',
|
||||
icon: 'create',
|
||||
},
|
||||
component: () => import('src/pages/Wagon/WagonCreate.vue')
|
||||
component: () => import('src/pages/Wagon/WagonCreate.vue'),
|
||||
},
|
||||
{
|
||||
path: ':id/edit',
|
||||
|
@ -45,7 +45,7 @@ export default {
|
|||
title: 'wagonEdit',
|
||||
icon: 'edit',
|
||||
},
|
||||
component: () => import('src/pages/Wagon/WagonCreate.vue')
|
||||
component: () => import('src/pages/Wagon/WagonCreate.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -62,7 +62,7 @@ export default {
|
|||
title: 'typesList',
|
||||
icon: 'view_list',
|
||||
},
|
||||
component: () => import('src/pages/Wagon/Type/WagonTypeList.vue')
|
||||
component: () => import('src/pages/Wagon/Type/WagonTypeList.vue'),
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
|
@ -71,7 +71,7 @@ export default {
|
|||
title: 'typeCreate',
|
||||
icon: 'create',
|
||||
},
|
||||
component: () => import('src/pages/Wagon/Type/WagonTypeCreate.vue')
|
||||
component: () => import('src/pages/Wagon/Type/WagonTypeCreate.vue'),
|
||||
},
|
||||
{
|
||||
path: ':id/edit',
|
||||
|
@ -80,9 +80,9 @@ export default {
|
|||
title: 'typeEdit',
|
||||
icon: 'edit',
|
||||
},
|
||||
component: () => import('src/pages/Wagon/Type/WagonTypeCreate.vue')
|
||||
component: () => import('src/pages/Wagon/Type/WagonTypeCreate.vue'),
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/// <reference types="cypress" />
|
||||
describe('VnBreadcrumbs', () => {
|
||||
const firstCard = '.q-infinite-scroll > :nth-child(1)';
|
||||
const lastBreadcrumb = '.q-breadcrumbs--last > .q-breadcrumbs__el';
|
||||
beforeEach(() => {
|
||||
cy.login('developer');
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
it('should not be breadcrumbs', () => {
|
||||
cy.get('.q-breadcrumbs').should('not.exist');
|
||||
});
|
||||
|
||||
it('should get the correct breadcrumbs', () => {
|
||||
cy.visit('#/customer/list');
|
||||
cy.get('.q-breadcrumbs__el').should('have.length', 2);
|
||||
|
||||
cy.get(firstCard).click();
|
||||
cy.get(`${lastBreadcrumb} > .q-icon`).should('have.text', 'launch');
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue