Merge branch 'dev' into 6822-createEntryTransferOption
gitea/salix-front/pipeline/pr-dev This commit looks good Details

This commit is contained in:
Robert Ferrús 2025-01-24 08:31:13 +00:00
commit 2ee3ba5251
101 changed files with 4038 additions and 3643 deletions

View File

@ -1,4 +1,4 @@
module.exports = { export default {
// https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy // https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy
// This option interrupts the configuration hierarchy at this file // This option interrupts the configuration hierarchy at this file
// Remove this if you have an higher level ESLint config file (it usually happens into a monorepos) // Remove this if you have an higher level ESLint config file (it usually happens into a monorepos)
@ -58,7 +58,7 @@ module.exports = {
rules: { rules: {
'prefer-promise-reject-errors': 'off', 'prefer-promise-reject-errors': 'off',
'no-unused-vars': 'warn', 'no-unused-vars': 'warn',
"vue/no-multiple-template-root": "off" , 'vue/no-multiple-template-root': 'off',
// allow debugger during development only // allow debugger during development only
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
}, },

4
.gitignore vendored
View File

@ -29,5 +29,5 @@ yarn-error.log*
*.sln *.sln
# Cypress directories and files # Cypress directories and files
/tests/cypress/videos /test/cypress/videos
/tests/cypress/screenshots /test/cypress/screenshots

View File

@ -1,23 +1,24 @@
const fs = require('fs'); import { existsSync, readFileSync, writeFileSync } from 'fs';
const path = require('path'); import { join, resolve } from 'path';
function getCurrentBranchName(p = process.cwd()) { function getCurrentBranchName(p = process.cwd()) {
if (!fs.existsSync(p)) return false; if (!existsSync(p)) return false;
const gitHeadPath = path.join(p, '.git', 'HEAD'); const gitHeadPath = join(p, '.git', 'HEAD');
if (!fs.existsSync(gitHeadPath)) if (!existsSync(gitHeadPath)) {
return getCurrentBranchName(path.resolve(p, '..')); return getCurrentBranchName(resolve(p, '..'));
}
const headContent = fs.readFileSync(gitHeadPath, 'utf-8'); const headContent = readFileSync(gitHeadPath, 'utf-8');
return headContent.trim().split('/')[2]; return headContent.trim().split('/')[2];
} }
const branchName = getCurrentBranchName(); const branchName = getCurrentBranchName();
if (branchName) { if (branchName) {
const msgPath = `.git/COMMIT_EDITMSG`; const msgPath = '.git/COMMIT_EDITMSG';
const msg = fs.readFileSync(msgPath, 'utf-8'); const msg = readFileSync(msgPath, 'utf-8');
const reference = branchName.match(/^\d+/); const reference = branchName.match(/^\d+/);
const referenceTag = `refs #${reference}`; const referenceTag = `refs #${reference}`;
@ -26,8 +27,7 @@ if (branchName) {
if (splitedMsg.length > 1) { if (splitedMsg.length > 1) {
const finalMsg = splitedMsg[0] + ': ' + referenceTag + splitedMsg.slice(1).join(':'); const finalMsg = splitedMsg[0] + ': ' + referenceTag + splitedMsg.slice(1).join(':');
fs.writeFileSync(msgPath, finalMsg); writeFileSync(msgPath, finalMsg);
} }
} }
} }

View File

@ -1,4 +1,4 @@
module.exports = { export default {
singleQuote: true, singleQuote: true,
printWidth: 90, printWidth: 90,
tabWidth: 4, tabWidth: 4,

View File

@ -1 +1 @@
module.exports = { extends: ['@commitlint/config-conventional'] }; export default { extends: ['@commitlint/config-conventional'] };

View File

@ -1,9 +1,9 @@
const { defineConfig } = require('cypress'); import { defineConfig } from 'cypress';
// https://docs.cypress.io/app/tooling/reporters // https://docs.cypress.io/app/tooling/reporters
// https://docs.cypress.io/app/references/configuration // https://docs.cypress.io/app/references/configuration
// https://www.npmjs.com/package/cypress-mochawesome-reporter // https://www.npmjs.com/package/cypress-mochawesome-reporter
module.exports = defineConfig({ export default defineConfig({
e2e: { e2e: {
baseUrl: 'http://localhost:9000/', baseUrl: 'http://localhost:9000/',
experimentalStudio: true, experimentalStudio: true,
@ -31,7 +31,7 @@ module.exports = defineConfig({
supportFile: 'test/cypress/support/unit.js', supportFile: 'test/cypress/support/unit.js',
}, },
setupNodeEvents(on, config) { setupNodeEvents(on, config) {
require('cypress-mochawesome-reporter/plugin')(on); import('cypress-mochawesome-reporter/plugin').then((plugin) => plugin.default(on));
// implement node event listeners here // implement node event listeners here
}, },
viewportWidth: 1280, viewportWidth: 1280,

View File

@ -6,6 +6,7 @@
"author": "Verdnatura", "author": "Verdnatura",
"private": true, "private": true,
"packageManager": "pnpm@8.15.1", "packageManager": "pnpm@8.15.1",
"type": "module",
"scripts": { "scripts": {
"resetDatabase": "cd ../salix && gulp docker", "resetDatabase": "cd ../salix && gulp docker",
"lint": "eslint --ext .js,.vue ./", "lint": "eslint --ext .js,.vue ./",
@ -20,14 +21,14 @@
"addReferenceTag": "node .husky/addReferenceTag.js" "addReferenceTag": "node .husky/addReferenceTag.js"
}, },
"dependencies": { "dependencies": {
"@quasar/cli": "^2.3.0", "@quasar/cli": "^2.4.1",
"@quasar/extras": "^1.16.14", "@quasar/extras": "^1.16.16",
"axios": "^1.4.0", "axios": "^1.4.0",
"chromium": "^3.0.3", "chromium": "^3.0.3",
"croppie": "^2.6.5", "croppie": "^2.6.5",
"moment": "^2.30.1", "moment": "^2.30.1",
"pinia": "^2.1.3", "pinia": "^2.1.3",
"quasar": "^2.17.4", "quasar": "^2.17.7",
"validator": "^13.9.0", "validator": "^13.9.0",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-i18n": "^9.3.0", "vue-i18n": "^9.3.0",
@ -36,22 +37,23 @@
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^19.2.1", "@commitlint/cli": "^19.2.1",
"@commitlint/config-conventional": "^19.1.0", "@commitlint/config-conventional": "^19.1.0",
"@intlify/unplugin-vue-i18n": "^0.8.1", "@intlify/unplugin-vue-i18n": "^0.8.2",
"@pinia/testing": "^0.1.2", "@pinia/testing": "^0.1.2",
"@quasar/app-vite": "^1.11.0", "@quasar/app-vite": "^2.0.8",
"@quasar/quasar-app-extension-qcalendar": "4.0.0-beta.15", "@quasar/quasar-app-extension-qcalendar": "^4.0.2",
"@quasar/quasar-app-extension-testing-unit-vitest": "^0.4.0", "@quasar/quasar-app-extension-testing-unit-vitest": "^0.4.0",
"@vue/test-utils": "^2.4.4", "@vue/test-utils": "^2.4.4",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"cypress": "^13.6.6", "cypress": "^13.6.6",
"cypress-mochawesome-reporter": "^3.8.2", "cypress-mochawesome-reporter": "^3.8.2",
"eslint": "^8.41.0", "eslint": "^9.18.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^10.0.1",
"eslint-plugin-cypress": "^2.13.3", "eslint-plugin-cypress": "^4.1.0",
"eslint-plugin-vue": "^9.14.1", "eslint-plugin-vue": "^9.32.0",
"husky": "^8.0.0", "husky": "^8.0.0",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"prettier": "^2.8.8", "prettier": "^3.4.2",
"sass": "^1.83.4",
"vitest": "^0.34.0" "vitest": "^0.34.0"
}, },
"engines": { "engines": {
@ -61,8 +63,8 @@
"bun": ">= 1.0.25" "bun": ">= 1.0.25"
}, },
"overrides": { "overrides": {
"@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue": "^5.2.1",
"vite": "^5.1.4", "vite": "^6.0.11",
"vitest": "^0.31.1" "vitest": "^0.31.1"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,14 @@
/* eslint-disable */ /* eslint-disable */
// https://github.com/michael-ciniawsky/postcss-load-config // https://github.com/michael-ciniawsky/postcss-load-config
module.exports = { import autoprefixer from 'autoprefixer';
// Uncomment the following line if you want to support RTL CSS
// import rtlcss from 'postcss-rtlcss';
export default {
plugins: [ plugins: [
// https://github.com/postcss/autoprefixer // https://github.com/postcss/autoprefixer
require('autoprefixer')({ autoprefixer({
overrideBrowserslist: [ overrideBrowserslist: [
'last 4 Chrome versions', 'last 4 Chrome versions',
'last 4 Firefox versions', 'last 4 Firefox versions',
@ -18,10 +22,7 @@ module.exports = {
}), }),
// https://github.com/elchininet/postcss-rtlcss // https://github.com/elchininet/postcss-rtlcss
// If you want to support RTL css, then // If you want to support RTL CSS, uncomment the following line:
// 1. yarn/npm install postcss-rtlcss // rtlcss(),
// 2. optionally set quasar.config.js > framework > lang to an RTL language
// 3. uncomment the following line:
// require('postcss-rtlcss')
], ],
}; };

View File

@ -8,11 +8,11 @@
// Configuration for your app // Configuration for your app
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js
const { configure } = require('quasar/wrappers'); import { configure } from 'quasar/wrappers';
const VueI18nPlugin = require('@intlify/unplugin-vue-i18n/vite'); import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
const path = require('path'); import path from 'path';
module.exports = configure(function (/* ctx */) { export default configure(function (/* ctx */) {
return { return {
eslint: { eslint: {
// fix: true, // fix: true,

View File

@ -1,6 +1,8 @@
{ {
"@quasar/testing-unit-vitest": { "@quasar/testing-unit-vitest": {
"options": ["scripts"] "options": [
"scripts"
]
}, },
"@quasar/qcalendar": {} "@quasar/qcalendar": {}
} }

View File

@ -20,7 +20,7 @@ describe('Axios boot', () => {
describe('onRequest()', async () => { describe('onRequest()', async () => {
it('should set the "Authorization" property on the headers', async () => { it('should set the "Authorization" property on the headers', async () => {
const config = { headers: {} }; const config = { headers: {} };
localStorage.setItem('token', 'DEFAULT_TOKEN');
const resultConfig = onRequest(config); const resultConfig = onRequest(config);
expect(resultConfig).toEqual( expect(resultConfig).toEqual(

View File

@ -3,9 +3,9 @@ import { useSession } from 'src/composables/useSession';
import { Router } from 'src/router'; import { Router } from 'src/router';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import { useStateQueryStore } from 'src/stores/useStateQueryStore'; import { useStateQueryStore } from 'src/stores/useStateQueryStore';
import { getToken, isLoggedIn } from 'src/utils/session';
import { i18n } from 'src/boot/i18n'; import { i18n } from 'src/boot/i18n';
const session = useSession();
const { notify } = useNotify(); const { notify } = useNotify();
const stateQuery = useStateQueryStore(); const stateQuery = useStateQueryStore();
const baseUrl = '/api/'; const baseUrl = '/api/';
@ -13,7 +13,7 @@ axios.defaults.baseURL = baseUrl;
const axiosNoError = axios.create({ baseURL: baseUrl }); const axiosNoError = axios.create({ baseURL: baseUrl });
const onRequest = (config) => { const onRequest = (config) => {
const token = session.getToken(); const token = getToken();
if (token.length && !config.headers.Authorization) { if (token.length && !config.headers.Authorization) {
config.headers.Authorization = token; config.headers.Authorization = token;
config.headers['Accept-Language'] = i18n.global.locale.value; config.headers['Accept-Language'] = i18n.global.locale.value;
@ -37,15 +37,15 @@ const onResponse = (response) => {
return response; return response;
}; };
const onResponseError = (error) => { const onResponseError = async (error) => {
stateQuery.remove(error.config); stateQuery.remove(error.config);
if (session.isLoggedIn() && error.response?.status === 401) { if (isLoggedIn() && error.response?.status === 401) {
session.destroy(false); await useSession().destroy(false);
const hash = window.location.hash; const hash = window.location.hash;
const url = hash.slice(1); const url = hash.slice(1);
Router.push(`/login?redirect=${url}`); Router.push(`/login?redirect=${url}`);
} else if (!session.isLoggedIn()) { } else if (!isLoggedIn()) {
return Promise.reject(error); return Promise.reject(error);
} }

View File

@ -61,6 +61,7 @@ onMounted(() => stateStore.setMounted());
/> />
<QSpace /> <QSpace />
<div id="searchbar" class="searchbar"></div> <div id="searchbar" class="searchbar"></div>
<div id="searchbar-after"></div>
<QSpace /> <QSpace />
<div class="q-pl-sm q-gutter-sm row items-center no-wrap"> <div class="q-pl-sm q-gutter-sm row items-center no-wrap">
<div id="actions-prepend"></div> <div id="actions-prepend"></div>

View File

@ -181,7 +181,7 @@ onMounted(() => {
watch( watch(
() => $props.columns, () => $props.columns,
(value) => splitColumns(value), (value) => splitColumns(value),
{ immediate: true } { immediate: true },
); );
const isTableMode = computed(() => mode.value == TABLE_MODE); const isTableMode = computed(() => mode.value == TABLE_MODE);
@ -212,7 +212,7 @@ function splitColumns(columns) {
// Status column // Status column
if (splittedColumns.value.chips.length) { if (splittedColumns.value.chips.length) {
splittedColumns.value.columnChips = splittedColumns.value.chips.filter( splittedColumns.value.columnChips = splittedColumns.value.chips.filter(
(c) => !c.isId (c) => !c.isId,
); );
if (splittedColumns.value.columnChips.length) if (splittedColumns.value.columnChips.length)
splittedColumns.value.columns.unshift({ splittedColumns.value.columns.unshift({
@ -314,7 +314,19 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
show-if-above show-if-above
> >
<QScrollArea class="fit"> <QScrollArea class="fit">
<VnTableFilter :data-key="$attrs['data-key']" :columns="columns" :redirect="redirect" /> <VnTableFilter
:data-key="$attrs['data-key']"
:columns="columns"
:redirect="redirect"
>
<template
v-for="(_, slotName) in $slots"
#[slotName]="slotData"
:key="slotName"
>
<slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" />
</template>
</VnTableFilter>
</QScrollArea> </QScrollArea>
</QDrawer> </QDrawer>
<CrudModel <CrudModel
@ -472,7 +484,9 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) {
btn.isPrimary ? 'text-primary-light' : 'color-vn-text ' btn.isPrimary ? 'text-primary-light' : 'color-vn-text '
" "
:style="`visibility: ${ :style="`visibility: ${
(btn.show && btn.show(row)) ?? true ? 'visible' : 'hidden' ((btn.show && btn.show(row)) ?? true)
? 'visible'
: 'hidden'
}`" }`"
@click="btn.action(row)" @click="btn.action(row)"
/> />

View File

@ -62,5 +62,8 @@ function columnName(col) {
<span>{{ formatFn(tag.value) }}</span> <span>{{ formatFn(tag.value) }}</span>
</div> </div>
</template> </template>
<template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName">
<slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" />
</template>
</VnFilterPanel> </VnFilterPanel>
</template> </template>

View File

@ -0,0 +1,53 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore';
import { useHasContent } from 'src/composables/useHasContent';
import { watch } from 'vue';
const { t } = useI18n();
const stateStore = useStateStore();
const hasContent = useHasContent('#advanced-menu');
const $props = defineProps({
isMainSection: {
type: Boolean,
default: false,
},
});
watch(
() => $props.isMainSection,
(val) => {
if (stateStore) stateStore.rightAdvancedDrawer = val;
},
{ immediate: true }
);
</script>
<template>
<Teleport to="#searchbar-after" v-if="stateStore.isHeaderMounted()">
<QBtn
v-if="hasContent || $slots['advanced-menu']"
flat
@click="stateStore.toggleRightAdvancedDrawer()"
round
dense
icon="tune"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.advancedMenu') }}
</QTooltip>
</QBtn>
</Teleport>
<QDrawer
v-model="stateStore.rightAdvancedDrawer"
side="right"
:width="256"
:overlay="!isMainSection"
v-bind="$attrs"
>
<QScrollArea class="fit">
<div id="advanced-menu"></div>
<slot v-if="!hasContent" name="advanced-menu" />
</QScrollArea>
</QDrawer>
</template>

View File

@ -17,7 +17,7 @@ onMounted(() => {
}); });
</script> </script>
<template> <template>
<Teleport to="#actions-append" v-if="stateStore.isHeaderMounted()"> <Teleport to="#actions-prepend" v-if="stateStore.isHeaderMounted()">
<div class="row q-gutter-x-sm"> <div class="row q-gutter-x-sm">
<QBtn <QBtn
v-if="hasContent || $slots['right-panel']" v-if="hasContent || $slots['right-panel']"

View File

@ -102,7 +102,7 @@ const columns = computed(() => [
storage: 'dms', storage: 'dms',
collection: null, collection: null,
resolution: null, resolution: null,
id: prop.row.file.split('.')[0], id: Number(prop.row.file.split('.')[0]),
token: token, token: token,
class: 'rounded', class: 'rounded',
ratio: 1, ratio: 1,

View File

@ -70,6 +70,9 @@ const handleModelValue = (data) => {
<VnSelectDialog <VnSelectDialog
v-model="modelValue" v-model="modelValue"
option-filter-value="search" option-filter-value="search"
:option-label="
(opt) => (typeof modelValue === 'string' ? modelValue : showLabel(opt))
"
url="Postcodes/filter" url="Postcodes/filter"
@update:model-value="handleModelValue" @update:model-value="handleModelValue"
:use-like="false" :use-like="false"

View File

@ -15,6 +15,7 @@ import FetchData from '../FetchData.vue';
import VnSelect from './VnSelect.vue'; import VnSelect from './VnSelect.vue';
import VnUserLink from '../ui/VnUserLink.vue'; import VnUserLink from '../ui/VnUserLink.vue';
import VnPaginate from '../ui/VnPaginate.vue'; import VnPaginate from '../ui/VnPaginate.vue';
import RightMenu from './RightMenu.vue';
const stateStore = useStateStore(); const stateStore = useStateStore();
const validationsStore = useValidator(); const validationsStore = useValidator();
@ -130,7 +131,7 @@ const actionsIcon = {
}; };
const validDate = new RegExp( const validDate = new RegExp(
/^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])/.source + /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])/.source +
/T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/.source /T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/.source,
); );
function castJsonValue(value) { function castJsonValue(value) {
@ -192,7 +193,7 @@ function getLogTree(data) {
user: log.user, user: log.user,
userFk: log.userFk, userFk: log.userFk,
logs: [], logs: [],
}) }),
); );
} }
// Model // Model
@ -210,7 +211,7 @@ function getLogTree(data) {
id: log.changedModelId, id: log.changedModelId,
showValue: log.changedModelValue, showValue: log.changedModelValue,
logs: [], logs: [],
}) }),
); );
nLogs = 0; nLogs = 0;
} }
@ -282,7 +283,7 @@ function setDate(type) {
to = date.adjustDate( to = date.adjustDate(
to, to,
{ hour: 21, minute: 59, second: 59, millisecond: 999 }, { hour: 21, minute: 59, second: 59, millisecond: 999 },
true true,
); );
switch (type) { switch (type) {
@ -365,7 +366,7 @@ async function clearFilter() {
dateTo.value = undefined; dateTo.value = undefined;
userRadio.value = undefined; userRadio.value = undefined;
Object.keys(checkboxOptions.value).forEach( Object.keys(checkboxOptions.value).forEach(
(opt) => (checkboxOptions.value[opt].selected = false) (opt) => (checkboxOptions.value[opt].selected = false),
); );
await applyFilter(); await applyFilter();
} }
@ -378,7 +379,7 @@ watch(
() => router.currentRoute.value.params.id, () => router.currentRoute.value.params.id,
() => { () => {
applyFilter(); applyFilter();
} },
); );
</script> </script>
<template> <template>
@ -391,7 +392,7 @@ watch(
const changedModel = item.changedModel; const changedModel = item.changedModel;
return { return {
locale: useCapitalize( locale: useCapitalize(
validations[changedModel]?.locale?.name ?? changedModel validations[changedModel]?.locale?.name ?? changedModel,
), ),
value: changedModel, value: changedModel,
}; };
@ -507,7 +508,7 @@ watch(
:title=" :title="
date.formatDate( date.formatDate(
log.creationDate, log.creationDate,
'DD/MM/YYYY hh:mm:ss' 'DD/MM/YYYY hh:mm:ss',
) ?? `date:'dd/MM/yyyy HH:mm:ss'` ) ?? `date:'dd/MM/yyyy HH:mm:ss'`
" "
> >
@ -577,7 +578,7 @@ watch(
t( t(
`actions.${ `actions.${
actionsText[log.action] actionsText[log.action]
}` }`,
) )
" "
/> />
@ -677,7 +678,8 @@ watch(
</div> </div>
</template> </template>
</VnPaginate> </VnPaginate>
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> <RightMenu>
<template #right-panel>
<QList dense> <QList dense>
<QSeparator /> <QSeparator />
<QItem class="q-mt-sm"> <QItem class="q-mt-sm">
@ -732,14 +734,17 @@ watch(
v-model="userSelect" v-model="userSelect"
option-label="name" option-label="name"
option-value="id" option-value="id"
:url="`${model}Logs/${$route.params.id}/editors`" :url="`${model}Logs/${route.params.id}/editors`"
:fields="['id', 'nickname', 'name', 'image']" :fields="['id', 'nickname', 'name', 'image']"
sort-by="nickname" sort-by="nickname"
@update:model-value="selectFilter('userSelect')" @update:model-value="selectFilter('userSelect')"
hide-selected hide-selected
> >
<template #option="{ opt, itemProps }"> <template #option="{ opt, itemProps }">
<QItem v-bind="itemProps" class="q-pa-xs row items-center"> <QItem
v-bind="itemProps"
class="q-pa-xs row items-center"
>
<QItemSection class="col-3 items-center"> <QItemSection class="col-3 items-center">
<VnAvatar :worker-id="opt.id" /> <VnAvatar :worker-id="opt.id" />
</QItemSection> </QItemSection>
@ -809,7 +814,8 @@ watch(
/> />
</QItem> </QItem>
</QList> </QList>
</Teleport> </template>
</RightMenu>
<QDialog v-model="dateFromDialog"> <QDialog v-model="dateFromDialog">
<QDate <QDate
:years-in-month-view="false" :years-in-month-view="false"

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import RightMenu from './RightMenu.vue'; import RightAdvancedMenu from './RightAdvancedMenu.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue';
import VnTableFilter from '../VnTable/VnTableFilter.vue'; import VnTableFilter from '../VnTable/VnTableFilter.vue';
import { onBeforeMount, onMounted, onUnmounted, computed, ref } from 'vue'; import { onBeforeMount, onMounted, onUnmounted, computed, ref } from 'vue';
@ -54,6 +54,7 @@ const sectionValue = computed(() => $props.section ?? $props.dataKey);
const isMainSection = ref(false); const isMainSection = ref(false);
const searchbarId = 'section-searchbar'; const searchbarId = 'section-searchbar';
const advancedMenuSlot = 'advanced-menu';
const hasContent = useHasContent(`#${searchbarId}`); const hasContent = useHasContent(`#${searchbarId}`);
onBeforeMount(() => { onBeforeMount(() => {
@ -93,9 +94,9 @@ function checkIsMain() {
/> />
<div :id="searchbarId"></div> <div :id="searchbarId"></div>
</slot> </slot>
<RightMenu> <RightAdvancedMenu :is-main-section="isMainSection">
<template #right-panel v-if="$slots['rightMenu'] || rightFilter"> <template #advanced-menu v-if="$slots[advancedMenuSlot] || rightFilter">
<slot name="rightMenu"> <slot :name="advancedMenuSlot">
<VnTableFilter <VnTableFilter
v-if="rightFilter && columns" v-if="rightFilter && columns"
:data-key="dataKey" :data-key="dataKey"
@ -104,7 +105,7 @@ function checkIsMain() {
/> />
</slot> </slot>
</template> </template>
</RightMenu> </RightAdvancedMenu>
<slot name="body" v-if="isMainSection" /> <slot name="body" v-if="isMainSection" />
<RouterView v-else /> <RouterView v-else />
</template> </template>

View File

@ -27,7 +27,7 @@ const $props = defineProps({
default: () => [], default: () => [],
}, },
optionLabel: { optionLabel: {
type: [String], type: [String, Function],
default: 'name', default: 'name',
}, },
optionValue: { optionValue: {

View File

@ -0,0 +1,146 @@
import { createWrapper, axios } from 'app/test/vitest/helper';
import { vi, afterEach, beforeEach, beforeAll, describe, expect, it } from 'vitest';
import VnDms from 'src/components/common/VnDms.vue';
class MockFormData {
constructor() {
this.entries = {};
}
append(key, value) {
if (!key) {
throw new Error('Key is required for FormData.append');
}
this.entries[key] = value;
}
get(key) {
return this.entries[key] || null;
}
getAll() {
return this.entries;
}
}
global.FormData = MockFormData;
describe('VnDms', () => {
let wrapper;
let vm;
let postMock;
const postResponseMock = { data: { success: true } };
const data = {
hasFile: true,
hasFileAttached: true,
reference: 'DMS-test',
warehouseFk: 1,
companyFk: 2,
dmsTypeFk: 3,
description: 'This is a test description',
files: { name: 'example.txt', content: new Blob(['file content'], { type: 'text/plain' })},
};
const expectedBody = {
hasFile: true,
hasFileAttached: true,
reference: 'DMS-test',
warehouseId: 1,
companyId: 2,
dmsTypeId: 3,
description: 'This is a test description',
};
beforeAll(() => {
wrapper = createWrapper(VnDms, {
propsData: {
url: '/test',
formInitialData: { id: 1, reference: 'test' },
model: 'Worker',
}
});
wrapper = wrapper.wrapper;
vm = wrapper.vm;
vi.spyOn(vm, '$emit');
});
beforeEach(() => {
postMock = vi.spyOn(axios, 'post').mockResolvedValue(postResponseMock);
vm.dms = data;
});
afterEach(() => {
vi.clearAllMocks();
});
describe('mapperDms', () => {
it('should map DMS data correctly and add file to FormData', () => {
const [formData, params] = vm.mapperDms(data);
expect(formData.get('example.txt')).toBe(data.files);
expect(expectedBody).toEqual(params.params);
});
it('should map DMS data correctly without file', () => {
delete data.files;
const [formData, params] = vm.mapperDms(data);
expect(formData.getAll()).toEqual({});
expect(expectedBody).toEqual(params.params);
});
});
describe('getUrl', () => {
it('should returns prop url when is set', async () => {
expect(vm.getUrl()).toBe('/test');
});
it('should returns url dms/"props.formInitialData.id"/updateFile when prop url is null', async () => {
await wrapper.setProps({ url: null });
expect(vm.getUrl()).toBe('dms/1/updateFile');
});
it('should returns url "props.model"/"route.params.id"/uploadFile when formInitialData is null', async () => {
await wrapper.setProps({ formInitialData: null });
vm.route.params.id = '123';
expect(vm.getUrl()).toBe('Worker/123/uploadFile');
});
});
describe('save', () => {
it('should save data correctly', async () => {
await vm.save();
expect(postMock).toHaveBeenCalledWith(vm.getUrl(), expect.any(FormData), { params: expectedBody });
expect(wrapper.emitted('onDataSaved')).toBeTruthy();
});
});
describe('defaultData', () => {
it('should set dms with formInitialData', async () => {
const testData = {
hasFile: false,
hasFileAttached: false,
reference: 'defaultData-test',
warehouseFk: 2,
companyFk: 3,
dmsTypeFk: 2,
description: 'This is a test description'
}
await wrapper.setProps({ formInitialData: testData });
vm.defaultData();
expect(vm.dms).toEqual(testData);
});
it('should add reference with "route.params.id" to dms if formInitialData is null', async () => {
await wrapper.setProps({ formInitialData: null });
vm.route.params.id= '111';
vm.defaultData();
expect(vm.dms.reference).toBe('111');
});
});
});

View File

@ -0,0 +1,91 @@
import { createWrapper } from 'app/test/vitest/helper';
import { vi, describe, expect, it } from 'vitest';
import VnInput from 'src/components/common/VnInput.vue';
describe('VnInput', () => {
let vm;
let wrapper;
let input;
function generateWrapper(value, isOutlined, emptyToNull, insertable) {
wrapper = createWrapper(VnInput, {
props: {
modelValue: value,
isOutlined, emptyToNull, insertable,
maxlength: 101
},
attrs: {
label: 'test',
required: true,
maxlength: 101,
maxLength: 10,
'max-length':20
},
});
wrapper = wrapper.wrapper;
vm = wrapper.vm;
input = wrapper.find('[data-cy="test_input"]');
};
describe('value', () => {
it('should emit update:modelValue when value changes', async () => {
generateWrapper('12345', false, false, true)
await input.setValue('123');
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
expect(wrapper.emitted('update:modelValue')[0]).toEqual(['123']);
});
it('should emit update:modelValue with null when input is empty', async () => {
generateWrapper('12345', false, true, true);
await input.setValue('');
expect(wrapper.emitted('update:modelValue')[0]).toEqual([null]);
});
});
describe('styleAttrs', () => {
it('should return empty styleAttrs when isOutlined is false', async () => {
generateWrapper('123', false, false, false);
expect(vm.styleAttrs).toEqual({});
});
it('should set styleAttrs when isOutlined is true', async () => {
generateWrapper('123', true, false, false);
expect(vm.styleAttrs.outlined).toBe(true);
});
});
describe('handleKeydown', () => {
it('should do nothing when "Backspace" key is pressed', async () => {
generateWrapper('12345', false, false, true);
await input.trigger('keydown', { key: 'Backspace' });
expect(wrapper.emitted('update:modelValue')).toBeUndefined();
const spyhandler = vi.spyOn(vm, 'handleInsertMode');
expect(spyhandler).not.toHaveBeenCalled();
});
/*
TODO: #8399 REDMINE
*/
it.skip('handleKeydown respects insertable behavior', async () => {
const expectedValue = '12345';
generateWrapper('1234', false, false, true);
vm.focus()
await input.trigger('keydown', { key: '5' });
await vm.$nextTick();
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
expect(wrapper.emitted('update:modelValue')[0]).toEqual([expectedValue ]);
expect(vm.value).toBe( expectedValue);
});
});
describe('focus', () => {
it('should call focus method when input is focused', async () => {
generateWrapper('123', false, false, true);
const focusSpy = vi.spyOn(input.element, 'focus');
vm.focus();
expect(focusSpy).toHaveBeenCalled();
});
});
});

View File

@ -41,7 +41,7 @@ const card = toRef(props, 'item');
</div> </div>
</div> </div>
<div class="content"> <div class="content">
<span class="link"> <span class="link" @click.stop>
{{ card.name }} {{ card.name }}
<ItemDescriptorProxy :id="card.id" /> <ItemDescriptorProxy :id="card.id" />
</span> </span>

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { computed } from 'vue'; import { computed } from 'vue';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import '@quasar/quasar-ui-qcalendar/src/QCalendarVariables.sass'; import '@quasar/quasar-ui-qcalendar/src/QCalendarVariables.scss';
const $props = defineProps({ const $props = defineProps({
bordered: { bordered: {

View File

@ -190,7 +190,10 @@ const getLocale = (label) => {
const globalLocale = `globals.params.${param}`; const globalLocale = `globals.params.${param}`;
if (te(globalLocale)) return t(globalLocale); if (te(globalLocale)) return t(globalLocale);
else if (te(t(`params.${param}`))); else if (te(t(`params.${param}`)));
else return t(`${route.meta.moduleName.toLowerCase()}.params.${param}`); else {
const camelCaseModuleName = route.meta.moduleName.charAt(0).toLowerCase() + route.meta.moduleName.slice(1);
return t(`${camelCaseModuleName}.params.${param}`);
}
}; };
</script> </script>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { defineProps, ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
const { t } = useI18n(); const { t } = useI18n();

View File

@ -0,0 +1,71 @@
import { vi, describe, expect, it, beforeEach, afterEach } from 'vitest';
import { createWrapper } from 'app/test/vitest/helper';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
describe('VnSearchbar', () => {
let vm;
let wrapper;
let applyFilterSpy;
const searchText = 'Bolas de madera';
const userParams = {staticKey: 'staticValue'};
beforeEach(async () => {
wrapper = createWrapper(VnSearchbar, {
propsData: {
dataKey: 'testKey',
filter: null,
whereFilter: null,
searchRemoveParams: true,
},
});
wrapper = wrapper.wrapper;
vm = wrapper.vm;
vm.searchText = searchText;
vm.arrayData.store.userParams = userParams;
applyFilterSpy = vi.spyOn(vm.arrayData, 'applyFilter').mockImplementation(() => {});
});
afterEach(() => {
vi.clearAllMocks();
});
it('search resets pagination and applies filter', async () => {
const resetPaginationSpy = vi.spyOn(vm.arrayData, 'resetPagination').mockImplementation(() => {});
await vm.search();
expect(resetPaginationSpy).toHaveBeenCalled();
expect(applyFilterSpy).toHaveBeenCalledWith({
params: { search: searchText },
});
});
it('search includes static params if searchRemoveParams is false', async () => {
wrapper.setProps({ searchRemoveParams: false });
await vm.$nextTick();
await vm.search();
expect(applyFilterSpy).toHaveBeenCalledWith({
params: { staticKey: 'staticValue', search: searchText },
filter: {skip: 0},
});
});
it('updates store when dataKey changes', async () => {
expect(vm.store.userParams).toEqual(userParams);
wrapper.setProps({ dataKey: 'newTestKey' });
await vm.$nextTick();
expect(vm.store.userParams).toEqual({});
});
it('computes the "to" property correctly for redirection', () => {
vm.arrayData.store.searchUrl = 'searchParam';
vm.arrayData.store.currentFilter = { category: 'plants' };
const expectedQuery = JSON.stringify({
...vm.arrayData.store.currentFilter,
search: searchText,
});
expect(vm.to.query.searchParam).toBe(expectedQuery);
});
});

View File

@ -170,10 +170,9 @@ export function useArrayData(key, userOptions) {
async function addOrder(field, direction = 'ASC') { async function addOrder(field, direction = 'ASC') {
const newOrder = field + ' ' + direction; const newOrder = field + ' ' + direction;
let order = store.order || []; const order = toArray(store.order);
if (typeof order == 'string') order = [order];
let index = order.findIndex((o) => o.split(' ')[0] === field); let index = getOrderIndex(order, field);
if (index > -1) { if (index > -1) {
order[index] = newOrder; order[index] = newOrder;
} else { } else {
@ -190,16 +189,23 @@ export function useArrayData(key, userOptions) {
} }
async function deleteOrder(field) { async function deleteOrder(field) {
let order = store.order ?? []; const order = toArray(store.order);
if (typeof order == 'string') order = [order]; const index = getOrderIndex(order, field);
const index = order.findIndex((o) => o.split(' ')[0] === field);
if (index > -1) order.splice(index, 1); if (index > -1) order.splice(index, 1);
store.order = order; store.order = order;
fetch({}); fetch({});
} }
function getOrderIndex(order, field) {
return order.findIndex((o) => o.split(' ')[0] === field);
}
function toArray(str = []) {
if (Array.isArray(str)) return str;
if (typeof str === 'string') return str.split(',').map((item) => item.trim());
}
function sanitizerParams(params, exprBuilder) { function sanitizerParams(params, exprBuilder) {
for (const param in params) { for (const param in params) {
if (params[param] === '' || params[param] === null) { if (params[param] === '' || params[param] === null) {
@ -290,8 +296,7 @@ export function useArrayData(key, userOptions) {
Object.assign(params, store.userParams); Object.assign(params, store.userParams);
if (params.filter) params.filter.skip = store.skip; if (params.filter) params.filter.skip = store.skip;
if (store?.order && typeof store?.order == 'string') store.order = [store.order]; if (store.order) params.filter.order = toArray(store.order);
if (store.order?.length) params.filter.order = [...store.order];
else delete params.filter.order; else delete params.filter.order;
return { filter, params, limit: filter.limit }; return { filter, params, limit: filter.limit };

View File

@ -5,7 +5,7 @@ export function useHasContent(selector) {
const hasContent = ref(); const hasContent = ref();
onMounted(() => { onMounted(() => {
container.value = document.querySelector(selector); container.value = document?.querySelector(selector);
if (!container.value) return; if (!container.value) return;
const observer = new MutationObserver(() => { const observer = new MutationObserver(() => {

View File

@ -6,6 +6,7 @@ import axios from 'axios';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import useNotify from './useNotify'; import useNotify from './useNotify';
import { useTokenConfig } from './useTokenConfig'; import { useTokenConfig } from './useTokenConfig';
import { getToken, getTokenMultimedia } from 'src/utils/session';
const TOKEN_MULTIMEDIA = 'tokenMultimedia'; const TOKEN_MULTIMEDIA = 'tokenMultimedia';
const TOKEN = 'token'; const TOKEN = 'token';
@ -15,19 +16,6 @@ export function useSession() {
let isCheckingToken = false; let isCheckingToken = false;
let intervalId = null; let intervalId = null;
function getToken() {
const localToken = localStorage.getItem(TOKEN);
const sessionToken = sessionStorage.getItem(TOKEN);
return localToken || sessionToken || '';
}
function getTokenMultimedia() {
const localTokenMultimedia = localStorage.getItem(TOKEN_MULTIMEDIA);
const sessionTokenMultimedia = sessionStorage.getItem(TOKEN_MULTIMEDIA);
return localTokenMultimedia || sessionTokenMultimedia || '';
}
function setSession(data) { function setSession(data) {
let keepLogin = data.keepLogin; let keepLogin = data.keepLogin;
const storage = keepLogin ? localStorage : sessionStorage; const storage = keepLogin ? localStorage : sessionStorage;

View File

@ -1,6 +1,6 @@
// app global css in SCSS form // app global css in SCSS form
@import './icons.scss'; @import './icons.scss';
@import '@quasar/quasar-ui-qcalendar/src/QCalendarMonth.sass'; @import '@quasar/quasar-ui-qcalendar/src/QCalendarMonth.scss';
body.body--light { body.body--light {
--vn-header-color: #cecece; --vn-header-color: #cecece;
@ -312,11 +312,11 @@ input::-webkit-inner-spin-button {
} }
.q-item > .q-item__section:has(.q-checkbox) { .q-item > .q-item__section:has(.q-checkbox) {
max-width: min-content; max-width: fit-content;
} }
.row > .column:has(.q-checkbox) { .row > .column:has(.q-checkbox) {
max-width: min-content; max-width: fit-content;
} }
.q-field__inner { .q-field__inner {
.q-field__control { .q-field__control {

View File

@ -5,9 +5,11 @@ globals:
quantity: Quantity quantity: Quantity
language: Language language: Language
entity: Entity entity: Entity
preview: Preview
user: User user: User
details: Details details: Details
collapseMenu: Collapse left menu collapseMenu: Collapse lateral menu
advancedMenu: Advanced menu
backToDashboard: Return to dashboard backToDashboard: Return to dashboard
notifications: Notifications notifications: Notifications
userPanel: User panel userPanel: User panel
@ -726,62 +728,6 @@ travel:
destination: Destination destination: Destination
thermograph: Thermograph thermograph: Thermograph
travelFileDescription: 'Travel id { travelId }' travelFileDescription: 'Travel id { travelId }'
item:
descriptor:
buyer: Buyer
color: Color
category: Category
available: Available
warehouseText: 'Calculated on the warehouse of { warehouseName }'
itemDiary: Item diary
list:
id: Identifier
stems: Stems
category: Category
typeName: Type
isActive: Active
userName: Buyer
weightByPiece: Weight/Piece
stemMultiplier: Multiplier
fixedPrice:
itemFk: Item ID
groupingPrice: Grouping price
packingPrice: Packing price
hasMinPrice: Has min price
minPrice: Min price
started: Started
ended: Ended
create:
priority: Priority
buyRequest:
requester: Requester
requested: Requested
attender: Atender
achieved: Achieved
concept: Concept
summary:
otherData: Other data
tax: Tax
botanical: Botanical
barcode: Barcode
completeName: Complete name
family: Familiy
stems: Stems
multiplier: Multiplier
buyer: Buyer
doPhoto: Do photo
intrastatCode: Intrastat code
ref: Reference
relevance: Relevance
weight: Weight (gram)/stem
units: Units/box
expense: Expense
generic: Generic
recycledPlastic: Recycled plastic
nonRecycledPlastic: Non recycled plastic
minSalesQuantity: Min sales quantity
genus: Genus
specie: Specie
components: components:
topbar: {} topbar: {}
itemsFilterPanel: itemsFilterPanel:

View File

@ -5,9 +5,11 @@ globals:
language: Idioma language: Idioma
quantity: Cantidad quantity: Cantidad
entity: Entidad entity: Entidad
preview: Vista previa
user: Usuario user: Usuario
details: Detalles details: Detalles
collapseMenu: Contraer menú lateral collapseMenu: Contraer menú lateral
advancedMenu: Menú avanzado
backToDashboard: Volver al tablón backToDashboard: Volver al tablón
notifications: Notificaciones notifications: Notificaciones
userPanel: Panel de usuario userPanel: Panel de usuario
@ -722,62 +724,6 @@ travel:
destination: Destino destination: Destino
thermograph: Termógrafo thermograph: Termógrafo
travelFileDescription: 'Id envío { travelId }' travelFileDescription: 'Id envío { travelId }'
item:
descriptor:
buyer: Comprador
color: Color
category: Categoría
available: Disponible
warehouseText: 'Calculado sobre el almacén de { warehouseName }'
itemDiary: Registro de compra-venta
list:
id: Identificador
stems: Tallos
category: Reino
typeName: Tipo
isActive: Activo
weightByPiece: Peso (gramos)/tallo
userName: Comprador
stemMultiplier: Multiplicador
fixedPrice:
itemFk: ID Artículo
groupingPrice: Precio grouping
packingPrice: Precio packing
hasMinPrice: Tiene precio mínimo
minPrice: Precio min
started: Inicio
ended: Fin
create:
priority: Prioridad
summary:
otherData: Otros datos
tax: IVA
botanical: Botánico
barcode: Código de barras
completeName: Nombre completo
family: Familia
stems: Tallos
multiplier: Multiplicador
buyer: Comprador
doPhoto: Hacer foto
intrastatCode: Código intrastat
ref: Referencia
relevance: Relevancia
weight: Peso (gramos)/tallo
units: Unidades/caja
expense: Gasto
generic: Genérico
recycledPlastic: Plástico reciclado
nonRecycledPlastic: Plástico no reciclado
minSalesQuantity: Cantidad mínima de venta
genus: Genus
specie: Specie
buyRequest:
requester: Solicitante
requested: Solicitado
attender: Comprador
achieved: Conseguido
concept: Concepto
components: components:
topbar: {} topbar: {}
itemsFilterPanel: itemsFilterPanel:

View File

@ -4,7 +4,6 @@ import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import axios from 'axios'; import axios from 'axios';
import { useStateStore } from 'src/stores/useStateStore';
import { toDate, toPercentage, toCurrency } from 'filters/index'; import { toDate, toPercentage, toCurrency } from 'filters/index';
import { tMobile } from 'src/composables/tMobile'; import { tMobile } from 'src/composables/tMobile';
import CrudModel from 'src/components/CrudModel.vue'; import CrudModel from 'src/components/CrudModel.vue';
@ -13,11 +12,11 @@ import VnSelect from 'src/components/common/VnSelect.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue'; import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import RightMenu from 'src/components/common/RightMenu.vue';
const { t } = useI18n(); const { t } = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
const route = useRoute(); const route = useRoute();
const stateStore = computed(() => useStateStore());
const claim = ref(null); const claim = ref(null);
const claimRef = ref(); const claimRef = ref();
const claimId = route.params.id; const claimId = route.params.id;
@ -201,7 +200,8 @@ async function post(query, params) {
auto-load auto-load
@on-fetch="(data) => (destinationTypes = data)" @on-fetch="(data) => (destinationTypes = data)"
/> />
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted() && claim"> <RightMenu v-if="claim">
<template #right-panel>
<QCard class="totalClaim q-my-md q-pa-sm no-box-shadow"> <QCard class="totalClaim q-my-md q-pa-sm no-box-shadow">
{{ `${t('Total claimed')}: ${toCurrency(totalClaimed)}` }} {{ `${t('Total claimed')}: ${toCurrency(totalClaimed)}` }}
</QCard> </QCard>
@ -238,7 +238,10 @@ async function post(query, params) {
<QCard class="q-mb-md q-pa-sm no-box-shadow" style="position: static"> <QCard class="q-mb-md q-pa-sm no-box-shadow" style="position: static">
<QInput <QInput
:disable=" :disable="
!(claim.responsibility >= Math.ceil(DEFAULT_MAX_RESPONSABILITY) / 2) !(
claim.responsibility >=
Math.ceil(DEFAULT_MAX_RESPONSABILITY) / 2
)
" "
:label="t('confirmGreuges')" :label="t('confirmGreuges')"
class="q-field__native text-grey-2" class="q-field__native text-grey-2"
@ -251,8 +254,8 @@ async function post(query, params) {
v-model="multiplicatorValue" v-model="multiplicatorValue"
/> />
</QCard> </QCard>
</Teleport> </template>
<Teleport to="#st-data" v-if="stateStore.isSubToolbarShown()"> </Teleport> </RightMenu>
<CrudModel <CrudModel
v-if="claim" v-if="claim"
data-key="ClaimEnds" data-key="ClaimEnds"

View File

@ -134,7 +134,7 @@ const STATE_COLOR = {
order: ['cs.priority ASC', 'created ASC'], order: ['cs.priority ASC', 'created ASC'],
}" }"
> >
<template #rightMenu> <template #advanced-menu>
<ClaimFilter data-key="ClaimList" ref="claimFilterRef" /> <ClaimFilter data-key="ClaimList" ref="claimFilterRef" />
</template> </template>
<template #body> <template #body>

View File

@ -1,25 +1,12 @@
<script setup> <script setup>
import { computed } from 'vue'; import VnCardBeta from 'components/common/VnCardBeta.vue';
import { useRoute } from 'vue-router';
import VnCard from 'components/common/VnCard.vue';
import CustomerDescriptor from './CustomerDescriptor.vue'; import CustomerDescriptor from './CustomerDescriptor.vue';
import CustomerFilter from '../CustomerFilter.vue';
const route = useRoute();
const routeName = computed(() => route.name);
</script> </script>
<template> <template>
<VnCard <VnCardBeta
data-key="Client" data-key="Client"
base-url="Clients" base-url="Clients"
:descriptor="CustomerDescriptor" :descriptor="CustomerDescriptor"
:filter-panel="routeName != 'CustomerConsumption' && CustomerFilter"
search-data-key="CustomerList"
:searchbar-props="{
url: 'Clients/filter',
label: 'Search customer',
info: 'You can search by customer id or name',
}"
/> />
</template> </template>

View File

@ -153,6 +153,7 @@ const updateDateParams = (value, params) => {
data-key="CustomerConsumption" data-key="CustomerConsumption"
url="Clients/consumption" url="Clients/consumption"
:order="['itemTypeFk', 'itemName', 'itemSize', 'description']" :order="['itemTypeFk', 'itemName', 'itemSize', 'description']"
:filter="{ where: { clientFk: route.params.id } }"
:columns="columns" :columns="columns"
search-url="consumption" search-url="consumption"
:user-params="userParams" :user-params="userParams"

View File

@ -1,177 +0,0 @@
<script setup>
import { useI18n } from 'vue-i18n';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue';
import { QItem } from 'quasar';
import VnSelect from 'src/components/common/VnSelect.vue';
import { QItemSection } from 'quasar';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import { toDate } from 'src/filters';
const { t } = useI18n();
defineProps({ dataKey: { type: String, required: true } });
</script>
<template>
<VnFilterPanel :data-key="dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params }">
<QItem>
<QItemSection>
<VnInput
:label="t('params.item')"
v-model="params.itemId"
is-outlined
lazy-rules
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
v-model="params.buyerId"
url="TicketRequests/getItemTypeWorker"
:fields="['id', 'nickname']"
sort-by="nickname ASC"
:label="t('params.buyer')"
option-value="id"
option-label="nickname"
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
v-model="params.typeId"
url="ItemTypes"
:include="['category']"
:fields="['id', 'name', 'categoryFk']"
sort-by="name ASC"
:label="t('params.typeId')"
option-label="name"
option-value="id"
dense
outlined
rounded
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption>{{
scope.opt?.category?.name
}}</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
v-model="params.categoryId"
url="ItemCategories"
:fields="['id', 'name']"
sort-by="name ASC"
:label="t('params.categoryId')"
option-label="name"
option-value="id"
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
v-model="params.campaignId"
url="Campaigns/latest"
sort-by="dated DESC"
:label="t('params.campaignId')"
option-label="code"
option-value="id"
dense
outlined
rounded
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{
t(`params.${scope.opt?.code}`)
}}</QItemLabel>
<QItemLabel caption>{{
toDate(scope.opt.dated)
}}</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputDate
:label="t('params.from')"
v-model="params.from"
@update:model-value="searchFn()"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputDate
:label="t('params.to')"
v-model="params.to"
@update:model-value="searchFn()"
is-outlined
/>
</QItemSection>
</QItem>
</template>
</VnFilterPanel>
</template>
<i18n>
en:
params:
item: Item id
buyer: Buyer
type: Type
category: Category
itemId: Item id
buyerId: Buyer
typeId: Type
categoryId: Category
from: From
to: To
campaignId: Campaña
valentinesDay: Valentine's Day
mothersDay: Mother's Day
allSaints: All Saints' Day
es:
params:
item: Id artículo
buyer: Comprador
type: Tipo
category: Categoría
itemId: Id Artículo
buyerId: Comprador
typeId: Tipo
categoryId: Reino
from: Desde
to: Hasta
campaignId: Campaña
valentinesDay: Día de San Valentín
mothersDay: Día de la Madre
allSaints: Día de Todos los Santos
</i18n>

View File

@ -187,14 +187,18 @@ const debtWarning = computed(() => {
</QBtn> </QBtn>
<QBtn <QBtn
:to="{ :to="{
name: 'AccountSummary', name: 'OrderList',
params: { id: entity.id }, query: {
createForm: JSON.stringify({
clientFk: entity.id,
}),
},
}" }"
size="md" size="md"
icon="face" icon="vn:basketadd"
color="primary" color="primary"
> >
<QTooltip>{{ t('Go to user') }}</QTooltip> <QTooltip>{{ t('globals.pageTitles.createOrder') }}</QTooltip>
</QBtn> </QBtn>
<QBtn <QBtn
v-if="entity.supplier" v-if="entity.supplier"
@ -218,14 +222,9 @@ en:
unpaidDated: 'Date {dated}' unpaidDated: 'Date {dated}'
unpaidAmount: 'Amount {amount}' unpaidAmount: 'Amount {amount}'
es: es:
Go to module index: Ir al índice del módulo
Customer ticket list: Listado de tickets del cliente Customer ticket list: Listado de tickets del cliente
Customer invoice out list: Listado de facturas del cliente Customer invoice out list: Listado de facturas del cliente
New order: Nuevo pedido
New ticket: Nuevo ticket
Go to user: Ir al usuario
Go to supplier: Ir al proveedor Go to supplier: Ir al proveedor
Customer unpaid: Cliente impago
Unpaid: Impagado Unpaid: Impagado
unpaidDated: 'Fecha {dated}' unpaidDated: 'Fecha {dated}'
unpaidAmount: 'Importe {amount}' unpaidAmount: 'Importe {amount}'

View File

@ -51,7 +51,6 @@ const openCreateForm = (type) => {
}; };
const clientFk = { const clientFk = {
ticket: 'clientId', ticket: 'clientId',
order: 'clientFk',
}; };
const key = clientFk[type]; const key = clientFk[type];
if (!key) return; if (!key) return;
@ -70,11 +69,6 @@ const openCreateForm = (type) => {
{{ t('globals.pageTitles.createTicket') }} {{ t('globals.pageTitles.createTicket') }}
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem v-ripple clickable @click="openCreateForm('order')">
<QItemSection>
{{ t('globals.pageTitles.createOrder') }}
</QItemSection>
</QItem>
<QItem v-ripple clickable> <QItem v-ripple clickable>
<QItemSection @click="showSmsDialog()">{{ t('Send SMS') }}</QItemSection> <QItemSection @click="showSmsDialog()">{{ t('Send SMS') }}</QItemSection>
</QItem> </QItem>

View File

@ -5,18 +5,19 @@ import { useRouter } from 'vue-router';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { toDate } from 'src/filters'; import { toDate } from 'src/filters';
import RightMenu from 'src/components/common/RightMenu.vue';
import CustomerSummary from './Card/CustomerSummary.vue'; import CustomerSummary from './Card/CustomerSummary.vue';
import CustomerFilter from './CustomerFilter.vue'; import CustomerFilter from './CustomerFilter.vue';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import VnLocation from 'src/components/common/VnLocation.vue'; import VnLocation from 'src/components/common/VnLocation.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue';
import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue';
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
import VnSection from 'src/components/common/VnSection.vue';
const { t } = useI18n(); const { t } = useI18n();
const router = useRouter(); const router = useRouter();
const tableRef = ref(); const tableRef = ref();
const dataKey = 'CustomerList';
const columns = computed(() => [ const columns = computed(() => [
{ {
align: 'left', align: 'left',
@ -398,21 +399,23 @@ function handleLocation(data, location) {
</script> </script>
<template> <template>
<VnSearchbar <VnSection
:info="t('You can search by customer id or name')" :data-key="dataKey"
:label="t('Search customer')" :columns="columns"
data-key="CustomerList" prefix="customer"
/> :array-data-props="{
<RightMenu> url: 'Clients/filter',
<template #right-panel> order: ['id DESC'],
}"
>
<template #advanced-menu>
<CustomerFilter data-key="CustomerList" /> <CustomerFilter data-key="CustomerList" />
</template> </template>
</RightMenu> <template #body>
<VnTable <VnTable
ref="tableRef" ref="tableRef"
data-key="CustomerList" :data-key="dataKey"
url="Clients/filter" url="Clients/filter"
order="id DESC"
:create="{ :create="{
urlCreate: 'Clients/createWithUser', urlCreate: 'Clients/createWithUser',
title: t('globals.pageTitles.customerCreate'), title: t('globals.pageTitles.customerCreate'),
@ -431,7 +434,7 @@ function handleLocation(data, location) {
:label="t('customer.summary.salesPerson')" :label="t('customer.summary.salesPerson')"
v-model="data.salesPersonFk" v-model="data.salesPersonFk"
:params="{ :params="{
departmentCodes: ['VT'], departmentCodes: ['VT', 'shopping'],
}" }"
:has-avatar="true" :has-avatar="true"
:id-value="data.salesPersonFk" :id-value="data.salesPersonFk"
@ -463,7 +466,12 @@ function handleLocation(data, location) {
@update:model-value="(location) => handleLocation(data, location)" @update:model-value="(location) => handleLocation(data, location)"
/> />
<QInput v-model="data.userName" :label="t('Web user')" /> <QInput v-model="data.userName" :label="t('Web user')" />
<QInput :label="t('Email')" clearable type="email" v-model="data.email"> <QInput
:label="t('Email')"
clearable
type="email"
v-model="data.email"
>
<template #append> <template #append>
<QIcon name="info" class="cursor-info"> <QIcon name="info" class="cursor-info">
<QTooltip max-width="400px">{{ <QTooltip max-width="400px">{{
@ -475,6 +483,8 @@ function handleLocation(data, location) {
</template> </template>
</VnTable> </VnTable>
</template> </template>
</VnSection>
</template>
<i18n> <i18n>
es: es:
Web user: Usuario web Web user: Usuario web

View File

@ -214,7 +214,7 @@ const toCustomerSamples = () => {
<template #custom-buttons> <template #custom-buttons>
<QBtn <QBtn
:disabled="isLoading || !sampleType?.hasPreview" :disabled="isLoading || !sampleType?.hasPreview"
:label="t('Preview')" :label="t('globals.preview')"
:loading="isLoading" :loading="isLoading"
@click.stop="getPreview()" @click.stop="getPreview()"
color="primary" color="primary"
@ -353,7 +353,6 @@ es:
Its only used when sample is sent: Se utiliza únicamente cuando se envía la plantilla Its only used when sample is sent: Se utiliza únicamente cuando se envía la plantilla
To who should the recipient replay?: ¿A quien debería responder el destinatario? To who should the recipient replay?: ¿A quien debería responder el destinatario?
Edit address: Editar dirección Edit address: Editar dirección
Preview: Vista previa
Email cannot be blank: Debes introducir un email Email cannot be blank: Debes introducir un email
Choose a sample: Selecciona una plantilla Choose a sample: Selecciona una plantilla
Choose a company: Selecciona una empresa Choose a company: Selecciona una empresa

View File

@ -94,6 +94,8 @@ customer:
hasToInvoiceByAddress: Invoice by address hasToInvoiceByAddress: Invoice by address
isToBeMailed: Mailing isToBeMailed: Mailing
hasSepaVnl: VNL B2B received hasSepaVnl: VNL B2B received
search: Search customer
searchInfo: You can search by customer ID
params: params:
id: Id id: Id
isWorker: Is Worker isWorker: Is Worker

View File

@ -1,5 +1,3 @@
Search customer: Buscar cliente
You can search by customer id or name: Puedes buscar por id o nombre del cliente
customer: customer:
card: card:
debt: Riesgo debt: Riesgo
@ -96,6 +94,8 @@ customer:
hasToInvoiceByAddress: Factura por consigna hasToInvoiceByAddress: Factura por consigna
isToBeMailed: Env. emails isToBeMailed: Env. emails
hasSepaVnl: Recibido B2B VNL hasSepaVnl: Recibido B2B VNL
search: Buscar cliente
searchInfo: Puedes buscar por id o nombre del cliente
params: params:
id: ID id: ID
isWorker: Es trabajador isWorker: Es trabajador

View File

@ -179,7 +179,7 @@ es:
Supplier card: Ficha del proveedor Supplier card: Ficha del proveedor
All travels with current agency: Todos los envíos con la agencia actual All travels with current agency: Todos los envíos con la agencia actual
All entries with current supplier: Todas las entradas con el proveedor actual All entries with current supplier: Todas las entradas con el proveedor actual
Go to module index: Ir al índice del modulo Show entry report: Ver informe del pedido
Inventory entry: Es inventario Inventory entry: Es inventario
Virtual entry: Es una redada Virtual entry: Es una redada
</i18n> </i18n>

View File

@ -205,7 +205,7 @@ const columns = computed(() => [
userFilter: entryFilter, userFilter: entryFilter,
}" }"
> >
<template #rightMenu> <template #advanced-menu>
<EntryFilter data-key="EntryList" /> <EntryFilter data-key="EntryList" />
</template> </template>
<template #body> <template #body>
@ -231,7 +231,7 @@ const columns = computed(() => [
> >
<QTooltip>{{ <QTooltip>{{
t( t(
'entry.list.tableVisibleColumns.isExcludedFromAvailable' 'entry.list.tableVisibleColumns.isExcludedFromAvailable',
) )
}}</QTooltip> }}</QTooltip>
</QIcon> </QIcon>

View File

@ -1,19 +1,11 @@
<script setup> <script setup>
import VnCard from 'components/common/VnCard.vue'; import VnCardBeta from 'components/common/VnCardBeta.vue';
import ItemDescriptor from './ItemDescriptor.vue'; import ItemDescriptor from './ItemDescriptor.vue';
import ItemListFilter from '../ItemListFilter.vue';
</script> </script>
<template> <template>
<VnCard <VnCardBeta
data-key="Item" data-key="Item"
base-url="Items" base-url="Items"
:descriptor="ItemDescriptor" :descriptor="ItemDescriptor"
:filter-panel="ItemListFilter"
search-data-key="ItemList"
:searchbar-props="{
url: 'Items/filter',
label: 'searchbar.label',
info: 'searchbar.info',
}"
/> />
</template> </template>

View File

@ -6,18 +6,17 @@ import VnImg from 'src/components/ui/VnImg.vue';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import { toDate } from 'src/filters'; import { toDate } from 'src/filters';
import FetchedTags from 'src/components/ui/FetchedTags.vue'; import FetchedTags from 'src/components/ui/FetchedTags.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import ItemSummary from '../Item/Card/ItemSummary.vue'; import ItemSummary from '../Item/Card/ItemSummary.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import ItemDescriptorProxy from './Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from './Card/ItemDescriptorProxy.vue';
import ItemTypeDescriptorProxy from './ItemType/Card/ItemTypeDescriptorProxy.vue'; import ItemTypeDescriptorProxy from './ItemType/Card/ItemTypeDescriptorProxy.vue';
import { cloneItem } from 'src/pages/Item/composables/cloneItem'; import { cloneItem } from 'src/pages/Item/composables/cloneItem';
import RightMenu from 'src/components/common/RightMenu.vue';
import ItemListFilter from './ItemListFilter.vue'; import ItemListFilter from './ItemListFilter.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import axios from 'axios'; import axios from 'axios';
import VnSection from 'src/components/common/VnSection.vue';
const entityId = computed(() => route.params.id); const entityId = computed(() => route.params.id);
const { openCloneDialog } = cloneItem(); const { openCloneDialog } = cloneItem();
@ -25,9 +24,11 @@ const { viewSummary } = useSummaryDialog();
const { t } = useI18n(); const { t } = useI18n();
const tableRef = ref(); const tableRef = ref();
const route = useRoute(); const route = useRoute();
const dataKey = 'ItemList';
const validPriorities = ref([]); const validPriorities = ref([]);
const defaultTag = ref(); const defaultTag = ref();
const defaultPriority = ref(); const defaultPriority = ref();
const itemFilter = { const itemFilter = {
include: [ include: [
{ {
@ -324,22 +325,29 @@ onBeforeMount(async () => {
}); });
}); });
</script> </script>
<template> <template>
<VnSearchbar <VnSection
data-key="ItemList" :data-key="dataKey"
:label="t('item.searchbar.label')" :columns="columns"
:info="t('item.searchbar.info')" prefix="item"
/> :array-data-props="{
<RightMenu> url: 'Items/filter',
<template #right-panel> order: ['isActive DESC', 'name', 'id'],
userFilter: itemFilter,
}"
>
<template #advanced-menu>
<ItemListFilter data-key="ItemList" /> <ItemListFilter data-key="ItemList" />
</template> </template>
</RightMenu> <template #body>
<VnTable <VnTable
v-if="defaultTag" v-if="defaultTag"
ref="tableRef" ref="tableRef"
data-key="ItemList" :data-key="dataKey"
url="Items/filter" :columns="columns"
:right-search="false"
redirect="Item"
:create="{ :create="{
urlCreate: 'Items/new', urlCreate: 'Items/new',
title: t('item.list.newItem'), title: t('item.list.newItem'),
@ -350,12 +358,7 @@ onBeforeMount(async () => {
priority: defaultPriority, priority: defaultPriority,
}, },
}" }"
:order="['isActive DESC', 'name', 'id']"
:columns="columns"
redirect="Item"
:is-editable="false" :is-editable="false"
:right-search="false"
:filter="itemFilter"
> >
<template #column-image="{ row }"> <template #column-image="{ row }">
<VnImg <VnImg
@ -391,7 +394,7 @@ onBeforeMount(async () => {
{{ row?.subName.toUpperCase() }} {{ row?.subName.toUpperCase() }}
</div> </div>
</div> </div>
<FetchedTags :item="row" /> <FetchedTags :item="row" :columns="3" />
</template> </template>
<template #more-create-dialog="{ data }"> <template #more-create-dialog="{ data }">
<VnInput <VnInput
@ -413,7 +416,9 @@ onBeforeMount(async () => {
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
<QItemSection> <QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel> <QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption> #{{ scope.opt?.id }} </QItemLabel> <QItemLabel caption>
#{{ scope.opt?.id }}
</QItemLabel>
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>
@ -457,7 +462,9 @@ onBeforeMount(async () => {
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
<QItemSection> <QItemSection>
<QItemLabel>{{ scope.opt?.description }}</QItemLabel> <QItemLabel>{{ scope.opt?.description }}</QItemLabel>
<QItemLabel caption> #{{ scope.opt?.id }} </QItemLabel> <QItemLabel caption>
#{{ scope.opt?.id }}
</QItemLabel>
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>
@ -485,6 +492,8 @@ onBeforeMount(async () => {
</template> </template>
</VnTable> </VnTable>
</template> </template>
</VnSection>
</template>
<style lang="scss" scoped> <style lang="scss" scoped>
.subName { .subName {
text-transform: uppercase; text-transform: uppercase;
@ -497,5 +506,4 @@ es:
New item: Nuevo artículo New item: Nuevo artículo
Create Item: Crear artículo Create Item: Crear artículo
You can search by id: Puedes buscar por id You can search by id: Puedes buscar por id
Preview: Vista previa
</i18n> </i18n>

View File

@ -1,7 +1,6 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref, onMounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { onMounted } from 'vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
@ -233,7 +232,7 @@ onMounted(async () => {
<QItemSection> <QItemSection>
<VnSelect <VnSelect
:label="t('params.buyerFk')" :label="t('params.buyerFk')"
v-model="params.buyerFk" v-model="params.workerFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"
:options="buyersOptions" :options="buyersOptions"
option-value="id" option-value="id"

View File

@ -1,20 +1,12 @@
<script setup> <script setup>
import VnCard from 'components/common/VnCard.vue'; import VnCardBeta from 'components/common/VnCardBeta.vue';
import ItemTypeDescriptor from 'src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue'; import ItemTypeDescriptor from 'src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue';
import ItemTypeFilter from 'src/pages/Item/ItemType/ItemTypeFilter.vue';
import ItemTypeSearchbar from '../ItemTypeSearchbar.vue';
</script> </script>
<template> <template>
<VnCard <VnCardBeta
data-key="ItemTypeSummary" data-key="ItemTypeSummary"
base-url="ItemTypes" base-url="ItemTypes"
:descriptor="ItemTypeDescriptor" :descriptor="ItemTypeDescriptor"
:filter-panel="ItemTypeFilter" />
search-data-key="ItemTypeList"
search-url="ItemTypes"
>
<template #searchbar>
<ItemTypeSearchbar />
</template>
</VnCard>
</template> </template>

View File

@ -63,7 +63,3 @@ const setData = (entity) => (data.value = useCardDescription(entity.code, entity
</CardDescriptor> </CardDescriptor>
</template> </template>
<i18n>
es:
Go to module index: Ir al índice del módulo
</i18n>

View File

@ -1,90 +0,0 @@
<script setup>
import { useI18n } from 'vue-i18n';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const emit = defineEmits(['search']);
const exprBuilder = (param, value) => {
switch (param) {
case 'name':
return {
name: { like: `%${value}%` },
};
case 'code':
return {
code: { like: `%${value}%` },
};
case 'search':
if (value) {
if (!isNaN(value)) {
return { id: value };
} else {
return {
or: [
{
name: {
like: `%${value}%`,
},
},
{
code: {
like: `%${value}%`,
},
},
],
};
}
}
}
};
</script>
<template>
<VnFilterPanel
:data-key="props.dataKey"
:search-button="true"
@search="emit('search')"
search-url="table"
:expr-builder="exprBuilder"
>
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params }">
<QItem>
<QItemSection>
<VnInput :label="t('Name')" v-model="params.name" is-outlined />
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput v-model="params.code" :label="t('Code')" is-outlined />
</QItemSection>
</QItem>
</template>
</VnFilterPanel>
</template>
<i18n>
en:
params:
name: Name
code: Code
es:
params:
name: Nombre
code: Código
Name: Nombre
Code: Código
</i18n>

View File

@ -1,19 +0,0 @@
<script setup>
import VnSearchbar from 'components/ui/VnSearchbar.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>
<template>
<VnSearchbar
data-key="ItemTypeList"
url="ItemTypes"
:label="t('Search item type')"
:info="t('Search itemType by id, name or code')"
/>
</template>
<i18n>
es:
Search item type: Buscar familia
Search itemType by id, name or code: Buscar familia por id, nombre o código
</i18n>

View File

@ -15,3 +15,5 @@ itemType:
promo: Promo promo: Promo
itemPackingType: Item packing type itemPackingType: Item packing type
isUnconventionalSize: Is unconventional size isUnconventionalSize: Is unconventional size
search: Search item type
searchInfo: Search item type by id, name or code

View File

@ -15,3 +15,5 @@ itemType:
promo: Promoción promo: Promoción
itemPackingType: Tipo de embalaje itemPackingType: Tipo de embalaje
isUnconventionalSize: Es de tamaño poco convencional isUnconventionalSize: Es de tamaño poco convencional
search: Buscar familia
searchInfo: Buscar familia por id, nombre o código

View File

@ -1,17 +1,50 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import ItemTypeSearchbar from 'src/pages/Item/ItemType/ItemTypeSearchbar.vue';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import ItemTypeFilter from './ItemType/ItemTypeFilter.vue';
import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue';
import VnSection from 'src/components/common/VnSection.vue';
const { t } = useI18n(); const { t } = useI18n();
const tableRef = ref(); const tableRef = ref();
const itemCategoriesOptions = ref([]); const itemCategoriesOptions = ref([]);
const temperatureOptions = ref([]); const temperatureOptions = ref([]);
const dataKey = 'ItemTypeList';
const exprBuilder = (param, value) => {
switch (param) {
case 'name':
return {
name: { like: `%${value}%` },
};
case 'code':
return {
code: { like: `%${value}%` },
};
case 'search':
if (value) {
if (!isNaN(value)) {
return { id: value };
} else {
return {
or: [
{
name: {
like: `%${value}%`,
},
},
{
code: {
like: `%${value}%`,
},
},
],
};
}
}
}
};
const columns = computed(() => [ const columns = computed(() => [
{ {
@ -103,23 +136,15 @@ const columns = computed(() => [
@on-fetch="(data) => (temperatureOptions = data)" @on-fetch="(data) => (temperatureOptions = data)"
auto-load auto-load
/> />
<RightMenu> <VnSection
<template #right-panel> :data-key="dataKey"
<ItemTypeFilter data-key="ItemTypeList" /> :columns="columns"
</template> prefix="itemType"
</RightMenu> :array-data-props="{
<ItemTypeSearchbar /> url: 'ItemTypes',
<VnTable order: 'name ASC',
ref="tableRef" exprBuilder,
data-key="ItemTypeList" userFilter: {
url="ItemTypes"
:create="{
urlCreate: 'ItemTypes',
title: t('Create ItemTypes'),
onDataSaved: () => tableRef.reload(),
formInitialData: {},
}"
:user-filter="{
include: { include: {
relation: 'worker', relation: 'worker',
scope: { scope: {
@ -132,11 +157,21 @@ const columns = computed(() => [
}, },
}, },
}, },
},
}"
>
<template #body>
<VnTable
ref="tableRef"
:data-key="dataKey"
:create="{
urlCreate: 'ItemTypes',
title: t('Create ItemTypes'),
onDataSaved: () => tableRef.reload(),
formInitialData: {},
}" }"
order="name ASC"
:columns="columns" :columns="columns"
auto-load auto-load
:right-search="false"
redirect="item/item-type" redirect="item/item-type"
> >
<template #column-workerFk="{ row }"> <template #column-workerFk="{ row }">
@ -147,6 +182,8 @@ const columns = computed(() => [
</template> </template>
</VnTable> </VnTable>
</template> </template>
</VnSection>
</template>
<i18n> <i18n>
es: es:

View File

@ -180,44 +180,46 @@ item:
intrastat: Intrastat intrastat: Intrastat
origin: Origin origin: Origin
buyRequest: buyRequest:
ticketId: 'Ticket ID' ticketId: Ticket ID
shipped: 'Shipped' shipped: Shipped
requester: 'Requester' requester: Requester
requested: 'Requested' requested: Requested
price: 'Price' price: Price
attender: 'Attender' attender: Attender
item: 'Item' item: Item
achieved: 'Achieved' achieved: Achieved
concept: 'Concept' concept: Concept
state: 'State' state: State
summary: summary:
basicData: 'Basic data' basicData: Basic data
otherData: 'Other data' otherData: Other data
description: 'Description' description: Description
tax: 'Tax' tax: Tax
tags: 'Tags' tags: Tags
botanical: 'Botanical' botanical: Botanical
barcode: 'Barcode' barcode: Barcode
name: 'Nombre' name: Name
completeName: 'Nombre completo' completeName: Complete name
family: 'Familia' family: Family
size: 'Medida' size: Size
origin: 'Origen' origin: Origin
stems: 'Tallos' stems: Stems
multiplier: 'Multiplicador' multiplier: Multiplier
buyer: 'Comprador' buyer: Buyer
doPhoto: 'Do photo' doPhoto: Do photo
intrastatCode: 'Código intrastat' intrastatCode: Intrastat code
intrastat: 'Intrastat' intrastat: 'Intrastat'
ref: 'Referencia' ref: Reference
relevance: 'Relevancia' relevance: Relevance
weight: 'Peso (gramos)/tallo' weight: Weight (gram)/stem
units: 'Unidades/caja' units: Units/box
expense: 'Gasto' expense: Expense
generic: 'Genérico' generic: Generic
recycledPlastic: 'Plástico reciclado' recycledPlastic: Recycled plastic
nonRecycledPlastic: 'Plástico no reciclado' nonRecycledPlastic: Non recycled plastic
minSalesQuantity: 'Cantidad mínima de venta' minSalesQuantity: Min sales quantity
genus: 'Genus' genus: Genus
specie: 'Specie' specie: Specie
search: 'Search item'
searchInfo: 'You can search by id'
regularizeStock: Regularize stock regularizeStock: Regularize stock

View File

@ -73,9 +73,6 @@ itemTags:
addTag: Añadir etiqueta addTag: Añadir etiqueta
tag: Etiqueta tag: Etiqueta
value: Valor value: Valor
searchbar:
label: Buscar artículo
info: Buscar por id de artículo
itemType: itemType:
shared: shared:
code: Código code: Código
@ -108,9 +105,6 @@ item:
concept: Concepto concept: Concepto
denyOptions: Denegado denyOptions: Denegado
scopeDays: Días en adelante scopeDays: Días en adelante
searchbar:
label: Buscar artículo
info: Puedes buscar por id
descriptor: descriptor:
item: Artículo item: Artículo
buyer: Comprador buyer: Comprador
@ -182,35 +176,35 @@ item:
intrastat: Intrastat intrastat: Intrastat
origin: Origen origin: Origen
summary: summary:
basicData: 'Datos básicos' basicData: Datos básicos
otherData: 'Otros datos' otherData: Otros datos
description: 'Descripción' description: Descripción
tax: 'IVA' tax: IVA
tags: 'Etiquetas' tags: Etiquetas
botanical: 'Botánico' botanical: Botánico
barcode: 'Código de barras' barcode: Código de barras
name: 'Nombre' name: Nombre
completeName: 'Nombre completo' completeName: Nombre completo
family: 'Familia' family: Familia
size: 'Medida' size: Medida
origin: 'Origen' origin: Origen
stems: 'Tallos' stems: Tallos
multiplier: 'Multiplicador' multiplier: Multiplicador
buyer: 'Comprador' buyer: Comprador
doPhoto: 'Hacer foto' doPhoto: Hacer foto
intrastatCode: 'Código intrastat' intrastatCode: Código intrastat
intrastat: 'Intrastat' intrastat: Intrastat
ref: 'Referencia' ref: Referencia
relevance: 'Relevancia' relevance: Relevancia
weight: 'Peso (gramos)/tallo' weight: Peso (gramos)/tallo
units: 'Unidades/caja' units: Unidades/caja
expense: 'Gasto' expense: Gasto
generic: 'Genérico' generic: Genérico
recycledPlastic: 'Plástico reciclado' recycledPlastic: Plástico reciclado
nonRecycledPlastic: 'Plástico no reciclado' nonRecycledPlastic: Plástico no reciclado
minSalesQuantity: 'Cantidad mínima de venta' minSalesQuantity: Cantidad mínima de venta
genus: 'Genus' genus: Genus
specie: 'Specie' specie: Specie
regularizeStock: Regularizar stock regularizeStock: Regularizar stock
buyRequest: buyRequest:
ticketId: 'ID Ticket' ticketId: 'ID Ticket'
@ -223,3 +217,5 @@ item:
achieved: 'Conseguido' achieved: 'Conseguido'
concept: 'Concepto' concept: 'Concepto'
state: 'Estado' state: 'Estado'
search: 'Buscar artículo'
searchInfo: 'Puedes buscar por id'

View File

@ -290,7 +290,7 @@ const columns = computed(() => [
}, },
}, },
{ {
title: t('salesTicketsTable.preview'), title: t('globals.preview'),
icon: 'preview', icon: 'preview',
color: 'primary', color: 'primary',
action: (row) => viewSummary(row.id, TicketSummary), action: (row) => viewSummary(row.id, TicketSummary),

View File

@ -33,7 +33,6 @@ salesTicketsTable:
isFragile: Is fragile isFragile: Is fragile
zone: Zone zone: Zone
goToLines: Go to lines goToLines: Go to lines
preview: Preview
total: Total total: Total
preparation: H.Prep preparation: H.Prep
payMethod: Pay method payMethod: Pay method

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { onMounted, ref, computed, watch } from 'vue'; import { onMounted, ref, watch } from 'vue';
import axios from 'axios'; import axios from 'axios';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnPaginate from 'src/components/ui/VnPaginate.vue'; import VnPaginate from 'src/components/ui/VnPaginate.vue';
@ -9,6 +9,7 @@ import CatalogItem from 'src/components/ui/CatalogItem.vue';
import OrderCatalogFilter from 'src/pages/Order/Card/OrderCatalogFilter.vue'; import OrderCatalogFilter from 'src/pages/Order/Card/OrderCatalogFilter.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import RightMenu from 'src/components/common/RightMenu.vue';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@ -89,14 +90,16 @@ watch(
:search-remove-params="false" :search-remove-params="false"
/> />
</Teleport> </Teleport>
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> <RightMenu>
<template #right-panel>
<OrderCatalogFilter <OrderCatalogFilter
:data-key="dataKey" :data-key="dataKey"
:tag-value="tagValue" :tag-value="tagValue"
:tags="tags" :tags="tags"
:initial-catalog-params="catalogParams" :initial-catalog-params="catalogParams"
/> />
</Teleport> </template>
</RightMenu>
<QPage class="column items-center q-pa-md" data-cy="orderCatalogPage"> <QPage class="column items-center q-pa-md" data-cy="orderCatalogPage">
<div class="full-width"> <div class="full-width">
<VnPaginate :data-key="dataKey"> <VnPaginate :data-key="dataKey">
@ -141,5 +144,5 @@ watch(
<i18n> <i18n>
es: es:
You can search items by name or id: Puedes buscar items por nombre o id You can search items by name or id: Puedes buscar items por nombre o id
Search items: Buscar items Search items: Buscar artículos
</i18n> </i18n>

View File

@ -115,6 +115,7 @@ const removeTagGroupParam = (params, search, valIndex) => {
} else { } else {
params.tagGroups.splice(valIndex, 1); params.tagGroups.splice(valIndex, 1);
} }
search();
}; };
const setCategoryList = (data) => { const setCategoryList = (data) => {

View File

@ -4,7 +4,6 @@ import { useI18n } from 'vue-i18n';
import { ref, computed, watch } from 'vue'; import { ref, computed, watch } from 'vue';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import axios from 'axios'; import axios from 'axios';
import { useStateStore } from 'stores/useStateStore';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import { confirm } from 'src/pages/Order/composables/confirmOrder'; import { confirm } from 'src/pages/Order/composables/confirmOrder';
import { toCurrency, toDate } from 'src/filters'; import { toCurrency, toDate } from 'src/filters';
@ -16,9 +15,9 @@ import VnImg from 'src/components/ui/VnImg.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import FetchedTags from 'src/components/ui/FetchedTags.vue'; import FetchedTags from 'src/components/ui/FetchedTags.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
const router = useRouter(); const router = useRouter();
const stateStore = useStateStore();
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
@ -264,7 +263,8 @@ watch(
@on-fetch="(data) => (orderSummary.vat = data)" @on-fetch="(data) => (orderSummary.vat = data)"
auto-load auto-load
/> />
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> <RightMenu>
<template #right-panel>
<QCard <QCard
class="order-lines-summary q-pa-lg" class="order-lines-summary q-pa-lg"
v-if="orderSummary.vat && orderSummary.total" v-if="orderSummary.vat && orderSummary.total"
@ -277,10 +277,13 @@ watch(
:value="toCurrency(orderSummary.total - orderSummary.vat)" :value="toCurrency(orderSummary.total - orderSummary.vat)"
/> />
<VnLv :label="t('VAT') + ': '" :value="toCurrency(orderSummary?.vat)" /> <VnLv :label="t('VAT') + ': '" :value="toCurrency(orderSummary?.vat)" />
<VnLv :label="t('total') + ': '" :value="toCurrency(orderSummary?.total)" /> <VnLv
:label="t('total') + ': '"
:value="toCurrency(orderSummary?.total)"
/>
</QCard> </QCard>
</Teleport> </template>
</RightMenu>
<VnTable <VnTable
ref="tableLinesRef" ref="tableLinesRef"
data-key="OrderLines" data-key="OrderLines"

View File

@ -190,7 +190,7 @@ const getDateColor = (date) => {
order: ['landed DESC', 'clientFk ASC', 'id DESC'], order: ['landed DESC', 'clientFk ASC', 'id DESC'],
}" }"
> >
<template #rightMenu> <template #advanced-menu>
<OrderFilter data-key="OrderList" /> <OrderFilter data-key="OrderList" />
</template> </template>
<template #body> <template #body>

View File

@ -142,7 +142,7 @@ const total = computed(() => {
const openDmsUploadDialog = async () => { const openDmsUploadDialog = async () => {
dmsDialog.value.rowsToCreateInvoiceIn = selectedRows.value dmsDialog.value.rowsToCreateInvoiceIn = selectedRows.value
.filter( .filter(
(agencyTerm) => agencyTerm.supplierFk === selectedRows.value?.[0].supplierFk (agencyTerm) => agencyTerm.supplierFk === selectedRows.value?.[0].supplierFk,
) )
.map((agencyTerm) => ({ .map((agencyTerm) => ({
routeFk: agencyTerm.routeFk, routeFk: agencyTerm.routeFk,
@ -277,5 +277,4 @@ es:
Price: Precio Price: Precio
Received: Recibida Received: Recibida
Autonomous: Autónomos Autonomous: Autónomos
Preview: Vista previa
</i18n> </i18n>

View File

@ -112,7 +112,7 @@ const removeSelection = async () => {
await Promise.all( await Promise.all(
selectedRows.value.map((roadmap) => { selectedRows.value.map((roadmap) => {
axios.delete(`Roadmaps/${roadmap.id}`); axios.delete(`Roadmaps/${roadmap.id}`);
}) }),
); );
}; };
@ -236,6 +236,5 @@ es:
Plate: Matrícula Plate: Matrícula
Price: Precio Price: Precio
Observations: Observaciones Observations: Observaciones
Preview: Vista previa
Select the estimated date of departure (ETD): Selecciona la fecha estimada de salida Select the estimated date of departure (ETD): Selecciona la fecha estimada de salida
</i18n> </i18n>

View File

@ -33,7 +33,6 @@ route:
Mark as served: Mark as served Mark as served: Mark as served
Download selected routes as PDF: Download selected routes as PDF Download selected routes as PDF: Download selected routes as PDF
Add ticket: Add ticket Add ticket: Add ticket
Preview: Preview
Summary: Summary Summary: Summary
Route is closed: Route is closed Route is closed: Route is closed
Route is not served: Route is not served Route is not served: Route is not served

View File

@ -33,7 +33,7 @@ route:
Mark as served: Marcar como servidas Mark as served: Marcar como servidas
Download selected routes as PDF: Descargar rutas seleccionadas como PDF Download selected routes as PDF: Descargar rutas seleccionadas como PDF
Add ticket: Añadir tickets Add ticket: Añadir tickets
Preview: Vista previa preview: Vista previa
Summary: Resumen Summary: Resumen
Route is closed: La ruta está cerrada Route is closed: La ruta está cerrada
Route is not served: La ruta no está servida Route is not served: La ruta no está servida

View File

@ -188,7 +188,6 @@ const getEntryQueryParams = (supplier) => {
es: es:
All entries with current supplier: Todas las entradas con proveedor actual All entries with current supplier: Todas las entradas con proveedor actual
Go to client: Ir a cliente Go to client: Ir a cliente
Go to module index: Ir al índice del módulo
Inactive supplier: Proveedor inactivo Inactive supplier: Proveedor inactivo
Unverified supplier: Proveedor no verificado Unverified supplier: Proveedor no verificado
</i18n> </i18n>

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import axios from 'axios'; import axios from 'axios';
import { date, useQuasar } from 'quasar'; import { date, useQuasar } from 'quasar';
import { useStateStore } from 'src/stores/useStateStore'; import RightMenu from 'src/components/common/RightMenu.vue';
import { computed, onMounted, reactive, ref } from 'vue'; import { computed, onMounted, reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@ -9,9 +9,7 @@ import { useRouter } from 'vue-router';
const router = useRouter(); const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
const stateStore = useStateStore();
onMounted(async () => { onMounted(async () => {
stateStore.rightDrawer = true;
await fetch(); await fetch();
}); });
@ -86,7 +84,8 @@ async function getVideoList(expeditionId, timed) {
</script> </script>
<template> <template>
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> <RightMenu>
<template #right-panel>
<QList bordered separator style="max-width: 318px"> <QList bordered separator style="max-width: 318px">
<QItem v-if="lastExpedition && videoList.length"> <QItem v-if="lastExpedition && videoList.length">
<QItemSection> <QItemSection>
@ -139,7 +138,9 @@ async function getVideoList(expeditionId, timed) {
<QItemSection> <QItemSection>
<QItemLabel caption>{{ t('globals.created') }}</QItemLabel> <QItemLabel caption>{{ t('globals.created') }}</QItemLabel>
<QItemLabel> <QItemLabel>
{{ date.formatDate(expedition.created, 'YYYY-MM-DD HH:mm:ss') }} {{
date.formatDate(expedition.created, 'YYYY-MM-DD HH:mm:ss')
}}
</QItemLabel> </QItemLabel>
<QItemLabel caption>{{ t('globals.item') }}</QItemLabel> <QItemLabel caption>{{ t('globals.item') }}</QItemLabel>
<QItemLabel>{{ expedition.packagingItemFk }}</QItemLabel> <QItemLabel>{{ expedition.packagingItemFk }}</QItemLabel>
@ -148,7 +149,8 @@ async function getVideoList(expeditionId, timed) {
</QItemSection> </QItemSection>
</QItem> </QItem>
</QList> </QList>
</Teleport> </template>
</RightMenu>
<QCard> <QCard>
<QCarousel animated v-model="slide" height="max-content"> <QCarousel animated v-model="slide" height="max-content">
<QCarouselSlide <QCarouselSlide

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, computed, onMounted, watch, nextTick } from 'vue'; import { ref, computed, watch, nextTick } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
@ -9,15 +9,14 @@ import FetchData from 'components/FetchData.vue';
import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue'; import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue';
import VnImg from 'src/components/ui/VnImg.vue'; import VnImg from 'src/components/ui/VnImg.vue';
import { useStateStore } from 'stores/useStateStore';
import { dashIfEmpty } from 'src/filters'; import { dashIfEmpty } from 'src/filters';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import { toCurrency } from 'filters/index'; import { toCurrency } from 'filters/index';
import axios from 'axios'; import axios from 'axios';
import VnTable from 'src/components/VnTable/VnTable.vue'; import VnTable from 'src/components/VnTable/VnTable.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
const route = useRoute(); const route = useRoute();
const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const salesRef = ref(null); const salesRef = ref(null);
const arrayData = useArrayData('ticketData'); const arrayData = useArrayData('ticketData');
@ -164,10 +163,6 @@ const getTicketVolume = async () => {
const { data } = await axios.get(`Tickets/${ticketData.value.id}/getVolume`); const { data } = await axios.get(`Tickets/${ticketData.value.id}/getVolume`);
ticketVolume.value = data[0].volume; ticketVolume.value = data[0].volume;
}; };
onMounted(() => {
stateStore.rightDrawer = true;
});
</script> </script>
<template> <template>
@ -178,8 +173,14 @@ onMounted(() => {
@on-fetch="(data) => (components = data)" @on-fetch="(data) => (components = data)"
auto-load auto-load
/> />
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> <RightMenu>
<QCard class="q-pa-sm color-vn-text" bordered flat style="border-color: black"> <template #right-panel>
<QCard
class="q-pa-sm color-vn-text"
bordered
flat
style="border-color: black"
>
<QCardSection horizontal> <QCardSection horizontal>
<span class="text-weight-bold text-subtitle1 text-center full-width"> <span class="text-weight-bold text-subtitle1 text-center full-width">
{{ t('basicData.total') }} {{ t('basicData.total') }}
@ -198,7 +199,12 @@ onMounted(() => {
<span>{{ toCurrency(getTotal) }}</span> <span>{{ toCurrency(getTotal) }}</span>
</QCardSection> </QCardSection>
</QCard> </QCard>
<QCard class="q-pa-sm color-vn-text" bordered flat style="border-color: black"> <QCard
class="q-pa-sm color-vn-text"
bordered
flat
style="border-color: black"
>
<QCardSection horizontal> <QCardSection horizontal>
<span class="text-weight-bold text-subtitle1 text-center full-width"> <span class="text-weight-bold text-subtitle1 text-center full-width">
{{ t('ticketComponents.components') }} {{ t('ticketComponents.components') }}
@ -217,14 +223,21 @@ onMounted(() => {
}}</span> }}</span>
</QCardSection> </QCardSection>
</QCard> </QCard>
<QCard class="q-pa-sm color-vn-text" bordered flat style="border-color: black"> <QCard
class="q-pa-sm color-vn-text"
bordered
flat
style="border-color: black"
>
<QCardSection horizontal> <QCardSection horizontal>
<span class="text-weight-bold text-subtitle1 text-center full-width"> <span class="text-weight-bold text-subtitle1 text-center full-width">
{{ t('ticketComponents.zoneBreakdown') }} {{ t('ticketComponents.zoneBreakdown') }}
</span> </span>
</QCardSection> </QCardSection>
<QCardSection horizontal> <QCardSection horizontal>
<span class="q-mr-xs color-vn-label"> {{ t('basicData.price') }}: </span> <span class="q-mr-xs color-vn-label">
{{ t('basicData.price') }}:
</span>
<span>{{ toCurrency(ticketData?.zonePrice, 'EUR', 2) }}</span> <span>{{ toCurrency(ticketData?.zonePrice, 'EUR', 2) }}</span>
</QCardSection> </QCardSection>
<QCardSection horizontal> <QCardSection horizontal>
@ -234,14 +247,18 @@ onMounted(() => {
<span>{{ toCurrency(ticketData?.zoneBonus, 'EUR', 2) }}</span> <span>{{ toCurrency(ticketData?.zoneBonus, 'EUR', 2) }}</span>
</QCardSection> </QCardSection>
<QCardSection horizontal> <QCardSection horizontal>
<span class="q-mr-xs color-vn-label"> {{ t('ticketList.zone') }}: </span> <span class="q-mr-xs color-vn-label">
{{ t('ticketList.zone') }}:
</span>
<span class="link"> <span class="link">
{{ dashIfEmpty(ticketData?.zone?.name) }} {{ dashIfEmpty(ticketData?.zone?.name) }}
<ZoneDescriptorProxy :id="ticketData?.zone?.id" /> <ZoneDescriptorProxy :id="ticketData?.zone?.id" />
</span> </span>
</QCardSection> </QCardSection>
<QCardSection v-if="ticketData?.zone?.isVolumetric" horizontal> <QCardSection v-if="ticketData?.zone?.isVolumetric" horizontal>
<span class="q-mr-xs color-vn-label"> {{ t('volume.volume') }}: </span> <span class="q-mr-xs color-vn-label">
{{ t('volume.volume') }}:
</span>
<span>{{ ticketVolume }}</span> <span>{{ ticketVolume }}</span>
</QCardSection> </QCardSection>
<QCardSection horizontal> <QCardSection horizontal>
@ -251,7 +268,12 @@ onMounted(() => {
<span>{{ dashIfEmpty(ticketData?.packages) }}</span> <span>{{ dashIfEmpty(ticketData?.packages) }}</span>
</QCardSection> </QCardSection>
</QCard> </QCard>
<QCard class="q-pa-sm color-vn-text" bordered flat style="border-color: black"> <QCard
class="q-pa-sm color-vn-text"
bordered
flat
style="border-color: black"
>
<QCardSection horizontal> <QCardSection horizontal>
<span class="text-weight-bold text-subtitle1 text-center full-width"> <span class="text-weight-bold text-subtitle1 text-center full-width">
{{ t('ticketComponents.theoricalCost') }} {{ t('ticketComponents.theoricalCost') }}
@ -264,7 +286,8 @@ onMounted(() => {
<span>{{ toCurrency(theoricalCost, 'EUR', 2) }}</span> <span>{{ toCurrency(theoricalCost, 'EUR', 2) }}</span>
</QCardSection> </QCardSection>
</QCard> </QCard>
</Teleport> </template>
</RightMenu>
<VnTable <VnTable
ref="tableRef" ref="tableRef"
data-key="TicketComponents" data-key="TicketComponents"

View File

@ -239,7 +239,6 @@ function ticketFilter(ticket) {
<i18n> <i18n>
es: es:
This ticket is deleted: Este ticket está eliminado This ticket is deleted: Este ticket está eliminado
Go to module index: Ir al índice del modulo
Client inactive: Cliente inactivo Client inactive: Cliente inactivo
Client not checked: Cliente no verificado Client not checked: Cliente no verificado
Client has debt: Cliente con deuda Client has debt: Cliente con deuda

View File

@ -16,7 +16,6 @@ import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import TicketSaleMoreActions from './TicketSaleMoreActions.vue'; import TicketSaleMoreActions from './TicketSaleMoreActions.vue';
import TicketTransfer from './TicketTransfer.vue'; import TicketTransfer from './TicketTransfer.vue';
import { useStateStore } from 'stores/useStateStore';
import { toCurrency, toPercentage } from 'src/filters'; import { toCurrency, toPercentage } from 'src/filters';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import { useVnConfirm } from 'composables/useVnConfirm'; import { useVnConfirm } from 'composables/useVnConfirm';
@ -25,10 +24,10 @@ import axios from 'axios';
import VnTable from 'src/components/VnTable/VnTable.vue'; import VnTable from 'src/components/VnTable/VnTable.vue';
import VnUsesMana from 'src/components/ui/VnUsesMana.vue'; import VnUsesMana from 'src/components/ui/VnUsesMana.vue';
import VnConfirm from 'src/components/ui/VnConfirm.vue'; import VnConfirm from 'src/components/ui/VnConfirm.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const { notify } = useNotify(); const { notify } = useNotify();
const { openConfirmationModal } = useVnConfirm(); const { openConfirmationModal } = useVnConfirm();
@ -419,7 +418,6 @@ const setTransferParams = async () => {
}; };
onMounted(async () => { onMounted(async () => {
stateStore.rightDrawer = true;
getConfig(); getConfig();
}); });
@ -620,7 +618,8 @@ watch(
</QBtnGroup> </QBtnGroup>
</template> </template>
</VnSubToolbar> </VnSubToolbar>
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> <RightMenu>
<template #right-panel>
<div <div
class="q-pa-md q-mb-md q-ma-md color-vn-text" class="q-pa-md q-mb-md q-ma-md color-vn-text"
style="border: 2px solid black" style="border: 2px solid black"
@ -632,17 +631,25 @@ watch(
<span>{{ toCurrency(store.data?.totalWithoutVat) }}</span> <span>{{ toCurrency(store.data?.totalWithoutVat) }}</span>
</QCardSection> </QCardSection>
<QCardSection class="justify-end text-subtitle1" horizontal> <QCardSection class="justify-end text-subtitle1" horizontal>
<span class="q-mr-xs color-vn-label"> {{ t('ticketSale.tax') }}: </span> <span class="q-mr-xs color-vn-label">
{{ t('ticketSale.tax') }}:
</span>
<span>{{ <span>{{
toCurrency(store.data?.totalWithVat - store.data?.totalWithoutVat) toCurrency(store.data?.totalWithVat - store.data?.totalWithoutVat)
}}</span> }}</span>
</QCardSection> </QCardSection>
<QCardSection class="justify-end text-weight-bold text-subtitle1" horizontal> <QCardSection
<span class="q-mr-xs color-vn-label"> {{ t('basicData.total') }}: </span> class="justify-end text-weight-bold text-subtitle1"
horizontal
>
<span class="q-mr-xs color-vn-label">
{{ t('basicData.total') }}:
</span>
<span>{{ toCurrency(store.data?.totalWithVat) }}</span> <span>{{ toCurrency(store.data?.totalWithVat) }}</span>
</QCardSection> </QCardSection>
</div> </div>
</Teleport> </template>
</RightMenu>
<VnTable <VnTable
ref="tableRef" ref="tableRef"
data-key="TicketSales" data-key="TicketSales"

View File

@ -259,10 +259,9 @@ const moveTicketsAdvance = async () => {
destinationId: ticket.id, destinationId: ticket.id,
originShipped: ticket.futureShipped, originShipped: ticket.futureShipped,
destinationShipped: ticket.shipped, destinationShipped: ticket.shipped,
workerFk: ticket.workerFk, salesPersonFk: ticket.workerFk,
}); });
} }
const params = { tickets: ticketsToMove }; const params = { tickets: ticketsToMove };
await axios.post('Tickets/merge', params); await axios.post('Tickets/merge', params);
vnTableRef.value.reload(); vnTableRef.value.reload();

View File

@ -244,13 +244,15 @@ const totalPriceColor = (totalWithVat) =>
isLessThan50(totalWithVat) ? 'warning' : 'transparent'; isLessThan50(totalWithVat) ? 'warning' : 'transparent';
const moveTicketsFuture = async () => { const moveTicketsFuture = async () => {
const ticketsToMove = selectedTickets.value.map((ticket) => ({ const ticketsToMove = selectedTickets.value.map((ticket) => {
return {
originId: ticket.id, originId: ticket.id,
destinationId: ticket.futureId, destinationId: ticket.futureId,
originShipped: ticket.shipped, originShipped: ticket.shipped,
destinationShipped: ticket.futureShipped, destinationShipped: ticket.futureShipped,
workerFk: ticket.workerFk, salesPersonFk: ticket.salesPersonFk,
})); };
});
let params = { tickets: ticketsToMove }; let params = { tickets: ticketsToMove };
await axios.post('Tickets/merge', params); await axios.post('Tickets/merge', params);

View File

@ -465,7 +465,7 @@ function setReference(data) {
exprBuilder, exprBuilder,
}" }"
> >
<template #rightMenu> <template #advanced-menu>
<TicketFilter data-key="TicketList" /> <TicketFilter data-key="TicketList" />
</template> </template>
<template #body> <template #body>

View File

@ -14,6 +14,9 @@ const filter = {
'warehouseOutFk', 'warehouseOutFk',
'cargoSupplierFk', 'cargoSupplierFk',
'agencyModeFk', 'agencyModeFk',
'isRaid',
'isDelivered',
'isReceived',
], ],
include: [ include: [
{ {

View File

@ -85,7 +85,6 @@ const setData = (entity) => (data.value = useCardDescription(entity.ref, entity.
<i18n> <i18n>
es: es:
Go to module index: Ir al índice del módulo
The travel will be deleted: El envío será eliminado The travel will be deleted: El envío será eliminado
Do you want to delete this travel?: ¿Quieres eliminar este envío? Do you want to delete this travel?: ¿Quieres eliminar este envío?
All travels with current agency: Todos los envíos con la agencia actual All travels with current agency: Todos los envíos con la agencia actual

View File

@ -11,7 +11,7 @@ const { t } = useI18n();
const counters = ref({ const counters = ref({
alquilerBandeja: { count: 0, id: 96001, title: 'CC Bandeja', isTray: true }, alquilerBandeja: { count: 0, id: 96001, title: 'CC Bandeja', isTray: true },
bandejaRota: { count: 0, id: 88381, title: 'CC Bandeja Rota', isTray: true }, bandejaRota: { count: 0, id: 88381, title: 'CC Bandeja Rota', isTray: true },
carryOficial: { count: 0, id: 96000, title: 'CC Carry OFICIAL TAG5' }, carryOficial: { count: 0, id: 96000, title: 'CC Carry OFICIAL TAG6' },
candadoRojo: { count: 0, id: 96002, title: 'CC Carry NO OFICIAL' }, candadoRojo: { count: 0, id: 96002, title: 'CC Carry NO OFICIAL' },
sacadores: { count: 0, id: 142260, title: 'CC Sacadores' }, sacadores: { count: 0, id: 142260, title: 'CC Sacadores' },
sinChapa: { count: 0, id: 2214, title: 'DC Carry Sin Placa CC' }, sinChapa: { count: 0, id: 2214, title: 'DC Carry Sin Placa CC' },

View File

@ -1,18 +1,16 @@
<script setup> <script setup>
import { nextTick, ref, watch } from 'vue'; import { nextTick, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import WorkerCalendarFilter from 'pages/Worker/Card/WorkerCalendarFilter.vue'; import WorkerCalendarFilter from 'pages/Worker/Card/WorkerCalendarFilter.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import WorkerCalendarItem from 'pages/Worker/Card/WorkerCalendarItem.vue'; import WorkerCalendarItem from 'pages/Worker/Card/WorkerCalendarItem.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import { useStateStore } from 'stores/useStateStore';
import axios from 'axios'; import axios from 'axios';
import { useRouter } from 'vue-router';
const router = useRouter(); const router = useRouter();
const stateStore = useStateStore();
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const workerIsFreelance = ref(); const workerIsFreelance = ref();
@ -171,7 +169,8 @@ watch([year, businessFk], () => refreshData());
ref="WorkerFreelanceRef" ref="WorkerFreelanceRef"
auto-load auto-load
/> />
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> <RightMenu>
<template #right-panel>
<WorkerCalendarFilter <WorkerCalendarFilter
ref="workerCalendarFilterRef" ref="workerCalendarFilterRef"
v-model:business-fk="businessFk" v-model:business-fk="businessFk"
@ -180,7 +179,8 @@ watch([year, businessFk], () => refreshData());
:contract-holidays="contractHolidays" :contract-holidays="contractHolidays"
:year-holidays="yearHolidays" :year-holidays="yearHolidays"
/> />
</Teleport> </template>
</RightMenu>
<QPage class="column items-center"> <QPage class="column items-center">
<QCard v-if="workerIsFreelance"> <QCard v-if="workerIsFreelance">
<QCardSection class="text-center"> <QCardSection class="text-center">

View File

@ -6,7 +6,7 @@ import { useRoute } from 'vue-router';
import QCalendarMonthWrapper from 'src/components/ui/QCalendarMonthWrapper.vue'; import QCalendarMonthWrapper from 'src/components/ui/QCalendarMonthWrapper.vue';
import { QCalendarMonth } from '@quasar/quasar-ui-qcalendar/src/index.js'; import { QCalendarMonth } from '@quasar/quasar-ui-qcalendar/src/index.js';
import '@quasar/quasar-ui-qcalendar/src/QCalendarVariables.sass'; import '@quasar/quasar-ui-qcalendar/src/QCalendarVariables.scss';
import { useWeekdayStore } from 'src/stores/useWeekdayStore'; import { useWeekdayStore } from 'src/stores/useWeekdayStore';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';

View File

@ -10,6 +10,7 @@ import axios from 'axios';
import VnImg from 'src/components/ui/VnImg.vue'; import VnImg from 'src/components/ui/VnImg.vue';
import EditPictureForm from 'components/EditPictureForm.vue'; import EditPictureForm from 'components/EditPictureForm.vue';
import WorkerDescriptorMenu from './WorkerDescriptorMenu.vue'; import WorkerDescriptorMenu from './WorkerDescriptorMenu.vue';
import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue';
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -115,10 +116,13 @@ const handlePhotoUpdated = (evt = false) => {
:value="entity.user?.emailUser?.email" :value="entity.user?.emailUser?.email"
copy copy
/> />
<VnLv <VnLv :label="t('worker.list.department')">
:label="t('worker.list.department')" <template #value>
:value="entity.department ? entity.department.department.name : null" <span class="link" v-text="entity.department?.department?.name" />
/> <DepartmentDescriptorProxy :id="entity.department?.department?.id" />
</template>
</VnLv>
<VnLv :value="entity.phone"> <VnLv :value="entity.phone">
<template #label> <template #label>
{{ t('globals.phone') }} {{ t('globals.phone') }}

View File

@ -10,6 +10,7 @@ import WorkerTimeForm from 'pages/Worker/Card/WorkerTimeForm.vue';
import WorkerTimeReasonForm from 'pages/Worker/Card/WorkerTimeReasonForm.vue'; import WorkerTimeReasonForm from 'pages/Worker/Card/WorkerTimeReasonForm.vue';
import WorkerDateLabel from './WorkerDateLabel.vue'; import WorkerDateLabel from './WorkerDateLabel.vue';
import WorkerTimeControlCalendar from 'pages/Worker/Card/WorkerTimeControlCalendar.vue'; import WorkerTimeControlCalendar from 'pages/Worker/Card/WorkerTimeControlCalendar.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import axios from 'axios'; import axios from 'axios';
@ -483,7 +484,8 @@ onMounted(async () => {
</QBtnGroup> </QBtnGroup>
</div> </div>
</Teleport> </Teleport>
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> <RightMenu>
<template #right-panel>
<div class="q-pa-md q-mb-md" style="border: 2px solid #222"> <div class="q-pa-md q-mb-md" style="border: 2px solid #222">
<QCardSection horizontal> <QCardSection horizontal>
<span class="text-weight-bold text-subtitle1 text-center full-width"> <span class="text-weight-bold text-subtitle1 text-center full-width">
@ -509,7 +511,8 @@ onMounted(async () => {
@click-date="onInputChange" @click-date="onInputChange"
@on-moved="getMailStates" @on-moved="getMailStates"
/> />
</Teleport> </template>
</RightMenu>
<QPage class="column items-center"> <QPage class="column items-center">
<QTable :columns="columns" :rows="['']" hide-bottom class="full-width"> <QTable :columns="columns" :rows="['']" hide-bottom class="full-width">
<template #header="props"> <template #header="props">

View File

@ -98,6 +98,15 @@ const getLocale = (label) => {
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem>
<QItemSection>
<QCheckbox
:label="t('globals.params.myTeam')"
v-model="params.myTeam"
toggle-indeterminate
/>
</QItemSection>
</QItem>
</template> </template>
</VnFilterPanel> </VnFilterPanel>
</template> </template>

View File

@ -196,10 +196,10 @@ async function autofillBic(worker) {
prefix="workerSearch" prefix="workerSearch"
:array-data-props="{ :array-data-props="{
url: 'Workers/filter', url: 'Workers/filter',
order: ['id DESC'], order: 'id DESC',
}" }"
> >
<template #rightMenu> <template #advanced-menu>
<WorkerFilter data-key="WorkerList" /> <WorkerFilter data-key="WorkerList" />
</template> </template>
<template #body> <template #body>

View File

@ -66,7 +66,3 @@ const setData = (entity) => {
</CardDescriptor> </CardDescriptor>
</template> </template>
<i18n>
es:
Go to module index: Ir al índice del módulo
</i18n>

View File

@ -7,7 +7,7 @@ import { useRoute } from 'vue-router';
import ZoneClosingTable from './ZoneClosingTable.vue'; import ZoneClosingTable from './ZoneClosingTable.vue';
import QCalendarMonthWrapper from 'src/components/ui/QCalendarMonthWrapper.vue'; import QCalendarMonthWrapper from 'src/components/ui/QCalendarMonthWrapper.vue';
import { QCalendarMonth } from '@quasar/quasar-ui-qcalendar/src/index.js'; import { QCalendarMonth } from '@quasar/quasar-ui-qcalendar/src/index.js';
import '@quasar/quasar-ui-qcalendar/src/QCalendarVariables.sass'; import '@quasar/quasar-ui-qcalendar/src/QCalendarVariables.scss';
import { useWeekdayStore } from 'src/stores/useWeekdayStore'; import { useWeekdayStore } from 'src/stores/useWeekdayStore';
import axios from 'axios'; import axios from 'axios';

View File

@ -90,7 +90,7 @@ const redirectToZoneSummary = (id) => {
color="primary" color="primary"
@click.stop="viewSummary(props.row.id, ZoneSummary)" @click.stop="viewSummary(props.row.id, ZoneSummary)"
> >
<QTooltip>{{ t('zoneClosingTable.preview') }}</QTooltip> <QTooltip>{{ t('globals.preview') }}</QTooltip>
</QIcon> </QIcon>
</div> </div>
</QTd> </QTd>

View File

@ -50,8 +50,7 @@ deliveryPanel:
postcode: Postcode postcode: Postcode
query: Query query: Query
noEventsWarning: No service for the specified zone noEventsWarning: No service for the specified zone
zoneClosingTable:
preview: Preview
warehouses: warehouses:
deleteTitle: This item will be deleted deleteTitle: This item will be deleted
deleteSubtitle: Are you sure you want to continue? deleteSubtitle: Are you sure you want to continue?

View File

@ -1,4 +1,4 @@
import { route } from 'quasar/wrappers'; import { route as defineRouter } from 'quasar/wrappers';
import { import {
createRouter, createRouter,
createMemoryHistory, createMemoryHistory,
@ -8,14 +8,14 @@ import {
import routes from './routes'; import routes from './routes';
import { i18n } from 'src/boot/i18n'; import { i18n } from 'src/boot/i18n';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import { useSession } from 'src/composables/useSession';
import { useRole } from 'src/composables/useRole'; import { useRole } from 'src/composables/useRole';
import { useUserConfig } from 'src/composables/useUserConfig'; import { useUserConfig } from 'src/composables/useUserConfig';
import { useTokenConfig } from 'src/composables/useTokenConfig'; import { useTokenConfig } from 'src/composables/useTokenConfig';
import { useAcl } from 'src/composables/useAcl'; import { useAcl } from 'src/composables/useAcl';
import { isLoggedIn } from 'src/utils/session';
import { useSession } from 'src/composables/useSession';
const state = useState(); let session = null;
const session = useSession();
const { t, te } = i18n.global; const { t, te } = i18n.global;
const createHistory = process.env.SERVER const createHistory = process.env.SERVER
@ -43,11 +43,12 @@ const Router = createRouter({
* with the Router instance. * with the Router instance.
*/ */
export { Router }; export { Router };
export default route(function (/* { store, ssrContext } */) { export default defineRouter(function (/* { store, ssrContext } */) {
const state = useState();
Router.beforeEach(async (to, from, next) => { Router.beforeEach(async (to, from, next) => {
const { isLoggedIn } = session; if (!session) session = useSession();
const outLayout = Router.options.routes[0].children.map((r) => r.name); const outLayout = Router.options.routes[0].children.map((r) => r.name);
if (!isLoggedIn() && !outLayout.includes(to.name)) { if (!session.isLoggedIn() && !outLayout.includes(to.name)) {
return next({ name: 'Login', query: { redirect: to.fullPath } }); return next({ name: 'Login', query: { redirect: to.fullPath } });
} }

View File

@ -1,24 +1,12 @@
import { RouterView } from 'vue-router'; import { RouterView } from 'vue-router';
export default { const customerCard = {
path: '/customer', name: 'CustomerCard',
name: 'Customer', path: ':id',
component: () => import('src/pages/Customer/Card/CustomerCard.vue'),
redirect: { name: 'CustomerSummary' },
meta: { meta: {
title: 'customers', menu: [
icon: 'vn:client',
moduleName: 'Customer',
keyBinding: 'c',
},
component: RouterView,
redirect: { name: 'CustomerMain' },
menus: {
main: [
'CustomerList',
'CustomerPayments',
'CustomerNotifications',
'CustomerDefaulter',
],
card: [
'CustomerBasicData', 'CustomerBasicData',
'CustomerFiscalData', 'CustomerFiscalData',
'CustomerBillingData', 'CustomerBillingData',
@ -35,70 +23,6 @@ export default {
'CustomerOthers', 'CustomerOthers',
], ],
}, },
children: [
{
path: '',
name: 'CustomerMain',
component: () => import('src/components/common/VnModule.vue'),
redirect: { name: 'CustomerList' },
children: [
{
path: 'list',
name: 'CustomerList',
meta: {
title: 'list',
icon: 'view_list',
},
component: () => import('src/pages/Customer/CustomerList.vue'),
},
{
path: 'create',
name: 'CustomerCreate',
meta: {
title: 'customerCreate',
icon: 'add',
},
component: () => import('src/pages/Customer/CustomerCreate.vue'),
},
{
path: 'payments',
name: 'CustomerPayments',
meta: {
title: 'webPayments',
icon: 'vn:onlinepayment',
},
component: () =>
import('src/pages/Customer/Payments/CustomerPayments.vue'),
},
{
path: 'notifications',
name: 'CustomerNotifications',
meta: {
title: 'notifications',
icon: 'campaign',
},
component: () =>
import(
'src/pages/Customer/Notifications/CustomerNotifications.vue'
),
},
{
path: 'defaulter',
name: 'CustomerDefaulter',
meta: {
title: 'defaulter',
icon: 'vn:defaulter',
},
component: () =>
import('src/pages/Customer/Defaulter/CustomerDefaulter.vue'),
},
],
},
{
name: 'CustomerCard',
path: ':id',
component: () => import('src/pages/Customer/Card/CustomerCard.vue'),
redirect: { name: 'CustomerSummary' },
children: [ children: [
{ {
name: 'CustomerSummary', name: 'CustomerSummary',
@ -107,8 +31,7 @@ export default {
title: 'summary', title: 'summary',
icon: 'launch', icon: 'launch',
}, },
component: () => component: () => import('src/pages/Customer/Card/CustomerSummary.vue'),
import('src/pages/Customer/Card/CustomerSummary.vue'),
}, },
{ {
path: 'basic-data', path: 'basic-data',
@ -503,6 +426,91 @@ export default {
], ],
}, },
], ],
};
export default {
name: 'Customer',
path: '/customer',
meta: {
title: 'customers',
icon: 'vn:client',
moduleName: 'Customer',
keyBinding: 'c',
menu: [
'CustomerList',
'CustomerPayments',
'CustomerNotifications',
'CustomerDefaulter',
],
},
component: RouterView,
redirect: { name: 'CustomerMain' },
children: [
{
name: 'CustomerMain',
path: '',
component: () => import('src/components/common/VnModule.vue'),
redirect: { name: 'CustomerIndexMain' },
children: [
{
path: '',
name: 'CustomerIndexMain',
redirect: { name: 'CustomerList' },
component: () => import('src/pages/Customer/CustomerList.vue'),
children: [
{
name: 'CustomerList',
path: 'list',
meta: {
title: 'list',
icon: 'view_list',
},
},
customerCard,
],
},
{
path: 'create',
name: 'CustomerCreate',
meta: {
title: 'customerCreate',
icon: 'add',
},
component: () => import('src/pages/Customer/CustomerCreate.vue'),
},
{
path: 'payments',
name: 'CustomerPayments',
meta: {
title: 'webPayments',
icon: 'vn:onlinepayment',
},
component: () =>
import('src/pages/Customer/Payments/CustomerPayments.vue'),
},
{
path: 'notifications',
name: 'CustomerNotifications',
meta: {
title: 'notifications',
icon: 'campaign',
},
component: () =>
import(
'src/pages/Customer/Notifications/CustomerNotifications.vue'
),
},
{
path: 'defaulter',
name: 'CustomerDefaulter',
meta: {
title: 'defaulter',
icon: 'vn:defaulter',
},
component: () =>
import('src/pages/Customer/Defaulter/CustomerDefaulter.vue'),
},
],
}, },
], ],
}; };

View File

@ -15,7 +15,6 @@ import Entry from './entry';
import roadmap from './roadmap'; import roadmap from './roadmap';
import Parking from './parking'; import Parking from './parking';
import Agency from './agency'; import Agency from './agency';
import ItemType from './itemType';
import Zone from './zone'; import Zone from './zone';
import Account from './account'; import Account from './account';
import Monitor from './monitor'; import Monitor from './monitor';
@ -38,7 +37,6 @@ export default [
roadmap, roadmap,
Parking, Parking,
Agency, Agency,
ItemType,
Zone, Zone,
Account, Account,
Monitor, Monitor,

View File

@ -1,109 +1,23 @@
import { RouterView } from 'vue-router'; import { RouterView } from 'vue-router';
export default { const itemCard = {
path: '/item',
name: 'Item',
meta: {
title: 'items',
icon: 'vn:item',
moduleName: 'Item',
keyBinding: 'a',
},
component: RouterView,
redirect: { name: 'ItemMain' },
menus: {
main: [
'ItemList',
'WasteBreakdown',
'ItemFixedPrice',
'ItemRequest',
'ItemTypeList',
],
card: [
'ItemBasicData',
'ItemLog',
'ItemDiary',
'ItemTags',
'ItemTax',
'ItemBotanical',
'ItemBarcode',
'ItemShelving',
'ItemLastEntries',
'ItemTags',
],
},
children: [
{
path: '',
name: 'ItemMain',
component: () => import('src/components/common/VnModule.vue'),
redirect: { name: 'ItemList' },
children: [
{
path: 'list',
name: 'ItemList',
meta: {
title: 'list',
icon: 'view_list',
},
component: () => import('src/pages/Item/ItemList.vue'),
},
{
path: 'request',
name: 'ItemRequest',
meta: {
title: 'buyRequest',
icon: 'vn:buyrequest',
},
component: () => import('src/pages/Item/ItemRequest.vue'),
},
{
path: 'waste-breakdown',
name: 'WasteBreakdown',
meta: {
title: 'wasteBreakdown',
icon: 'vn:claims',
},
beforeEnter: (to, from, next) => {
next({ name: 'ItemList' });
window.location.href =
'https://grafana.verdnatura.es/d/TTNXQAxVk';
},
},
{
path: 'fixed-price',
name: 'ItemFixedPrice',
meta: {
title: 'fixedPrice',
icon: 'vn:fixedPrice',
},
component: () => import('src/pages/Item/ItemFixedPrice.vue'),
},
{
path: 'create',
name: 'ItemCreate',
meta: {
title: 'itemCreate',
},
component: () => import('src/pages/Item/ItemCreate.vue'),
},
{
path: 'item-type-list',
name: 'ItemTypeList',
meta: {
title: 'family',
icon: 'contact_support',
},
component: () => import('src/pages/Item/ItemTypeList.vue'),
},
],
},
{
name: 'ItemCard', name: 'ItemCard',
path: ':id', path: ':id',
component: () => import('src/pages/Item/Card/ItemCard.vue'), component: () => import('src/pages/Item/Card/ItemCard.vue'),
redirect: { name: 'ItemSummary' }, redirect: { name: 'ItemSummary' },
meta: {
menu: [
'ItemBasicData',
'ItemTags',
'ItemLastEntries',
'ItemTax',
'ItemBotanical',
'ItemShelving',
'ItemBarcode',
'ItemDiary',
'ItemLog',
],
},
children: [ children: [
{ {
name: 'ItemSummary', name: 'ItemSummary',
@ -155,7 +69,7 @@ export default {
name: 'ItemBotanical', name: 'ItemBotanical',
meta: { meta: {
title: 'botanical', title: 'botanical',
icon: 'local_florist', icon: 'vn:botanical',
}, },
component: () => import('src/pages/Item/Card/ItemBotanical.vue'), component: () => import('src/pages/Item/Card/ItemBotanical.vue'),
}, },
@ -196,6 +110,138 @@ export default {
component: () => import('src/pages/Item/Card/ItemLog.vue'), component: () => import('src/pages/Item/Card/ItemLog.vue'),
}, },
], ],
};
const itemTypeCard = {
name: 'ItemTypeCard',
path: ':id',
component: () => import('src/pages/Item/ItemType/Card/ItemTypeCard.vue'),
redirect: { name: 'ItemTypeSummary' },
meta: {
menu: ['ItemTypeBasicData', 'ItemTypeLog'],
},
children: [
{
path: 'summary',
name: 'ItemTypeSummary',
meta: {
title: 'summary',
icon: 'launch',
},
component: () => import('src/pages/Item/ItemType/Card/ItemTypeSummary.vue'),
},
{
path: 'basic-data',
name: 'ItemTypeBasicData',
meta: {
title: 'basicData',
icon: 'vn:settings',
},
component: () => import('src/pages/Item/ItemType/Card/ItemTypeBasicData.vue'),
},
{
path: 'log',
name: 'ItemTypeLog',
meta: {
title: 'log',
icon: 'vn:History',
},
component: () => import('src/pages/Item/ItemType/Card/ItemTypeLog.vue'),
},
],
};
export default {
name: 'Item',
path: '/item',
meta: {
title: 'items',
icon: 'vn:item',
moduleName: 'Item',
keyBinding: 'a',
menu: [
'ItemList',
'WasteBreakdown',
'ItemFixedPrice',
'ItemRequest',
'ItemTypeList',
],
},
component: RouterView,
redirect: { name: 'ItemMain' },
children: [
{
name: 'ItemMain',
path: '',
component: () => import('src/components/common/VnModule.vue'),
redirect: { name: 'ItemIndexMain' },
children: [
{
path: '',
name: 'ItemIndexMain',
redirect: { name: 'ItemList' },
component: () => import('src/pages/Item/ItemList.vue'),
children: [
{
name: 'ItemList',
path: 'list',
meta: {
title: 'list',
icon: 'view_list',
},
},
itemCard,
],
},
{
path: 'request',
name: 'ItemRequest',
meta: {
title: 'buyRequest',
icon: 'vn:buyrequest',
},
component: () => import('src/pages/Item/ItemRequest.vue'),
},
{
path: 'waste-breakdown',
name: 'WasteBreakdown',
meta: {
title: 'wasteBreakdown',
icon: 'vn:claims',
},
beforeEnter: (to, from, next) => {
next({ name: 'ItemList' });
window.location.href =
'https://grafana.verdnatura.es/d/TTNXQAxVk';
},
},
{
path: 'fixed-price',
name: 'ItemFixedPrice',
meta: {
title: 'fixedPrice',
icon: 'vn:fixedPrice',
},
component: () => import('src/pages/Item/ItemFixedPrice.vue'),
},
{
path: 'item-type',
name: 'ItemTypeMain',
redirect: { name: 'ItemTypeList' },
component: () => import('src/pages/Item/ItemTypeList.vue'),
children: [
{
name: 'ItemTypeList',
path: 'list',
meta: {
title: 'family',
icon: 'contact_support',
},
},
itemTypeCard,
],
},
],
}, },
], ],
}; };

View File

@ -1,56 +0,0 @@
import { RouterView } from 'vue-router';
export default {
path: '/item/item-type',
name: 'ItemType',
meta: {
title: 'itemType',
icon: 'contact_support',
moduleName: 'ItemType',
},
component: RouterView,
redirect: { name: 'ItemTypeList' },
menus: {
main: [],
card: ['ItemTypeBasicData', 'ItemTypeLog'],
},
children: [
{
name: 'ItemTypeCard',
path: ':id',
component: () => import('src/pages/Item/ItemType/Card/ItemTypeCard.vue'),
redirect: { name: 'ItemTypeSummary' },
children: [
{
name: 'ItemTypeSummary',
path: 'summary',
meta: {
title: 'summary',
},
component: () =>
import('src/pages/Item/ItemType/Card/ItemTypeSummary.vue'),
},
{
name: 'ItemTypeBasicData',
path: 'basic-data',
meta: {
title: 'basicData',
icon: 'vn:settings',
},
component: () =>
import('src/pages/Item/ItemType/Card/ItemTypeBasicData.vue'),
},
{
path: 'log',
name: 'ItemTypeLog',
meta: {
title: 'log',
icon: 'vn:History',
},
component: () =>
import('src/pages/Item/ItemType/Card/ItemTypeLog.vue'),
},
],
},
],
};

View File

@ -168,7 +168,7 @@ const ticketCard = {
name: 'TicketBoxing', name: 'TicketBoxing',
meta: { meta: {
title: 'boxing', title: 'boxing',
icon: 'science', icon: 'view_in_ar',
}, },
component: () => import('src/pages/Ticket/Card/TicketBoxing.vue'), component: () => import('src/pages/Ticket/Card/TicketBoxing.vue'),
}, },

View File

@ -9,7 +9,6 @@ import invoiceIn from './modules/invoiceIn';
import wagon from './modules/wagon'; import wagon from './modules/wagon';
import supplier from './modules/supplier'; import supplier from './modules/supplier';
import travel from './modules/travel'; import travel from './modules/travel';
import ItemType from './modules/itemType';
import shelving from 'src/router/modules/shelving'; import shelving from 'src/router/modules/shelving';
import order from 'src/router/modules/order'; import order from 'src/router/modules/order';
import entry from 'src/router/modules/entry'; import entry from 'src/router/modules/entry';
@ -88,7 +87,6 @@ const routes = [
entry, entry,
parking, parking,
agency, agency,
ItemType,
zone, zone,
account, account,
{ {

View File

@ -5,6 +5,7 @@ export const useStateStore = defineStore('stateStore', () => {
const isMounted = ref(false); const isMounted = ref(false);
const leftDrawer = ref(false); const leftDrawer = ref(false);
const rightDrawer = ref(false); const rightDrawer = ref(false);
const rightAdvancedDrawer = ref(false);
const subToolbar = ref(false); const subToolbar = ref(false);
function toggleLeftDrawer() { function toggleLeftDrawer() {
@ -15,6 +16,10 @@ export const useStateStore = defineStore('stateStore', () => {
rightDrawer.value = !rightDrawer.value; rightDrawer.value = !rightDrawer.value;
} }
function toggleRightAdvancedDrawer() {
rightAdvancedDrawer.value = !rightAdvancedDrawer.value;
}
function rightDrawerChangeValue(value) { function rightDrawerChangeValue(value) {
rightDrawer.value = value; rightDrawer.value = value;
} }
@ -46,10 +51,12 @@ export const useStateStore = defineStore('stateStore', () => {
return { return {
leftDrawer, leftDrawer,
rightDrawer, rightDrawer,
rightAdvancedDrawer,
setMounted, setMounted,
isHeaderMounted, isHeaderMounted,
toggleLeftDrawer, toggleLeftDrawer,
toggleRightDrawer, toggleRightDrawer,
toggleRightAdvancedDrawer,
isLeftDrawerShown, isLeftDrawerShown,
isRightDrawerShown, isRightDrawerShown,
isSubToolbarShown, isSubToolbarShown,

Some files were not shown because too many files have changed in this diff Show More