0
0
Fork 0

Merge branch 'dev' into 4797-workerNotificationManager

This commit is contained in:
Alex Moreno 2023-11-09 10:04:47 +00:00
commit 9993344d70
16 changed files with 420 additions and 271 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "salix-front", "name": "salix-front",
"version": "23.42.01", "version": "23.48.01",
"description": "Salix frontend", "description": "Salix frontend",
"productName": "Salix", "productName": "Salix",
"author": "Verdnatura", "author": "Verdnatura",

View File

@ -122,9 +122,24 @@ watch(formUrl, async () => {
<QIcon name="warning" size="md" class="q-mr-md" /> <QIcon name="warning" size="md" class="q-mr-md" />
<span>{{ t('globals.changesToSave') }}</span> <span>{{ t('globals.changesToSave') }}</span>
</QBanner> </QBanner>
<QForm v-if="formData" @submit="save" @reset="reset" class="q-pa-md"> <div class="column items-center">
<slot name="form" :data="formData" :validate="validate" :filter="filter"></slot> <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> </QForm>
</div>
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()"> <Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
<div v-if="$props.defaultActions"> <div v-if="$props.defaultActions">
<QBtnGroup push class="q-gutter-x-sm"> <QBtnGroup push class="q-gutter-x-sm">
@ -156,3 +171,12 @@ watch(formUrl, async () => {
color="primary" color="primary"
/> />
</template> </template>
<style lang="scss" scoped>
#formModel {
max-width: 800px;
width: 100%;
}
.q-card {
padding: 32px;
}
</style>

View File

@ -7,6 +7,7 @@ import { useStateStore } from 'stores/useStateStore';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import PinnedModules from './PinnedModules.vue'; import PinnedModules from './PinnedModules.vue';
import UserPanel from 'components/UserPanel.vue'; import UserPanel from 'components/UserPanel.vue';
import VnBreadcrumbs from './common/VnBreadcrumbs.vue';
const { t } = useI18n(); const { t } = useI18n();
const session = useSession(); const session = useSession();
@ -57,10 +58,7 @@ const pinnedModulesRef = ref();
</QTooltip> </QTooltip>
</QBtn> </QBtn>
</RouterLink> </RouterLink>
<QToolbarTitle shrink class="text-weight-bold" v-if="$q.screen.gt.sm"> <VnBreadcrumbs v-if="$q.screen.gt.sm" />
{{ appName }}
<QBadge label="Beta" align="top" />
</QToolbarTitle>
<QSpace /> <QSpace />
<div id="searchbar" class="searchbar"></div> <div id="searchbar" class="searchbar"></div>
<QSpace /> <QSpace />
@ -112,6 +110,7 @@ const pinnedModulesRef = ref();
<div id="actions-append"></div> <div id="actions-append"></div>
</div> </div>
</QToolbar> </QToolbar>
<VnBreadcrumbs v-if="$q.screen.lt.md" class="q-ml-md" />
</QHeader> </QHeader>
</template> </template>

View File

@ -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>

View File

@ -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>

View File

@ -105,7 +105,11 @@ async function search() {
class="cursor-pointer" 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> <QTooltip>{{ props.info }}</QTooltip>
</QIcon> </QIcon>
</template> </template>

View File

@ -0,0 +1,3 @@
export function useCamelCase(value) {
return value.replace(/[-_](.)/g, (_, char) => char.toUpperCase());
}

View File

@ -0,0 +1,3 @@
export function useFirstUpper(str) {
return str && str.charAt(0).toUpperCase() + str.substr(1);
}

View File

@ -6,6 +6,7 @@ import { useI18n } from 'vue-i18n';
import { useSession } from 'src/composables/useSession'; import { useSession } from 'src/composables/useSession';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@ -90,9 +91,6 @@ const statesFilter = {
auto-load auto-load
/> />
<FetchData url="ClaimStates" @on-fetch="setClaimStates" auto-load /> <FetchData url="ClaimStates" @on-fetch="setClaimStates" auto-load />
<div class="column items-center">
<QCard>
<FormModel <FormModel
:url="`Claims/${route.params.id}`" :url="`Claims/${route.params.id}`"
:url-update="`Claims/updateClaim/${route.params.id}`" :url-update="`Claims/updateClaim/${route.params.id}`"
@ -100,7 +98,7 @@ const statesFilter = {
model="claim" model="claim"
> >
<template #form="{ data, validate, filter }"> <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"> <div class="col">
<QInput <QInput
v-model="data.client.name" v-model="data.client.name"
@ -122,10 +120,7 @@ const statesFilter = {
transition-show="scale" transition-show="scale"
transition-hide="scale" transition-hide="scale"
> >
<QDate <QDate v-model="data.created" mask="YYYY-MM-DD">
v-model="data.created"
mask="YYYY-MM-DD"
>
<div class="row items-center justify-end"> <div class="row items-center justify-end">
<QBtn <QBtn
v-close-popup v-close-popup
@ -140,8 +135,8 @@ const statesFilter = {
</template> </template>
</QInput> </QInput>
</div> </div>
</div> </VnRow>
<div class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">
<QSelect <QSelect
v-model="data.workerFk" v-model="data.workerFk"
@ -152,9 +147,7 @@ const statesFilter = {
:label="t('claim.basicData.assignedTo')" :label="t('claim.basicData.assignedTo')"
map-options map-options
use-input use-input
@filter=" @filter="(value, update) => filter(value, update, workerFilter)"
(value, update) => filter(value, update, workerFilter)
"
:rules="validate('claim.claimStateFk')" :rules="validate('claim.claimStateFk')"
:input-debounce="0" :input-debounce="0"
> >
@ -179,16 +172,14 @@ const statesFilter = {
:label="t('claim.basicData.state')" :label="t('claim.basicData.state')"
map-options map-options
use-input use-input
@filter=" @filter="(value, update) => filter(value, update, statesFilter)"
(value, update) => filter(value, update, statesFilter)
"
:rules="validate('claim.claimStateFk')" :rules="validate('claim.claimStateFk')"
:input-debounce="0" :input-debounce="0"
> >
</QSelect> </QSelect>
</div> </div>
</div> </VnRow>
<div class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">
<QInput <QInput
v-model.number="data.packages" v-model.number="data.packages"
@ -204,24 +195,15 @@ const statesFilter = {
:rules="validate('claim.rma')" :rules="validate('claim.rma')"
/> />
</div> </div>
</div> </VnRow>
<div class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">
<QCheckbox <QCheckbox
v-model="data.hasToPickUp" v-model="data.hasToPickUp"
:label="t('claim.basicData.picked')" :label="t('claim.basicData.picked')"
/> />
</div> </div>
</div> </VnRow>
</template> </template>
</FormModel> </FormModel>
</QCard>
</div>
</template> </template>
<style lang="scss" scoped>
.q-card {
width: 100%;
max-width: 60em;
}
</style>

View File

@ -6,6 +6,7 @@ import { useI18n } from 'vue-i18n';
import { useSession } from 'src/composables/useSession'; import { useSession } from 'src/composables/useSession';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@ -58,11 +59,10 @@ const filterOptions = {
@on-fetch="(data) => (businessTypes = data)" @on-fetch="(data) => (businessTypes = data)"
auto-load auto-load
/> />
<div class="column items-center">
<QCard>
<FormModel :url="`Clients/${route.params.id}`" model="customer"> <FormModel :url="`Clients/${route.params.id}`" model="customer">
<template #form="{ data, validate, filter }"> <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"> <div class="col">
<QInput <QInput
v-model="data.socialName" v-model="data.socialName"
@ -84,8 +84,8 @@ const filterOptions = {
:input-debounce="0" :input-debounce="0"
/> />
</div> </div>
</div> </VnRow>
<div class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">
<QInput <QInput
v-model="data.contact" v-model="data.contact"
@ -103,8 +103,8 @@ const filterOptions = {
clearable clearable
/> />
</div> </div>
</div> </VnRow>
<div class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">
<QInput <QInput
v-model="data.phone" v-model="data.phone"
@ -121,8 +121,8 @@ const filterOptions = {
clearable clearable
/> />
</div> </div>
</div> </VnRow>
<div class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">
<QSelect <QSelect
v-model="data.salesPersonFk" v-model="data.salesPersonFk"
@ -133,10 +133,7 @@ const filterOptions = {
:label="t('customer.basicData.salesPerson')" :label="t('customer.basicData.salesPerson')"
map-options map-options
use-input use-input
@filter=" @filter="(value, update) => filter(value, update, filterOptions)"
(value, update) =>
filter(value, update, filterOptions)
"
:rules="validate('client.salesPersonFk')" :rules="validate('client.salesPersonFk')"
:input-debounce="0" :input-debounce="0"
> >
@ -164,15 +161,7 @@ const filterOptions = {
:input-debounce="0" :input-debounce="0"
/> />
</div> </div>
</div> </VnRow>
</template> </template>
</FormModel> </FormModel>
</QCard>
</div>
</template> </template>
<style lang="scss" scoped>
.q-card {
width: 800px;
}
</style>

View File

@ -8,6 +8,7 @@ import CardDescriptor from 'components/ui/CardDescriptor.vue';
import TicketDescriptorMenu from './TicketDescriptorMenu.vue'; import TicketDescriptorMenu from './TicketDescriptorMenu.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import useCardDescription from 'src/composables/useCardDescription'; import useCardDescription from 'src/composables/useCardDescription';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
const $props = defineProps({ const $props = defineProps({
id: { id: {

View File

@ -87,7 +87,7 @@ function showSmsDialog(template, customData) {
componentProps: { componentProps: {
phone: phone, phone: phone,
template: template, template: template,
locale: client.user.lang, locale: client?.user?.lang ?? 'default_locale',
data: data, data: data,
promise: sendSms, promise: sendSms,
}, },

View File

@ -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>

View File

@ -21,13 +21,13 @@ export default {
redirect: { name: 'CmrList' }, redirect: { name: 'CmrList' },
children: [ children: [
{ {
path: 'cmr/list', path: 'cmr',
name: 'CmrList', name: 'CmrList',
meta: { meta: {
title: 'cmrsList', title: 'cmrsList',
icon: 'fact_check', icon: 'fact_check',
}, },
component: () => import('src/pages/Route/Cmr/CmrList.vue') component: () => import('src/pages/Route/Cmr/CmrList.vue'),
}, },
], ],
}, },

View File

@ -27,7 +27,7 @@ export default {
title: 'wagonsList', title: 'wagonsList',
icon: 'vn:trolley', icon: 'vn:trolley',
}, },
component: () => import('src/pages/Wagon/WagonList.vue') component: () => import('src/pages/Wagon/WagonList.vue'),
}, },
{ {
path: 'create', path: 'create',
@ -36,7 +36,7 @@ export default {
title: 'wagonCreate', title: 'wagonCreate',
icon: 'create', icon: 'create',
}, },
component: () => import('src/pages/Wagon/WagonCreate.vue') component: () => import('src/pages/Wagon/WagonCreate.vue'),
}, },
{ {
path: ':id/edit', path: ':id/edit',
@ -45,7 +45,7 @@ export default {
title: 'wagonEdit', title: 'wagonEdit',
icon: 'edit', icon: 'edit',
}, },
component: () => import('src/pages/Wagon/WagonCreate.vue') component: () => import('src/pages/Wagon/WagonCreate.vue'),
}, },
], ],
}, },
@ -62,7 +62,7 @@ export default {
title: 'typesList', title: 'typesList',
icon: 'view_list', icon: 'view_list',
}, },
component: () => import('src/pages/Wagon/Type/WagonTypeList.vue') component: () => import('src/pages/Wagon/Type/WagonTypeList.vue'),
}, },
{ {
path: 'create', path: 'create',
@ -71,7 +71,7 @@ export default {
title: 'typeCreate', title: 'typeCreate',
icon: 'create', icon: 'create',
}, },
component: () => import('src/pages/Wagon/Type/WagonTypeCreate.vue') component: () => import('src/pages/Wagon/Type/WagonTypeCreate.vue'),
}, },
{ {
path: ':id/edit', path: ':id/edit',
@ -80,9 +80,9 @@ export default {
title: 'typeEdit', title: 'typeEdit',
icon: 'edit', icon: 'edit',
}, },
component: () => import('src/pages/Wagon/Type/WagonTypeCreate.vue') component: () => import('src/pages/Wagon/Type/WagonTypeCreate.vue'),
}, },
], ],
} },
], ],
}; };

View File

@ -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');
});
});