Merge branch 'dev' into 7136_vnselectFilter_paginate

This commit is contained in:
Javier Segarra 2024-07-05 13:57:58 +02:00
commit 0907bfb8e7
143 changed files with 11719 additions and 8298 deletions

View File

@ -1,12 +1,11 @@
{ {
"name": "salix-front", "name": "salix-front",
"version": "24.28.1", "version": "24.30.1",
"description": "Salix frontend", "description": "Salix frontend",
"productName": "Salix", "productName": "Salix",
"author": "Verdnatura", "author": "Verdnatura",
"private": true, "private": true,
"packageManager": "pnpm@8.15.1", "packageManager": "pnpm@8.15.1",
"type": "module",
"scripts": { "scripts": {
"lint": "eslint --ext .js,.vue ./", "lint": "eslint --ext .js,.vue ./",
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore", "format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
@ -23,7 +22,7 @@
"chromium": "^3.0.3", "chromium": "^3.0.3",
"croppie": "^2.6.5", "croppie": "^2.6.5",
"pinia": "^2.1.3", "pinia": "^2.1.3",
"quasar": "^2.16.4", "quasar": "^2.14.5",
"validator": "^13.9.0", "validator": "^13.9.0",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-i18n": "^9.2.2", "vue-i18n": "^9.2.2",
@ -32,7 +31,7 @@
"devDependencies": { "devDependencies": {
"@intlify/unplugin-vue-i18n": "^0.8.1", "@intlify/unplugin-vue-i18n": "^0.8.1",
"@pinia/testing": "^0.1.2", "@pinia/testing": "^0.1.2",
"@quasar/app-vite": "^1.9.3", "@quasar/app-vite": "^1.7.3",
"@quasar/quasar-app-extension-qcalendar": "4.0.0-beta.15", "@quasar/quasar-app-extension-qcalendar": "4.0.0-beta.15",
"@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",
@ -44,8 +43,7 @@
"eslint-plugin-vue": "^9.14.1", "eslint-plugin-vue": "^9.14.1",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"vite": "^2.5.10", "vitest": "^0.31.1"
"vitest": "^0.34.0"
}, },
"engines": { "engines": {
"node": "^20 || ^18 || ^16", "node": "^20 || ^18 || ^16",

File diff suppressed because it is too large Load Diff

BIN
public/no-image-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
public/no-image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
public/no-user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@ -1,104 +0,0 @@
/* eslint-env node */
/*
* This file runs in a Node context (it's NOT transpiled by Babel), so use only
* the ES6 features that are supported by your Node version. https://node.green/
*/
const { configure } = require('quasar/wrappers');
const VueI18nPlugin = require('@intlify/unplugin-vue-i18n/vite');
const path = require('path');
module.exports = configure(function (/* ctx */) {
return {
eslint: {
warnings: true,
errors: true,
},
boot: ['i18n', 'axios', 'vnDate', 'validations', 'quasar', 'quasar.defaults'],
css: ['app.scss'],
extras: ['roboto-font', 'material-icons-outlined', 'material-symbols-outlined'],
build: {
target: {
browser: ['es2022', 'edge88', 'firefox78', 'chrome87', 'safari13.1'],
node: 'node18',
},
vueRouterMode: 'hash',
rawDefine: {
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
},
extendViteConf(viteConf) {
delete viteConf.build.polyfillModulePreload;
viteConf.build.modulePreload = {
polyfill: false,
};
},
alias: {
composables: path.join(__dirname, './src/composables'),
filters: path.join(__dirname, './src/filters'),
},
vitePlugins: [
[
VueI18nPlugin({
runtimeOnly: false,
include: [
path.resolve(__dirname, './src/i18n/locale/**'),
path.resolve(__dirname, './src/pages/**/locale/**'),
],
}),
],
],
},
devServer: {
server: {
type: 'http',
},
proxy: {
'/api': {
target: 'http://0.0.0.0:3000',
logLevel: 'debug',
changeOrigin: true,
secure: false,
},
},
open: false,
},
framework: {
config: {
dark: 'auto',
},
lang: 'en-GB',
plugins: ['Notify', 'Dialog'],
all: 'auto',
autoImportComponentCase: 'pascal',
},
animations: [],
ssr: {
pwa: false,
prodPort: 3000,
middlewares: ['render'],
},
pwa: {
workboxMode: 'generateSW',
injectPwaMetaTags: true,
swFilename: 'sw.js',
manifestFilename: 'manifest.json',
useCredentialsForManifestTag: false,
},
cordova: {},
capacitor: {
hideSplashscreen: true,
},
electron: {
inspectPort: 5858,
bundler: 'packager',
packager: {},
builder: {
appId: 'salix-frontend',
},
},
bex: {
contentScripts: ['my-content-script'],
},
};
});

242
quasar.config.js Normal file
View File

@ -0,0 +1,242 @@
/* eslint-env node */
/*
* This file runs in a Node context (it's NOT transpiled by Babel), so use only
* the ES6 features that are supported by your Node version. https://node.green/
*/
// Configuration for your app
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js
const { configure } = require('quasar/wrappers');
const VueI18nPlugin = require('@intlify/unplugin-vue-i18n/vite');
const path = require('path');
module.exports = configure(function (/* ctx */) {
return {
eslint: {
// fix: true,
// include = [],
// exclude = [],
// rawOptions = {},
warnings: true,
errors: true,
},
// https://v2.quasar.dev/quasar-cli/prefetch-feature
// preFetch: true,
// app boot file (/src/boot)
// --> boot files are part of "main.js"
// https://v2.quasar.dev/quasar-cli/boot-files
boot: ['i18n', 'axios', 'vnDate', 'validations', 'quasar', 'quasar.defaults'],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
css: ['app.scss'],
// https://github.com/quasarframework/quasar/tree/dev/extras
extras: [
// 'ionicons-v4',
// 'mdi-v5',
// 'fontawesome-v6',
// 'eva-icons',
// 'themify',
// 'line-awesome',
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
'roboto-font',
'material-icons-outlined',
'material-symbols-outlined',
],
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
build: {
target: {
browser: ['es2022', 'edge88', 'firefox78', 'chrome87', 'safari13.1'],
node: 'node18',
},
vueRouterMode: 'hash', // available values: 'hash', 'history'
// vueRouterBase,
// vueDevtools,
// vueOptionsAPI: false,
// rebuildCache: true, // rebuilds Vite/linter/etc cache on startup
// publicPath: '/',
// analyze: true,
// env: {},
rawDefine: {
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
},
// ignorePublicFolder: true,
// minify: false,
// polyfillModulePreload: true,
// distDir
extendViteConf(viteConf) {
// FIXME: Delete deprecated property polyfillModulePreload
// that is set by Quasar by default
delete viteConf.build.polyfillModulePreload;
viteConf.build.modulePreload = {
polyfill: false,
};
},
// viteVuePluginOptions: {},
alias: {
composables: path.join(__dirname, './src/composables'),
filters: path.join(__dirname, './src/filters'),
},
vitePlugins: [
[
VueI18nPlugin({
runtimeOnly: false,
include: [
path.resolve(__dirname, './src/i18n/locale/**'),
path.resolve(__dirname, './src/pages/**/locale/**'),
],
}),
],
],
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
devServer: {
server: {
type: 'http',
},
proxy: {
'/api': {
target: 'http://0.0.0.0:3000',
logLevel: 'debug',
changeOrigin: true,
secure: false,
},
},
open: false,
},
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
framework: {
config: {
config: {
dark: 'auto',
},
},
lang: 'en-GB',
// iconSet: 'material-icons', // Quasar icon set
// lang: 'en-US', // Quasar language pack
// For special cases outside of where the auto-import strategy can have an impact
// (like functional components as one of the examples),
// you can manually specify Quasar components/directives to be available everywhere:
//
// components: [],
// directives: [],
// Quasar plugins
plugins: ['Notify', 'Dialog'],
all: 'auto',
autoImportComponentCase: 'pascal',
},
// animations: 'all', // --- includes all animations
// https://v2.quasar.dev/options/animations
animations: [],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#property-sourcefiles
// sourceFiles: {
// rootComponent: 'src/App.vue',
// router: 'src/router/index',
// store: 'src/store/index',
// registerServiceWorker: 'src-pwa/register-service-worker',
// serviceWorker: 'src-pwa/custom-service-worker',
// pwaManifestFile: 'src-pwa/manifest.json',
// electronMain: 'src-electron/electron-main',
// electronPreload: 'src-electron/electron-preload'
// },
// https://v2.quasar.dev/quasar-cli/developing-ssr/configuring-ssr
ssr: {
// ssrPwaHtmlFilename: 'offline.html', // do NOT use index.html as name!
// will mess up SSR
// extendSSRWebserverConf (esbuildConf) {},
// extendPackageJson (json) {},
pwa: false,
// manualStoreHydration: true,
// manualPostHydrationTrigger: true,
prodPort: 3000, // The default port that the production server should use
// (gets superseded if process.env.PORT is specified at runtime)
middlewares: [
'render', // keep this as last one
],
},
// https://v2.quasar.dev/quasar-cli/developing-pwa/configuring-pwa
pwa: {
workboxMode: 'generateSW', // or 'injectManifest'
injectPwaMetaTags: true,
swFilename: 'sw.js',
manifestFilename: 'manifest.json',
useCredentialsForManifestTag: false,
// useFilenameHashes: true,
// extendGenerateSWOptions (cfg) {}
// extendInjectManifestOptions (cfg) {},
// extendManifestJson (json) {}
// extendPWACustomSWConf (esbuildConf) {}
},
// Full list of options: https://v2.quasar.dev/quasar-cli/developing-cordova-apps/configuring-cordova
cordova: {
// noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing
},
// Full list of options: https://v2.quasar.dev/quasar-cli/developing-capacitor-apps/configuring-capacitor
capacitor: {
hideSplashscreen: true,
},
// Full list of options: https://v2.quasar.dev/quasar-cli/developing-electron-apps/configuring-electron
electron: {
// extendElectronMainConf (esbuildConf)
// extendElectronPreloadConf (esbuildConf)
inspectPort: 5858,
bundler: 'packager', // 'packager' or 'builder'
packager: {
// https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options
// OS X / Mac App Store
// appBundleId: '',
// appCategoryType: '',
// osxSign: '',
// protocol: 'myapp://path',
// Windows only
// win32metadata: { ... }
},
builder: {
// https://www.electron.build/configuration/configuration
appId: 'salix-frontend',
},
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex
bex: {
contentScripts: ['my-content-script'],
// extendBexScriptsConf (esbuildConf) {}
// extendBexManifestJson (json) {}
},
};
});

View File

@ -10,6 +10,7 @@ import VnPaginate from 'components/ui/VnPaginate.vue';
import VnConfirm from 'components/ui/VnConfirm.vue'; import VnConfirm from 'components/ui/VnConfirm.vue';
import SkeletonTable from 'components/ui/SkeletonTable.vue'; import SkeletonTable from 'components/ui/SkeletonTable.vue';
import { tMobile } from 'src/composables/tMobile'; import { tMobile } from 'src/composables/tMobile';
import VnSubToolbar from './ui/VnSubToolbar.vue';
const { push } = useRouter(); const { push } = useRouter();
const quasar = useQuasar(); const quasar = useQuasar();
@ -67,7 +68,7 @@ const $props = defineProps({
default: '', default: '',
description: 'It is used for redirect on click "save and continue"', description: 'It is used for redirect on click "save and continue"',
}, },
hasSubtoolbar: { hasSubToolbar: {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
@ -313,8 +314,11 @@ watch(formUrl, async () => {
></slot> ></slot>
</template> </template>
</VnPaginate> </VnPaginate>
<SkeletonTable v-if="!formData" :columns="$attrs.columns?.length" /> <SkeletonTable
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown() && hasSubtoolbar"> v-if="!formData && $attrs.autoLoad"
:columns="$attrs.columns?.length"
/>
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown() && hasSubToolbar">
<QBtnGroup push style="column-gap: 10px"> <QBtnGroup push style="column-gap: 10px">
<slot name="moreBeforeActions" /> <slot name="moreBeforeActions" />
<QBtn <QBtn

View File

@ -90,7 +90,7 @@ const $props = defineProps({
}); });
const emit = defineEmits(['onFetch', 'onDataSaved']); const emit = defineEmits(['onFetch', 'onDataSaved']);
const modelValue = computed( const modelValue = computed(
() => $props.model ?? `formModel_${route?.meta?.title ?? route.name}` () => $props.model ?? `formModel_${route?.meta?.title ?? route.name}`,
).value; ).value;
const componentIsRendered = ref(false); const componentIsRendered = ref(false);
const arrayData = useArrayData(modelValue); const arrayData = useArrayData(modelValue);
@ -137,7 +137,7 @@ onMounted(async () => {
JSON.stringify(newVal) !== JSON.stringify(originalData.value); JSON.stringify(newVal) !== JSON.stringify(originalData.value);
isResetting.value = false; isResetting.value = false;
}, },
{ deep: true } { deep: true },
); );
} }
}); });
@ -145,7 +145,7 @@ onMounted(async () => {
if (!$props.url) if (!$props.url)
watch( watch(
() => arrayData.store.data, () => arrayData.store.data,
(val) => updateAndEmit('onFetch', val) (val) => updateAndEmit('onFetch', val),
); );
watch(formUrl, async () => { watch(formUrl, async () => {
@ -206,11 +206,11 @@ async function save() {
updateAndEmit('onDataSaved', formData.value, response?.data); updateAndEmit('onDataSaved', formData.value, response?.data);
if ($props.reload) await arrayData.fetch({}); if ($props.reload) await arrayData.fetch({});
hasChanges.value = false;
} catch (err) { } catch (err) {
console.error(err); console.error(err);
notify('errors.writeRequest', 'negative'); notify('errors.writeRequest', 'negative');
} finally { } finally {
hasChanges.value = false;
isLoading.value = false; isLoading.value = false;
} }
} }
@ -239,7 +239,7 @@ function filter(value, update, filterOptions) {
(ref) => { (ref) => {
ref.setOptionIndex(-1); ref.setOptionIndex(-1);
ref.moveOptionSelection(1, true); ref.moveOptionSelection(1, true);
} },
); );
} }
@ -262,7 +262,7 @@ defineExpose({
<template> <template>
<div class="column items-center full-width"> <div class="column items-center full-width">
<QForm <QForm
v-if="formData"
@submit="save" @submit="save"
@reset="reset" @reset="reset"
class="q-pa-md" class="q-pa-md"
@ -270,11 +270,13 @@ defineExpose({
> >
<QCard> <QCard>
<slot <slot
v-if="formData"
name="form" name="form"
:data="formData" :data="formData"
:validate="validate" :validate="validate"
:filter="filter" :filter="filter"
/> />
<SkeletonForm v-else/>
</QCard> </QCard>
</QForm> </QForm>
</div> </div>
@ -337,7 +339,7 @@ defineExpose({
</QBtnGroup> </QBtnGroup>
</div> </div>
</Teleport> </Teleport>
<SkeletonForm v-if="!formData" />
<QInnerLoading <QInnerLoading
:showing="isLoading" :showing="isLoading"
:label="t('globals.pleaseWait')" :label="t('globals.pleaseWait')"

View File

@ -58,6 +58,7 @@ function addChildren(module, route, parent) {
} }
const items = ref([]); const items = ref([]);
function getRoutes() { function getRoutes() {
if (props.source === 'main') { if (props.source === 'main') {
const modules = Object.assign([], navigation.getModules().value); const modules = Object.assign([], navigation.getModules().value);
@ -66,9 +67,8 @@ function getRoutes() {
const moduleDef = routes.find( const moduleDef = routes.find(
(route) => toLowerCamel(route.name) === item.module (route) => toLowerCamel(route.name) === item.module
); );
item.children = [];
if (!moduleDef) continue; if (!moduleDef) continue;
item.children = [];
addChildren(item.module, moduleDef, item.children); addChildren(item.module, moduleDef, item.children);
} }

View File

@ -21,7 +21,7 @@ const itemComputed = computed(() => {
</script> </script>
<template> <template>
<QItem <QItem
active-class="bg-hover" active-class="bg-vn-hover"
class="min-height" class="min-height"
:to="{ name: itemComputed.name }" :to="{ name: itemComputed.name }"
clickable clickable

View File

@ -1,21 +1,19 @@
<script setup> <script setup>
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useSession } from 'src/composables/useSession';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import PinnedModules from './PinnedModules.vue'; import PinnedModules from './PinnedModules.vue';
import UserPanel from 'components/UserPanel.vue'; import UserPanel from 'components/UserPanel.vue';
import VnBreadcrumbs from './common/VnBreadcrumbs.vue'; import VnBreadcrumbs from './common/VnBreadcrumbs.vue';
import VnImg from 'src/components/ui/VnImg.vue';
const { t } = useI18n(); const { t } = useI18n();
const stateStore = useStateStore(); const stateStore = useStateStore();
const quasar = useQuasar(); const quasar = useQuasar();
const state = useState(); const state = useState();
const user = state.getUser(); const user = state.getUser();
const { getTokenMultimedia } = useSession();
const token = getTokenMultimedia();
const appName = 'Lilium'; const appName = 'Lilium';
onMounted(() => stateStore.setMounted()); onMounted(() => stateStore.setMounted());
@ -83,11 +81,12 @@ const pinnedModulesRef = ref();
id="user" id="user"
> >
<QAvatar size="lg"> <QAvatar size="lg">
<QImg <VnImg
:src="`/api/Images/user/160x160/${user.id}/download?access_token=${token}`" :id="user.id"
spinner-color="primary" collection="user"
> size="160x160"
</QImg> :zoom-size="null"
/>
</QAvatar> </QAvatar>
<QTooltip bottom> <QTooltip bottom>
{{ t('globals.userPanel') }} {{ t('globals.userPanel') }}

View File

@ -70,14 +70,11 @@ const makeInvoice = async () => {
}); });
}); });
if (!response) { if (!response) {
console.log('entra cuando no checkbox');
return; return;
} }
} }
console.log('params: ', params);
const { data } = await axios.post('InvoiceOuts/transferInvoice', params); const { data } = await axios.post('InvoiceOuts/transferInvoice', params);
console.log('data: ', data);
notify(t('Transferred invoice'), 'positive'); notify(t('Transferred invoice'), 'positive');
const id = data?.[0]; const id = data?.[0];
if (id) router.push({ name: 'InvoiceOutSummary', params: { id } }); if (id) router.push({ name: 'InvoiceOutSummary', params: { id } });

View File

@ -12,12 +12,14 @@ import VnRow from 'components/ui/VnRow.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import { useClipboard } from 'src/composables/useClipboard'; import { useClipboard } from 'src/composables/useClipboard';
import VnImg from 'src/components/ui/VnImg.vue'; import VnImg from 'src/components/ui/VnImg.vue';
import { useRole } from 'src/composables/useRole';
const state = useState(); const state = useState();
const session = useSession(); const session = useSession();
const router = useRouter(); const router = useRouter();
const { t, locale } = useI18n(); const { t, locale } = useI18n();
const { copyText } = useClipboard(); const { copyText } = useClipboard();
const userLocale = computed({ const userLocale = computed({
get() { get() {
return locale.value; return locale.value;
@ -99,6 +101,7 @@ function saveUserData(param, value) {
axios.post('UserConfigs/setUserConfig', { [param]: value }); axios.post('UserConfigs/setUserConfig', { [param]: value });
localUserData(); localUserData();
} }
const isEmployee = computed(() => useRole().isEmployee());
</script> </script>
<template> <template>
@ -109,12 +112,14 @@ function saveUserData(param, value) {
auto-load auto-load
/> />
<FetchData <FetchData
v-if="isEmployee"
url="Companies" url="Companies"
order="name" order="name"
@on-fetch="(data) => (companiesData = data)" @on-fetch="(data) => (companiesData = data)"
auto-load auto-load
/> />
<FetchData <FetchData
v-if="isEmployee"
url="Accountings" url="Accountings"
order="name" order="name"
@on-fetch="(data) => (accountBankData = data)" @on-fetch="(data) => (accountBankData = data)"

View File

@ -46,6 +46,7 @@ const defaultComponents = {
component: markRaw(VnInput), component: markRaw(VnInput),
attrs: { attrs: {
disable: !$props.isEditable, disable: !$props.isEditable,
class: 'fit',
}, },
forceAttrs: { forceAttrs: {
label: $props.showLabel && $props.column.label, label: $props.showLabel && $props.column.label,
@ -55,6 +56,7 @@ const defaultComponents = {
component: markRaw(VnInput), component: markRaw(VnInput),
attrs: { attrs: {
disable: !$props.isEditable, disable: !$props.isEditable,
class: 'fit',
}, },
forceAttrs: { forceAttrs: {
label: $props.showLabel && $props.column.label, label: $props.showLabel && $props.column.label,
@ -66,6 +68,7 @@ const defaultComponents = {
readonly: true, readonly: true,
disable: !$props.isEditable, disable: !$props.isEditable,
style: 'min-width: 125px', style: 'min-width: 125px',
class: 'fit',
}, },
forceAttrs: { forceAttrs: {
label: $props.showLabel && $props.column.label, label: $props.showLabel && $props.column.label,
@ -77,7 +80,7 @@ const defaultComponents = {
const defaultAttrs = { const defaultAttrs = {
disable: !$props.isEditable, disable: !$props.isEditable,
'model-value': Boolean(prop), 'model-value': Boolean(prop),
class: 'no-padding', class: 'no-padding fit',
}; };
if (typeof prop == 'number') { if (typeof prop == 'number') {
@ -94,6 +97,7 @@ const defaultComponents = {
component: markRaw(VnSelect), component: markRaw(VnSelect),
attrs: { attrs: {
disable: !$props.isEditable, disable: !$props.isEditable,
class: 'fit',
}, },
forceAttrs: { forceAttrs: {
label: $props.showLabel && $props.column.label, label: $props.showLabel && $props.column.label,
@ -134,7 +138,7 @@ const col = computed(() => {
const components = computed(() => $props.components ?? defaultComponents); const components = computed(() => $props.components ?? defaultComponents);
</script> </script>
<template> <template>
<div class="row no-wrap fit"> <div class="row no-wrap">
<VnComponent <VnComponent
v-if="col.before" v-if="col.before"
:prop="col.before" :prop="col.before"

View File

@ -7,6 +7,7 @@ import { useArrayData } from 'composables/useArrayData';
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import VnInput from 'components/common/VnInput.vue'; import VnInput from 'components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import VnInputTime from 'components/common/VnInputTime.vue';
import VnTableColumn from 'components/VnTable/VnColumn.vue'; import VnTableColumn from 'components/VnTable/VnColumn.vue';
const $props = defineProps({ const $props = defineProps({
@ -39,7 +40,7 @@ const enterEvent = {
const defaultAttrs = { const defaultAttrs = {
filled: !$props.showTitle, filled: !$props.showTitle,
class: 'q-px-sm q-pb-xs q-pt-none', class: 'q-px-sm q-pb-xs q-pt-none fit',
dense: true, dense: true,
}; };
@ -75,12 +76,23 @@ const components = {
}, },
forceAttrs, forceAttrs,
}, },
time: {
component: markRaw(VnInputTime),
event: updateEvent,
attrs: {
...defaultAttrs,
disable: !$props.isEditable,
},
forceAttrs: {
label: $props.showLabel && $props.column.label,
},
},
checkbox: { checkbox: {
component: markRaw(QCheckbox), component: markRaw(QCheckbox),
event: updateEvent, event: updateEvent,
attrs: { attrs: {
dense: true, dense: true,
class: $props.showTitle ? 'q-py-sm q-mt-md' : 'q-px-md q-py-xs', class: $props.showTitle ? 'q-py-sm q-mt-md' : 'q-px-md q-py-xs fit',
'toggle-indeterminate': true, 'toggle-indeterminate': true,
}, },
forceAttrs, forceAttrs,
@ -89,7 +101,7 @@ const components = {
component: markRaw(VnSelect), component: markRaw(VnSelect),
event: updateEvent, event: updateEvent,
attrs: { attrs: {
class: 'q-px-md q-pb-xs q-pt-none', class: 'q-px-md q-pb-xs q-pt-none fit',
dense: true, dense: true,
filled: !$props.showTitle, filled: !$props.showTitle,
}, },

View File

@ -59,6 +59,14 @@ const $props = defineProps({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
hasSubToolbar: {
type: Boolean,
default: true,
},
disableOption: {
type: Object,
default: () => ({ card: false, table: false }),
},
}); });
const { t } = useI18n(); const { t } = useI18n();
const stateStore = useStateStore(); const stateStore = useStateStore();
@ -80,11 +88,13 @@ const tableModes = [
icon: 'view_column', icon: 'view_column',
title: t('table view'), title: t('table view'),
value: TABLE_MODE, value: TABLE_MODE,
disable: $props.disableOption?.table,
}, },
{ {
icon: 'grid_view', icon: 'grid_view',
title: t('grid view'), title: t('grid view'),
value: DEFAULT_MODE, value: DEFAULT_MODE,
disable: $props.disableOption?.card,
}, },
]; ];
@ -175,11 +185,14 @@ function columnName(col) {
} }
function getColAlign(col) { function getColAlign(col) {
return 'text-' + (col.align ?? 'left') return 'text-' + (col.align ?? 'left');
} }
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
defineExpose({ defineExpose({
reload, reload,
redirect: redirectFn, redirect: redirectFn,
selected,
}); });
</script> </script>
<template> <template>
@ -225,11 +238,18 @@ defineExpose({
:search-url="searchUrl" :search-url="searchUrl"
:disable-infinite-scroll="mode == TABLE_MODE" :disable-infinite-scroll="mode == TABLE_MODE"
@save-changes="reload" @save-changes="reload"
:has-subtoolbar="isEditable" :has-sub-toolbar="$attrs['hasSubToolbar'] ?? isEditable"
> >
<template
v-for="(_, slotName) in $slots"
#[slotName]="slotData"
:key="slotName"
>
<slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" />
</template>
<template #body="{ rows }"> <template #body="{ rows }">
<QTable <QTable
v-bind="$attrs['QTable']" v-bind="$attrs['q-table']"
class="vnTable" class="vnTable"
:columns="splittedColumns.columns" :columns="splittedColumns.columns"
:rows="rows" :rows="rows"
@ -246,6 +266,7 @@ defineExpose({
CrudModelRef.vnPaginateRef.paginate() CrudModelRef.vnPaginateRef.paginate()
" "
@row-click="(_, row) => rowClickFunction(row)" @row-click="(_, row) => rowClickFunction(row)"
@update:selected="emit('update:selected', $event)"
> >
<template #top-left> <template #top-left>
<slot name="top-left"></slot> <slot name="top-left"></slot>
@ -310,13 +331,15 @@ defineExpose({
class="no-margin q-px-xs" class="no-margin q-px-xs"
:class="getColAlign(col)" :class="getColAlign(col)"
> >
<VnTableColumn <slot :name="`column-${col.name}`" :col="col" :row="row">
:column="col" <VnTableColumn
:row="row" :column="col"
:is-editable="false" :row="row"
v-model="row[col.name]" :is-editable="false"
component-prop="columnField" v-model="row[col.name]"
/> component-prop="columnField"
/>
</slot>
</QTd> </QTd>
</template> </template>
<template #body-cell-tableActions="{ col, row }"> <template #body-cell-tableActions="{ col, row }">
@ -401,9 +424,9 @@ defineExpose({
> >
<VnLv <VnLv
:label=" :label="
!col.component && !col.component && col.label
col.label && ? `${col.label}:`
`${col.label}:` : ''
" "
> >
<template #value> <template #value>
@ -412,14 +435,20 @@ defineExpose({
stopEventPropagation($event) stopEventPropagation($event)
" "
> >
<VnTableColumn <slot
:column="col" :name="`column-${col.name}`"
:col="col"
:row="row" :row="row"
:is-editable="false" >
v-model="row[col.name]" <VnTableColumn
component-prop="columnField" :column="col"
:show-label="true" :row="row"
/> :is-editable="false"
v-model="row[col.name]"
component-prop="columnField"
:show-label="true"
/>
</slot>
</span> </span>
</template> </template>
</VnLv> </VnLv>
@ -477,6 +506,7 @@ defineExpose({
default="input" default="input"
v-model="data[column.name]" v-model="data[column.name]"
:show-label="true" :show-label="true"
component-prop="columnCreate"
/> />
<slot name="more-create-dialog" :data="data" /> <slot name="more-create-dialog" :data="data" />
</div> </div>

View File

@ -1,6 +1,6 @@
<script setup> <script setup>
import { onBeforeMount, computed } from 'vue'; import { onBeforeMount, computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import useCardSize from 'src/composables/useCardSize'; import useCardSize from 'src/composables/useCardSize';
@ -39,8 +39,17 @@ const arrayData = useArrayData(props.dataKey, {
onBeforeMount(async () => { onBeforeMount(async () => {
if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id }; if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id };
await arrayData.fetch({ append: false }); await arrayData.fetch({ append: false, updateRouter: false });
}); });
if (props.baseUrl) {
onBeforeRouteUpdate(async (to, from) => {
if (to.params.id !== from.params.id) {
arrayData.store.url = `${props.baseUrl}/${to.params.id}`;
await arrayData.fetch({ append: false, updateRouter: false });
}
});
}
</script> </script>
<template> <template>
<QDrawer <QDrawer

View File

@ -12,7 +12,7 @@ const $props = defineProps({
default: () => {}, default: () => {},
}, },
value: { value: {
type: [Object, Number, String], type: [Object, Number, String, Boolean],
default: () => {}, default: () => {},
}, },
}); });
@ -54,7 +54,6 @@ function toValueAttrs(attrs) {
v-bind="mix(toComponent).attrs" v-bind="mix(toComponent).attrs"
v-on="mix(toComponent).event ?? {}" v-on="mix(toComponent).event ?? {}"
v-model="model" v-model="model"
class="fit"
/> />
</span> </span>
</template> </template>

View File

@ -93,7 +93,12 @@ const inputRules = [
name="close" name="close"
size="xs" size="xs"
v-if="hover && value && !$attrs.disabled && $props.clearable" v-if="hover && value && !$attrs.disabled && $props.clearable"
@click="value = null" @click="
() => {
value = null;
emit('remove');
}
"
></QIcon> ></QIcon>
<QIcon v-if="info" name="info"> <QIcon v-if="info" name="info">
<QTooltip max-width="350px"> <QTooltip max-width="350px">

View File

@ -1,84 +1,31 @@
<script setup> <script setup>
import { computed, ref } from 'vue'; import { onMounted, watch, computed, ref } from 'vue';
import { date } from 'quasar';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import isValidDate from 'filters/isValidDate';
const props = defineProps({ const model = defineModel({ type: String });
modelValue: { const $props = defineProps({
type: String,
default: null,
},
readonly: {
type: Boolean,
default: false,
},
isOutlined: { isOutlined: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
emitDateFormat: {
type: Boolean,
default: false,
},
}); });
const hover = ref(false);
const emit = defineEmits(['update:modelValue']);
const { t } = useI18n(); const { t } = useI18n();
const requiredFieldRule = (val) => !!val || t('globals.fieldRequired'); const requiredFieldRule = (val) => !!val || t('globals.fieldRequired');
const joinDateAndTime = (date, time) => { const dateFormat = 'DD/MM/YYYY';
if (!date) { const isPopupOpen = ref();
return null; const hover = ref();
} const mask = ref();
if (!time) {
return new Date(date).toISOString();
}
const [year, month, day] = date.split('/');
return new Date(`${year}-${month}-${day}T${time}`).toISOString();
};
const time = computed(() => (props.modelValue ? props.modelValue.split('T')?.[1] : null)); onMounted(() => {
const value = computed({ // fix quasar bug
get() { mask.value = '##/##/####';
return props.modelValue;
},
set(value) {
emit(
'update:modelValue',
props.emitDateFormat ? new Date(value) : joinDateAndTime(value, time.value)
);
},
}); });
const isPopupOpen = ref(false);
const onDateUpdate = (date) => {
value.value = date;
isPopupOpen.value = false;
};
const padDate = (value) => value.toString().padStart(2, '0');
const formatDate = (dateString) => {
const date = new Date(dateString || '');
return `${date.getFullYear()}/${padDate(date.getMonth() + 1)}/${padDate(
date.getDate()
)}`;
};
const displayDate = (dateString) => {
if (!dateString || !isValidDate(dateString)) {
return '';
}
return new Date(dateString).toLocaleDateString([], {
year: 'numeric',
month: '2-digit',
day: '2-digit',
});
};
const styleAttrs = computed(() => { const styleAttrs = computed(() => {
return props.isOutlined return $props.isOutlined
? { ? {
dense: true, dense: true,
outlined: true, outlined: true,
@ -86,42 +33,101 @@ const styleAttrs = computed(() => {
} }
: {}; : {};
}); });
const formattedDate = computed({
get() {
if (!model.value) return model.value;
return date.formatDate(new Date(model.value), dateFormat);
},
set(value) {
if (value == model.value) return;
let newDate;
if (value) {
// parse input
if (value.includes('/') && value.length >= 10) {
if (value.at(2) == '/') value = value.split('/').reverse().join('/');
value = date.formatDate(
new Date(value).toISOString(),
'YYYY-MM-DDTHH:mm:ss.SSSZ'
);
}
let ymd = value.split('-').map((e) => parseInt(e));
newDate = new Date(ymd[0], ymd[1] - 1, ymd[2]);
if (model.value) {
const orgDate =
model.value instanceof Date ? model.value : new Date(model.value);
newDate.setHours(
orgDate.getHours(),
orgDate.getMinutes(),
orgDate.getSeconds(),
orgDate.getMilliseconds()
);
}
}
if (!isNaN(newDate)) model.value = newDate.toISOString();
},
});
const popupDate = computed(() =>
model.value ? date.formatDate(new Date(model.value), 'YYYY/MM/DD') : model.value
);
watch(
() => model.value,
(val) => (formattedDate.value = val),
{ immediate: true }
);
</script> </script>
<template> <template>
<div @mouseover="hover = true" @mouseleave="hover = false"> <div @mouseover="hover = true" @mouseleave="hover = false">
<QInput <QInput
v-model="formattedDate"
class="vn-input-date" class="vn-input-date"
readonly :mask="mask"
:model-value="displayDate(value)" placeholder="dd/mm/aaaa"
v-bind="{ ...$attrs, ...styleAttrs }" v-bind="{ ...$attrs, ...styleAttrs }"
:class="{ required: $attrs.required }" :class="{ required: $attrs.required }"
:rules="$attrs.required ? [requiredFieldRule] : null" :rules="$attrs.required ? [requiredFieldRule] : null"
@click="isPopupOpen = true" :clearable="false"
> >
<template #append> <template #append>
<QIcon <QIcon
name="close" name="close"
size="xs" size="xs"
v-if="hover && value && !readonly" v-if="
@click="onDateUpdate(null)" ($attrs.clearable == undefined || $attrs.clearable) &&
></QIcon> hover &&
<QIcon name="event" class="cursor-pointer"> model &&
<QPopupProxy !$attrs.disable
v-model="isPopupOpen" "
cover @click="
transition-show="scale" model = null;
transition-hide="scale" isPopupOpen = false;
:no-parent-event="props.readonly" "
> />
<QDate <QIcon name="event" class="cursor-pointer" />
:today-btn="true"
:model-value="formatDate(value)"
@update:model-value="onDateUpdate"
/>
</QPopupProxy>
</QIcon>
</template> </template>
<QMenu
transition-show="scale"
transition-hide="scale"
v-model="isPopupOpen"
anchor="bottom left"
self="top start"
:no-focus="true"
>
<QDate
v-model="popupDate"
:today-btn="true"
@update:model-value="
(date) => {
formattedDate = date;
isPopupOpen = false;
}
"
/>
</QMenu>
</QInput> </QInput>
</div> </div>
</template> </template>

View File

@ -1,14 +1,11 @@
<script setup> <script setup>
import { computed, ref } from 'vue'; import { watch, computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import isValidDate from 'filters/isValidDate'; import { date } from 'quasar';
const model = defineModel({ type: String });
const props = defineProps({ const props = defineProps({
modelValue: { timeOnly: {
type: String,
default: null,
},
readonly: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
@ -17,43 +14,12 @@ const props = defineProps({
default: false, default: false,
}, },
}); });
const emit = defineEmits(['update:modelValue']);
const { t } = useI18n(); const { t } = useI18n();
const requiredFieldRule = (val) => !!val || t('globals.fieldRequired'); const requiredFieldRule = (val) => !!val || t('globals.fieldRequired');
const value = computed({ const dateFormat = 'HH:mm';
get() { const isPopupOpen = ref();
return props.modelValue; const hover = ref();
},
set(value) {
const [hours, minutes] = value.split(':');
const date = new Date(props.modelValue);
date.setHours(Number.parseInt(hours) || 0, Number.parseInt(minutes) || 0, 0, 0);
emit('update:modelValue', value ? date.toISOString() : null);
},
});
const isPopupOpen = ref(false);
const onDateUpdate = (date) => {
internalValue.value = date;
};
const save = () => {
value.value = internalValue.value;
};
const formatTime = (dateString) => {
if (!dateString || !isValidDate(dateString)) {
return '';
}
const date = new Date(dateString || '');
return date.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
});
};
const internalValue = ref(formatTime(value));
const styleAttrs = computed(() => { const styleAttrs = computed(() => {
return props.isOutlined return props.isOutlined
@ -64,54 +30,87 @@ const styleAttrs = computed(() => {
} }
: {}; : {};
}); });
const formattedTime = computed({
get() {
if (!model.value || model.value?.length <= 5) return model.value;
return dateToTime(model.value);
},
set(value) {
if (value == model.value) return;
let time = value;
if (time) {
if (time?.length > 5) time = dateToTime(time);
if (!props.timeOnly) {
const hours = time.split(':');
const date = new Date();
date.setHours(hours[0], hours[1], 0);
time = date.toISOString();
}
}
model.value = time;
},
});
function dateToTime(newDate) {
return date.formatDate(new Date(newDate), dateFormat);
}
watch(
() => model.value,
(val) => (formattedTime.value = val),
{ immediate: true }
);
</script> </script>
<template> <template>
<QInput <div @mouseover="hover = true" @mouseleave="hover = false">
class="vn-input-time" <QInput
readonly class="vn-input-time"
:model-value="formatTime(value)" mask="##:##"
v-bind="{ ...$attrs, ...styleAttrs }" placeholder="--:--"
:class="{ required: $attrs.required }" v-model="formattedTime"
:rules="$attrs.required ? [requiredFieldRule] : null" v-bind="{ ...$attrs, ...styleAttrs }"
@click="isPopupOpen = true" :class="{ required: $attrs.required }"
> style="min-width: 100px"
<template #append> :rules="$attrs.required ? [requiredFieldRule] : null"
<QIcon name="Schedule" class="cursor-pointer"> >
<QPopupProxy <template #append>
v-model="isPopupOpen" <QIcon
cover name="close"
transition-show="scale" size="xs"
transition-hide="scale" v-if="
:no-parent-event="props.readonly" ($attrs.clearable == undefined || $attrs.clearable) &&
> hover &&
<QTime model &&
:format24h="false" !$attrs.disable
:model-value="formatTime(value)" "
@update:model-value="onDateUpdate" @click="
> model = null;
<div class="row items-center justify-end q-gutter-sm"> isPopupOpen = false;
<QBtn "
:label="t('Cancel')" />
color="primary" <QIcon name="Schedule" class="cursor-pointer" />
flat </template>
v-close-popup <QMenu
/> transition-show="scale"
<QBtn transition-hide="scale"
label="Ok" v-model="isPopupOpen"
color="primary" anchor="bottom left"
flat self="top start"
@click="save" :no-focus="true"
v-close-popup >
/> <QTime
</div> :format24h="false"
</QTime> v-model="formattedTime"
</QPopupProxy> mask="HH:mm"
</QIcon> landscape
</template> now-btn
</QInput> />
</QMenu>
</QInput>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
.vn-input-time.q-field--standard.q-field--readonly .q-field__control:before { .vn-input-time.q-field--standard.q-field--readonly .q-field__control:before {
border-bottom-style: solid; border-bottom-style: solid;
@ -121,8 +120,3 @@ const styleAttrs = computed(() => {
border-style: solid; border-style: solid;
} }
</style> </style>
<i18n>
es:
Cancel: Cancelar
</i18n>

View File

@ -49,6 +49,7 @@ const filter = {
'changedModelId', 'changedModelId',
'changedModelValue', 'changedModelValue',
'description', 'description',
'summaryId',
], ],
include: [ include: [
{ {
@ -459,12 +460,12 @@ onUnmounted(() => {
:style="{ :style="{
backgroundColor: useColor(modelLog.model), backgroundColor: useColor(modelLog.model),
}" }"
:title="modelLog.model" :title="`${modelLog.model} #${modelLog.id}`"
> >
{{ t(modelLog.modelI18n) }} {{ t(modelLog.modelI18n) }}
</QChip> </QChip>
<span class="model-id" v-if="modelLog.id" <span class="model-id" v-if="modelLog.summaryId"
>#{{ modelLog.id }}</span >#{{ modelLog.summaryId }}</span
> >
<span class="model-value" :title="modelLog.showValue"> <span class="model-value" :title="modelLog.showValue">
{{ modelLog.showValue }} {{ modelLog.showValue }}

View File

@ -46,7 +46,7 @@ let arrayData;
let store; let store;
let entity; let entity;
const isLoading = ref(false); const isLoading = ref(false);
const isSameDataKey = computed(() => $props.dataKey === route.meta.moduleName);
defineExpose({ getData }); defineExpose({ getData });
onBeforeMount(async () => { onBeforeMount(async () => {
@ -58,10 +58,12 @@ onBeforeMount(async () => {
store = arrayData.store; store = arrayData.store;
entity = computed(() => (Array.isArray(store.data) ? store.data[0] : store.data)); entity = computed(() => (Array.isArray(store.data) ? store.data[0] : store.data));
// It enables to load data only once if the module is the same as the dataKey // It enables to load data only once if the module is the same as the dataKey
if ($props.dataKey !== route.meta.moduleName || !route.params.id) await getData(); if (!isSameDataKey.value || !route.params.id) await getData();
watch( watch(
() => [$props.url, $props.filter], () => [$props.url, $props.filter],
async () => await getData() async () => {
if (!isSameDataKey.value) await getData();
}
); );
}); });
@ -77,14 +79,50 @@ async function getData() {
isLoading.value = false; isLoading.value = false;
} }
} }
function getValueFromPath(path) {
if (!path) return;
const keys = path.toString().split('.');
let current = entity.value;
for (let i = 0; i < keys.length; i++) {
if (current[keys[i]] === undefined) return undefined;
else current = current[keys[i]];
}
return current;
}
const emit = defineEmits(['onFetch']); const emit = defineEmits(['onFetch']);
const iconModule = computed(() => route.matched[1].meta.icon);
const toModule = computed(() =>
route.matched[1].path.split('/').length > 2
? route.matched[1].redirect
: route.matched[1].children[0].redirect
);
</script> </script>
<template> <template>
<div class="descriptor"> <div class="descriptor">
<template v-if="entity && !isLoading"> <template v-if="entity && !isLoading">
<div class="header bg-primary q-pa-sm justify-between"> <div class="header bg-primary q-pa-sm justify-between">
<slot name="header-extra-action" /> <slot name="header-extra-action"
><QBtn
round
flat
dense
size="md"
:icon="iconModule"
color="white"
class="link"
:to="toModule"
>
<QTooltip>
{{ t('globals.goToModuleIndex') }}
</QTooltip>
</QBtn></slot
>
<QBtn <QBtn
@click.stop="viewSummary(entity.id, $props.summary)" @click.stop="viewSummary(entity.id, $props.summary)"
round round
@ -139,8 +177,8 @@ const emit = defineEmits(['onFetch']);
<QList dense> <QList dense>
<QItemLabel header class="ellipsis text-h5" :lines="1"> <QItemLabel header class="ellipsis text-h5" :lines="1">
<div class="title"> <div class="title">
<span v-if="$props.title" :title="$props.title"> <span v-if="$props.title" :title="getValueFromPath(title)">
{{ entity[title] ?? $props.title }} {{ getValueFromPath(title) ?? $props.title }}
</span> </span>
<slot v-else name="description" :entity="entity"> <slot v-else name="description" :entity="entity">
<span :title="entity.name"> <span :title="entity.name">
@ -151,7 +189,7 @@ const emit = defineEmits(['onFetch']);
</QItemLabel> </QItemLabel>
<QItem dense> <QItem dense>
<QItemLabel class="subtitle" caption> <QItemLabel class="subtitle" caption>
#{{ $props.subtitle ?? entity.id }} #{{ getValueFromPath(subtitle) ?? entity.id }}
</QItemLabel> </QItemLabel>
</QItem> </QItem>
</QList> </QList>

View File

@ -187,15 +187,10 @@ function existSummary(routes) {
color: lighten($primary, 20%); color: lighten($primary, 20%);
} }
.q-checkbox { .q-checkbox {
display: flex;
margin-bottom: 9px;
& .q-checkbox__label { & .q-checkbox__label {
margin-left: 31px;
color: var(--vn-text-color); color: var(--vn-text-color);
} }
& .q-checkbox__inner { & .q-checkbox__inner {
position: absolute;
left: 0;
color: var(--vn-label-color); color: var(--vn-label-color);
} }
} }

View File

@ -1,20 +1,14 @@
<template> <template>
<div class="q-pa-md"> <div class="row q-gutter-md q-mb-md">
<div class="row q-gutter-md q-mb-md"> <QSkeleton type="QInput" class="col" square />
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" class="col" square />
<QSkeleton type="QInput" square /> </div>
</div> <div class="row q-gutter-md q-mb-md">
<div class="row q-gutter-md q-mb-md"> <QSkeleton type="QInput" class="col" square />
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" class="col" square />
<QSkeleton type="QInput" square /> </div>
</div> <div class="row q-gutter-md q-mb-md">
<div class="row q-gutter-md q-mb-md"> <QSkeleton type="QInput" class="col" square />
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" class="col" square />
<QSkeleton type="QInput" square />
</div>
<div class="row q-gutter-md">
<QSkeleton type="QBtn" />
<QSkeleton type="QBtn" />
</div>
</div> </div>
</template> </template>

View File

@ -7,8 +7,11 @@ import toDate from 'filters/toDate';
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue'; import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
const { t } = useI18n(); const { t } = useI18n();
const params = defineModel({ default: {}, required: true, type: Object });
const $props = defineProps({ const $props = defineProps({
modelValue: {
type: Object,
default: () => {},
},
dataKey: { dataKey: {
type: String, type: String,
required: true, required: true,
@ -55,7 +58,14 @@ const $props = defineProps({
}, },
}); });
const emit = defineEmits(['refresh', 'clear', 'search', 'init', 'remove']); const emit = defineEmits([
'update:modelValue',
'refresh',
'clear',
'search',
'init',
'remove',
]);
const arrayData = useArrayData($props.dataKey, { const arrayData = useArrayData($props.dataKey, {
exprBuilder: $props.exprBuilder, exprBuilder: $props.exprBuilder,
@ -64,9 +74,10 @@ const arrayData = useArrayData($props.dataKey, {
}); });
const route = useRoute(); const route = useRoute();
const store = arrayData.store; const store = arrayData.store;
const userParams = ref({});
onMounted(() => { onMounted(() => {
emit('init', { params: params.value }); userParams.value = $props.modelValue ?? {};
emit('init', { params: userParams.value });
}); });
function setUserParams(watchedParams) { function setUserParams(watchedParams) {
@ -75,7 +86,7 @@ function setUserParams(watchedParams) {
if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams); if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams);
watchedParams = { ...watchedParams, ...watchedParams.filter?.where }; watchedParams = { ...watchedParams, ...watchedParams.filter?.where };
delete watchedParams.filter; delete watchedParams.filter;
params.value = { ...params.value, ...watchedParams }; userParams.value = { ...userParams.value, ...watchedParams };
} }
watch( watch(
@ -88,18 +99,22 @@ watch(
(val) => setUserParams(val) (val) => setUserParams(val)
); );
watch(
() => $props.modelValue,
(val) => (userParams.value = val ?? {})
);
const isLoading = ref(false); const isLoading = ref(false);
async function search(evt) { async function search(evt) {
if (evt && $props.disableSubmitEvent) return; if (evt && $props.disableSubmitEvent) return;
store.filter.where = {}; store.filter.where = {};
isLoading.value = true; isLoading.value = true;
const filter = { ...params.value }; const filter = { ...userParams.value };
store.userParamsChanged = true; store.userParamsChanged = true;
store.filter.skip = 0; arrayData.reset(['skip', 'filter.skip', 'page']);
store.skip = 0; const { params: newParams } = await arrayData.addFilter({ params: userParams.value });
const { params: newParams } = await arrayData.addFilter({ params: params.value }); userParams.value = newParams;
params.value = newParams;
if (!$props.showAll && !Object.values(filter).length) store.data = []; if (!$props.showAll && !Object.values(filter).length) store.data = [];
@ -109,8 +124,9 @@ async function search(evt) {
async function reload() { async function reload() {
isLoading.value = true; isLoading.value = true;
const params = Object.values(params.value).filter((param) => param); const params = Object.values(userParams.value).filter((param) => param);
store.skip = 0;
store.page = 1;
await arrayData.fetch({ append: false }); await arrayData.fetch({ append: false });
if (!$props.showAll && !params.length) store.data = []; if (!$props.showAll && !params.length) store.data = [];
isLoading.value = false; isLoading.value = false;
@ -120,20 +136,19 @@ async function reload() {
async function clearFilters() { async function clearFilters() {
isLoading.value = true; isLoading.value = true;
store.userParamsChanged = true; store.userParamsChanged = true;
store.filter.skip = 0; arrayData.reset(['skip', 'filter.skip', 'page']);
store.skip = 0;
// Filtrar los params no removibles // Filtrar los params no removibles
const removableFilters = Object.keys(params.value).filter((param) => const removableFilters = Object.keys(userParams.value).filter((param) =>
$props.unremovableParams.includes(param) $props.unremovableParams.includes(param)
); );
const newParams = {}; const newParams = {};
// Conservar solo los params que no son removibles // Conservar solo los params que no son removibles
for (const key of removableFilters) { for (const key of removableFilters) {
newParams[key] = params.value[key]; newParams[key] = userParams.value[key];
} }
params.value = {}; userParams.value = {};
params.value = { ...newParams }; // Actualizar los params con los removibles userParams.value = { ...newParams }; // Actualizar los params con los removibles
await arrayData.applyFilter({ params: params.value }); await arrayData.applyFilter({ params: userParams.value });
if (!$props.showAll) { if (!$props.showAll) {
store.data = []; store.data = [];
@ -141,12 +156,13 @@ async function clearFilters() {
isLoading.value = false; isLoading.value = false;
emit('clear'); emit('clear');
emit('update:modelValue', userParams.value);
} }
const tagsList = computed(() => { const tagsList = computed(() => {
const tagList = []; const tagList = [];
for (const key of Object.keys(params.value)) { for (const key of Object.keys(userParams.value)) {
const value = params.value[key]; const value = userParams.value[key];
if (value == null || ($props.hiddenTags || []).includes(key)) continue; if (value == null || ($props.hiddenTags || []).includes(key)) continue;
tagList.push({ label: key, value }); tagList.push({ label: key, value });
} }
@ -161,9 +177,10 @@ const customTags = computed(() =>
); );
async function remove(key) { async function remove(key) {
params.value[key] = undefined; userParams.value[key] = undefined;
search(); search();
emit('remove', key); emit('remove', key);
emit('update:modelValue', userParams.value);
} }
function formatValue(value) { function formatValue(value) {
@ -236,7 +253,7 @@ function formatValue(value) {
<slot <slot
v-if="$slots.customTags" v-if="$slots.customTags"
name="customTags" name="customTags"
:params="params" :params="userParams"
:tags="customTags" :tags="customTags"
:format-fn="formatValue" :format-fn="formatValue"
:search-fn="search" :search-fn="search"
@ -246,7 +263,7 @@ function formatValue(value) {
<QSeparator /> <QSeparator />
</QList> </QList>
<QList dense class="list q-gutter-y-sm q-mt-sm"> <QList dense class="list q-gutter-y-sm q-mt-sm">
<slot name="body" :params="params" :search-fn="search"></slot> <slot name="body" :params="userParams" :search-fn="search"></slot>
</QList> </QList>
<template v-if="$props.searchButton"> <template v-if="$props.searchButton">
<QItem> <QItem>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue'; import { ref, computed } from 'vue';
import { useSession } from 'src/composables/useSession'; import { useSession } from 'src/composables/useSession';
const $props = defineProps({ const $props = defineProps({
@ -28,21 +28,29 @@ const $props = defineProps({
const show = ref(false); const show = ref(false);
const token = useSession().getTokenMultimedia(); const token = useSession().getTokenMultimedia();
const timeStamp = ref(`timestamp=${Date.now()}`); const timeStamp = ref(`timestamp=${Date.now()}`);
const url = computed( import noImage from '/public/no-user.png';
() => import { useRole } from 'src/composables/useRole';
`/api/${$props.storage}/${$props.collection}/${$props.size}/${$props.id}/download?access_token=${token}&${timeStamp.value}` const url = computed(() => {
); const isEmployee = useRole().isEmployee();
return isEmployee
? `/api/${$props.storage}/${$props.collection}/${$props.size}/${$props.id}/download?access_token=${token}&${timeStamp.value}`
: noImage;
});
const reload = () => { const reload = () => {
timeStamp.value = `timestamp=${Date.now()}`; timeStamp.value = `timestamp=${Date.now()}`;
}; };
defineExpose({ defineExpose({
reload, reload,
}); });
onMounted(() => {});
</script> </script>
<template> <template>
<QImg :src="url" v-bind="$attrs" @click="show = !show" spinner-color="primary" /> <QImg
:class="{ zoomIn: $props.zoomSize }"
:src="url"
v-bind="$attrs"
@click="show = !show"
spinner-color="primary"
/>
<QDialog v-model="show" v-if="$props.zoomSize"> <QDialog v-model="show" v-if="$props.zoomSize">
<QImg <QImg
:src="url" :src="url"
@ -56,7 +64,9 @@ onMounted(() => {});
<style lang="scss" scoped> <style lang="scss" scoped>
.q-img { .q-img {
cursor: zoom-in; &.zoomIn {
cursor: zoom-in;
}
min-width: 50px; min-width: 50px;
} }
.rounded { .rounded {

View File

@ -2,6 +2,7 @@
import { dashIfEmpty } from 'src/filters'; import { dashIfEmpty } from 'src/filters';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useClipboard } from 'src/composables/useClipboard'; import { useClipboard } from 'src/composables/useClipboard';
import { computed } from 'vue';
const $props = defineProps({ const $props = defineProps({
label: { type: String, default: null }, label: { type: String, default: null },
@ -24,52 +25,67 @@ function copyValueText() {
}, },
}); });
} }
const val = computed(() => $props.value);
</script> </script>
<style scoped>
.label,
.value {
white-space: pre-line;
word-wrap: break-word;
}
</style>
<template> <template>
<div class="vn-label-value"> <div class="vn-label-value">
<div v-if="$props.label || $slots.label" class="label"> <QCheckbox
<slot name="label"> v-if="typeof value === 'boolean'"
<span>{{ $props.label }}</span> v-model="val"
</slot> :label="label"
</div> disable
<div class="value"> dense
<slot name="value"> />
<span :title="$props.value"> <template v-else>
{{ $props.dash ? dashIfEmpty($props.value) : $props.value }} <div v-if="label || $slots.label" class="label">
</span> <slot name="label">
</slot> <span>{{ label }}</span>
</div> </slot>
<div class="info" v-if="$props.info"> </div>
<QIcon name="info" class="cursor-pointer" size="xs" color="grey"> <div class="value">
<QTooltip class="bg-dark text-white shadow-4" :offset="[10, 10]"> <slot name="value">
{{ $props.info }} <span :title="value">
</QTooltip> {{ dash ? dashIfEmpty(value) : value }}
</QIcon> </span>
</div> </slot>
<div class="copy" v-if="$props.copy && $props.value" @click="copyValueText()"> </div>
<QIcon name="Content_Copy" color="primary"> <div class="info" v-if="info">
<QTooltip>{{ t('globals.copyClipboard') }}</QTooltip> <QIcon name="info" class="cursor-pointer" size="xs" color="grey">
</QIcon> <QTooltip class="bg-dark text-white shadow-4" :offset="[10, 10]">
</div> {{ info }}
</QTooltip>
</QIcon>
</div>
<div class="copy" v-if="copy && value" @click="copyValueText()">
<QIcon name="Content_Copy" color="primary">
<QTooltip>{{ t('globals.copyClipboard') }}</QTooltip>
</QIcon>
</div>
</template>
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.vn-label-value:hover .copy { .vn-label-value {
visibility: visible; &:hover .copy {
cursor: pointer; visibility: visible;
cursor: pointer;
}
.label,
.value {
white-space: pre-line;
word-wrap: break-word;
}
.copy {
visibility: hidden;
}
.info {
margin-left: 5px;
}
} }
.copy {
visibility: hidden; :deep(.q-checkbox.disabled) {
} opacity: 1 !important;
.info {
margin-left: 5px;
} }
</style> </style>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { onMounted, ref, watch } from 'vue'; import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
@ -95,6 +95,8 @@ onMounted(async () => {
mounted.value = true; mounted.value = true;
}); });
onBeforeUnmount(() => arrayData.reset());
watch( watch(
() => props.data, () => props.data,
() => { () => {
@ -118,8 +120,7 @@ const addFilter = async (filter, params) => {
async function fetch(params) { async function fetch(params) {
useArrayData(props.dataKey, params); useArrayData(props.dataKey, params);
store.filter.skip = 0; arrayData.reset(['filter.skip', 'skip']);
store.skip = 0;
await arrayData.fetch({ append: false }); await arrayData.fetch({ append: false });
if (!store.hasMoreData) { if (!store.hasMoreData) {
isLoading.value = false; isLoading.value = false;

View File

@ -103,7 +103,7 @@ async function search() {
const staticParams = Object.entries(store.userParams).filter( const staticParams = Object.entries(store.userParams).filter(
([key, value]) => value && (props.staticParams || []).includes(key) ([key, value]) => value && (props.staticParams || []).includes(key)
); );
store.skip = 0; arrayData.reset(['skip', 'page']);
if (props.makeFetch) if (props.makeFetch)
await arrayData.applyFilter({ await arrayData.applyFilter({

View File

@ -1,6 +1,7 @@
<script setup> <script setup>
import { onMounted, onUnmounted, ref } from 'vue'; import { onMounted, onBeforeUnmount, ref, nextTick } from 'vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
const stateStore = useStateStore(); const stateStore = useStateStore();
const actions = ref(null); const actions = ref(null);
const data = ref(null); const data = ref(null);
@ -24,9 +25,7 @@ onMounted(() => {
if (data.value) observer.observe(data.value, opts); if (data.value) observer.observe(data.value, opts);
}); });
onUnmounted(() => { onBeforeUnmount(() => stateStore.toggleSubToolbar());
stateStore.toggleSubToolbar();
});
</script> </script>
<template> <template>

View File

@ -27,8 +27,12 @@ export function useRole() {
return false; return false;
} }
function isEmployee() {
return hasAny(['employee']);
}
return { return {
isEmployee,
fetch, fetch,
hasAny, hasAny,
state, state,

View File

@ -5,6 +5,7 @@
body.body--light { body.body--light {
--font-color: black; --font-color: black;
--vn-section-color: #e0e0e0; --vn-section-color: #e0e0e0;
--vn-section-hover-color: #b9b9b9;
--vn-page-color: #ffffff; --vn-page-color: #ffffff;
--vn-text-color: var(--font-color); --vn-text-color: var(--font-color);
--vn-label-color: #5f5f5f; --vn-label-color: #5f5f5f;
@ -19,6 +20,7 @@ body.body--light {
body.body--dark { body.body--dark {
--vn-page-color: #222; --vn-page-color: #222;
--vn-section-color: #3d3d3d; --vn-section-color: #3d3d3d;
--vn-section-hover-color: #747474;
--vn-text-color: white; --vn-text-color: white;
--vn-label-color: #a8a8a8; --vn-label-color: #a8a8a8;
--vn-accent-color: #424242; --vn-accent-color: #424242;
@ -71,8 +73,9 @@ select:-webkit-autofill {
.bg-vn-section-color { .bg-vn-section-color {
background-color: var(--vn-section-color); background-color: var(--vn-section-color);
} }
.bg-hover {
background-color: #666666; .bg-vn-hover {
background-color: var(--vn-section-hover-color);
} }
.color-vn-label { .color-vn-label {
@ -152,6 +155,15 @@ select:-webkit-autofill {
color: var(--vn-label-color); color: var(--vn-label-color);
} }
.disabled {
& .q-checkbox__label {
color: var(--vn-text-color);
}
& .q-checkbox__inner {
color: var(--vn-label-color);
}
}
.q-chip, .q-chip,
.q-notification__message, .q-notification__message,
.q-notification__icon { .q-notification__icon {
@ -174,8 +186,6 @@ select:-webkit-autofill {
justify-content: center; justify-content: center;
} }
/* q-notification row items-stretch q-notification--standard bg-negative text-white */
.q-card, .q-card,
.q-table, .q-table,
.q-table__bottom, .q-table__bottom,

View File

@ -90,6 +90,7 @@ globals:
send: Send send: Send
code: Code code: Code
pageTitles: pageTitles:
logIn: Login
summary: Summary summary: Summary
basicData: Basic data basicData: Basic data
log: Logs log: Logs
@ -100,13 +101,143 @@ globals:
modes: Modes modes: Modes
zones: Zones zones: Zones
zonesList: Zones zonesList: Zones
deliveryList: Delivery days deliveryDays: Delivery days
upcomingList: Upcoming deliveries upcomingDeliveries: Upcoming deliveries
role: Role role: Role
alias: Alias alias: Alias
aliasUsers: Users aliasUsers: Users
subRoles: Subroles subRoles: Subroles
inheritedRoles: Inherited Roles inheritedRoles: Inherited Roles
customers: Customers
list: List
webPayments: Web Payments
extendedList: Extended list
notifications: Notifications
defaulter: Defaulter
customerCreate: New customer
fiscalData: Fiscal data
billingData: Billing data
consignees: Consignees
notes: Notes
credits: Credits
greuges: Greuges
balance: Balance
recoveries: Recoveries
webAccess: Web access
sms: Sms
creditManagement: Credit management
creditContracts: Credit contracts
creditOpinion: Credit opinion
others: Others
samples: Samples
consumption: Consumption
mandates: Mandates
contacts: Contacts
webPayment: Web payment
fileManagement: File management
unpaid: Unpaid
entries: Entries
buys: Buys
dms: File management
entryCreate: New entry
latestBuys: Latest buys
tickets: Tickets
ticketCreate: New Tickets
boxing: Boxing
sale: Sale
claims: Claims
claimCreate: New claim
lines: Lines
photos: Photos
development: Development
action: Action
invoiceOuts: Invoice out
negativeBases: Negative Bases
globalInvoicing: Global invoicing
invoiceOutCreate: Create invoice out
shelving: Shelving
shelvingList: Shelving List
shelvingCreate: New shelving
invoiceIns: Invoices In
invoiceInCreate: Create invoice in
vat: VAT
dueDay: Due day
intrastat: Intrastat
corrective: Corrective
order: Orders
orderList: List
orderCreate: New order
catalog: Catalog
volume: Volume
workers: Workers
workerCreate: New worker
department: Department
pda: PDA
pbx: Private Branch Exchange
calendar: Calendar
timeControl: Time control
locker: Locker
wagons: Wagons
wagonsList: Wagons List
wagonCreate: Create wagon
wagonEdit: Edit wagon
typesList: Types List
typeCreate: Create type
typeEdit: Edit type
wagonCounter: Trolley counter
roadmap: Roadmap
stops: Stops
routes: Routes
cmrsList: CMRs list
RouteList: List
routeCreate: New route
RouteRoadmap: Roadmaps
RouteRoadmapCreate: Create roadmap
autonomous: Autonomous
suppliers: Suppliers
supplier: Supplier
labeler: Labeler
supplierCreate: New supplier
accounts: Accounts
addresses: Addresses
agencyTerm: Agency agreement
travel: Travels
extraCommunity: Extra community
travelCreate: New travel
history: Log
thermographs: Thermograph
items: Items
diary: Diary
tags: Tags
create: Create
buyRequest: Buy requests
fixedPrice: Fixed prices
wasteBreakdown: Waste breakdown
itemCreate: New item
barcode: Barcodes
tax: Tax
botanical: Botanical
itemTypeCreate: New item type
family: Item Type
lastEntries: Last entries
itemType: Item type
monitors: Monitors
dashboard: Dashboard
users: Users
createTicket: Create ticket
ticketAdvance: Advance tickets
futureTickets: Future tickets
purchaseRequest: Purchase request
weeklyTickets: Weekly tickets
formation: Formation
locations: Locations
warehouses: Warehouses
roles: Roles
connections: Connections
acls: ACLs
mailForwarding: Mail forwarding
mailAlias: Mail alias
privileges: Privileges
created: Created created: Created
worker: Worker worker: Worker
now: Now now: Now
@ -114,6 +245,7 @@ globals:
new: New new: New
comment: Comment comment: Comment
observations: Observations observations: Observations
goToModuleIndex: Go to module index
errors: errors:
statusUnauthorized: Access denied statusUnauthorized: Access denied
statusInternalServerError: An internal server error has ocurred statusInternalServerError: An internal server error has ocurred
@ -132,8 +264,6 @@ login:
loginError: Invalid username or password loginError: Invalid username or password
fieldRequired: This field is required fieldRequired: This field is required
twoFactorRequired: Two-factor verification required twoFactorRequired: Two-factor verification required
pageTitles:
logIn: Login
twoFactor: twoFactor:
code: Code code: Code
validate: Validate validate: Validate
@ -148,40 +278,8 @@ verifyEmail:
verifyEmail: Email verification verifyEmail: Email verification
dashboard: dashboard:
pageTitles: pageTitles:
dashboard: Dashboard
customer: customer:
pageTitles:
customers: Customers
list: List
webPayments: Web Payments
extendedList: Extended list
notifications: Notifications
defaulter: Defaulter
customerCreate: New customer
summary: Summary
basicData: Basic data
fiscalData: Fiscal data
billingData: Billing data
consignees: Consignees
notes: Notes
credits: Credits
greuges: Greuges
balance: Balance
recoveries: Recoveries
webAccess: Web access
log: Log
sms: Sms
creditManagement: Credit management
creditContracts: Credit contracts
creditOpinion: Credit opinion
others: Others
samples: Samples
consumption: Consumption
mandates: Mandates
contacts: Contacts
webPayment: Web payment
fileManagement: File management
unpaid: Unpaid
list: list:
phone: Phone phone: Phone
email: Email email: Email
@ -311,17 +409,6 @@ customer:
hasCoreVnl: VNL core received hasCoreVnl: VNL core received
hasSepaVnl: VNL B2B received hasSepaVnl: VNL B2B received
entry: entry:
pageTitles:
entries: Entries
list: List
summary: Summary
basicData: Basic data
buys: Buys
notes: Notes
dms: File management
log: Log
entryCreate: New entry
latestBuys: Latest buys
list: list:
newEntry: New entry newEntry: New entry
landed: Landed landed: Landed
@ -330,6 +417,18 @@ entry:
booked: Booked booked: Booked
confirmed: Confirmed confirmed: Confirmed
ordered: Ordered ordered: Ordered
tableVisibleColumns:
id: Id
reference: Reference
created: Creation
supplierFk: Supplier
isBooked: Booked
isConfirmed: Confirmed
isOrdered: Ordered
companyFk: Company
travelFk: Travel
isExcludedFromAvailable: Inventory
isRaid: Raid
summary: summary:
commission: Commission commission: Commission
currency: Currency currency: Currency
@ -405,34 +504,37 @@ entry:
landed: Landed landed: Landed
warehouseOut: Warehouse Out warehouseOut: Warehouse Out
latestBuys: latestBuys:
picture: Picture tableVisibleColumns:
itemFk: Item ID image: Picture
packing: Packing itemFk: Item ID
grouping: Grouping packing: Packing
quantity: Quantity grouping: Grouping
size: Size quantity: Quantity
tags: Tags size: Size
type: Type tags: Tags
intrastat: Intrastat type: Type
origin: Origin intrastat: Intrastat
weightByPiece: Weight/Piece origin: Origin
isActive: Active weightByPiece: Weight/Piece
family: Family isActive: Active
entryFk: Entry family: Family
buyingValue: Buying value entryFk: Entry
freightValue: Freight value buyingValue: Buying value
comissionValue: Commission value freightValue: Freight value
description: Description comissionValue: Commission value
packageValue: Package value description: Description
isIgnored: Is ignored packageValue: Package value
price2: Grouping isIgnored: Is ignored
price3: Packing price2: Grouping
minPrice: Min price3: Packing
ektFk: Ekt minPrice: Min
weight: Weight ektFk: Ekt
packagingFk: Package weight: Weight
packingOut: Package out packagingFk: Package
landing: Landing packingOut: Package out
landing: Landing
isExcludedFromAvailable: Es inventory
isRaid: Raid
ticket: ticket:
pageTitles: pageTitles:
tickets: Tickets tickets: Tickets
@ -444,10 +546,14 @@ ticket:
sms: Sms sms: Sms
notes: Notes notes: Notes
sale: Sale sale: Sale
volume: Volume
observation: Notes
ticketAdvance: Advance tickets ticketAdvance: Advance tickets
futureTickets: Future tickets futureTickets: Future tickets
purchaseRequest: Purchase request purchaseRequest: Purchase request
weeklyTickets: Weekly tickets weeklyTickets: Weekly tickets
services: Service
tracking: Tracking
list: list:
nickname: Nickname nickname: Nickname
state: State state: State
@ -522,18 +628,6 @@ ticket:
warehouse: Warehouse warehouse: Warehouse
agency: Agency agency: Agency
claim: claim:
pageTitles:
claims: Claims
list: List
claimCreate: New claim
summary: Summary
basicData: Basic Data
lines: Lines
photos: Photos
development: Development
log: Audit logs
notes: Notes
action: Action
list: list:
customer: Customer customer: Customer
assignedTo: Assigned assignedTo: Assigned
@ -597,14 +691,6 @@ claim:
noData: 'There are no images/videos, click here or drag and drop the file' noData: 'There are no images/videos, click here or drag and drop the file'
dragDrop: Drag and drop it here dragDrop: Drag and drop it here
invoiceOut: invoiceOut:
pageTitles:
invoiceOuts: Invoice out
list: List
negativeBases: Negative Bases
globalInvoicing: Global invoicing
invoiceOutCreate: Create invoice out
summary: Summary
basicData: Basic Data
list: list:
ref: Reference ref: Reference
issued: Issued issued: Issued
@ -672,13 +758,6 @@ invoiceOut:
errors: errors:
downloadCsvFailed: CSV download failed downloadCsvFailed: CSV download failed
shelving: shelving:
pageTitles:
shelving: Shelving
shelvingList: Shelving List
shelvingCreate: New shelving
summary: Summary
basicData: Basic Data
log: Logs
list: list:
parking: Parking parking: Parking
priority: Priority priority: Priority
@ -705,17 +784,6 @@ parking:
info: You can search by parking code info: You can search by parking code
label: Search parking... label: Search parking...
invoiceIn: invoiceIn:
pageTitles:
invoiceIns: Invoices In
list: List
invoiceInCreate: Create invoice in
summary: Summary
basicData: Basic data
vat: VAT
dueDay: Due day
intrastat: Intrastat
corrective: Corrective
log: Logs
list: list:
ref: Reference ref: Reference
supplier: Supplier supplier: Supplier
@ -766,15 +834,6 @@ invoiceIn:
stems: Stems stems: Stems
country: Country country: Country
order: order:
pageTitles:
order: Orders
orderList: List
orderCreate: New order
summary: Summary
basicData: Basic Data
catalog: Catalog
volume: Volume
lines: Lines
field: field:
salesPersonFk: Sales Person salesPersonFk: Sales Person
clientFk: Client clientFk: Client
@ -850,7 +909,6 @@ worker:
timeControl: Time control timeControl: Time control
locker: Locker locker: Locker
balance: Balance balance: Balance
formation: Formation
list: list:
name: Name name: Name
email: Email email: Email
@ -939,15 +997,6 @@ worker:
credit: Have credit: Have
concept: Concept concept: Concept
wagon: wagon:
pageTitles:
wagons: Wagons
wagonsList: Wagons List
wagonCreate: Create wagon
wagonEdit: Edit wagon
typesList: Types List
typeCreate: Create type
typeEdit: Edit type
wagonCounter: Trolley counter
type: type:
name: Name name: Name
submit: Submit submit: Submit
@ -976,20 +1025,9 @@ wagon:
minHeightBetweenTrays: 'The minimum height between trays is ' minHeightBetweenTrays: 'The minimum height between trays is '
maxWagonHeight: 'The maximum height of the wagon is ' maxWagonHeight: 'The maximum height of the wagon is '
uncompleteTrays: There are incomplete trays uncompleteTrays: There are incomplete trays
route/roadmap:
pageTitles:
roadmap: Roadmap
summary: Summary
basicData: Basic Data
stops: Stops
roadmap:
pageTitles:
roadmap: Roadmap
summary: Summary
basicData: Basic Data
stops: Stops
route: route:
pageTitles: pageTitles:
agency: Agency List
routes: Routes routes: Routes
cmrsList: CMRs list cmrsList: CMRs list
RouteList: List RouteList: List
@ -1028,28 +1066,20 @@ route:
volume: Volume volume: Volume
finished: Finished finished: Finished
supplier: supplier:
pageTitles:
suppliers: Suppliers
supplier: Supplier
list: List
supplierCreate: New supplier
summary: Summary
basicData: Basic data
fiscalData: Fiscal data
billingData: Billing data
log: Log
accounts: Accounts
contacts: Contacts
addresses: Addresses
consumption: Consumption
agencyTerm: Agency agreement
dms: File management
list: list:
payMethod: Pay method payMethod: Pay method
payDeadline: Pay deadline payDeadline: Pay deadline
payDay: Pay day payDay: Pay day
account: Account account: Account
newSupplier: New supplier newSupplier: New supplier
tableVisibleColumns:
id: Id
name: Name
nif: NIF/CIF
nickname: Alias
account: Account
payMethod: Pay Method
payDay: Pay Day
summary: summary:
responsible: Responsible responsible: Responsible
notes: Notes notes: Notes
@ -1135,15 +1165,16 @@ supplier:
date: Date date: Date
reference: Reference reference: Reference
travel: travel:
pageTitles: travelList:
travel: Travels tableVisibleColumns:
list: List id: Id
summary: Summary ref: Reference
extraCommunity: Extra community agency: Agency
travelCreate: New travel shipped: Shipped
basicData: Basic data landed: Landed
history: Log warehouseIn: Warehouse in
thermographs: Thermograph warehouseOut: Warehouse out
totalEntries: Total entries
summary: summary:
confirmed: Confirmed confirmed: Confirmed
entryId: Entry Id entryId: Entry Id
@ -1190,24 +1221,6 @@ travel:
travelFileDescription: 'Travel id { travelId }' travelFileDescription: 'Travel id { travelId }'
file: File file: File
item: item:
pageTitles:
items: Items
list: List
diary: Diary
tags: Tags
create: Create
buyRequest: Buy requests
fixedPrice: Fixed prices
wasteBreakdown: Waste breakdown
itemCreate: New item
barcode: Barcodes
tax: Tax
log: Log
botanical: Botanical
shelving: Shelving
itemTypeCreate: New item type
family: Item Type
lastEntries: Last entries
descriptor: descriptor:
item: Item item: Item
buyer: Buyer buyer: Buyer
@ -1293,22 +1306,6 @@ item:
minSalesQuantity: 'Cantidad mínima de venta' minSalesQuantity: 'Cantidad mínima de venta'
genus: 'Genus' genus: 'Genus'
specie: 'Specie' specie: 'Specie'
item/itemType:
pageTitles:
itemType: Item type
basicData: Basic data
summary: Summary
monitor:
pageTitles:
monitors: Monitors
list: List
zone:
pageTitles:
zones: Zones
zonesList: Zones
deliveryList: Delivery days
upcomingList: Upcoming deliveries
components: components:
topbar: {} topbar: {}
itemsFilterPanel: itemsFilterPanel:

View File

@ -90,6 +90,7 @@ globals:
send: Enviar send: Enviar
code: Código code: Código
pageTitles: pageTitles:
logIn: Inicio de sesión
summary: Resumen summary: Resumen
basicData: Datos básicos basicData: Datos básicos
log: Historial log: Historial
@ -100,14 +101,144 @@ globals:
modes: Modos modes: Modos
zones: Zonas zones: Zonas
zonesList: Zonas zonesList: Zonas
deliveryList: Días de entrega deliveryDays: Días de entrega
upcomingList: Próximos repartos upcomingDeliveries: Próximos repartos
role: Role role: Role
alias: Alias alias: Alias
aliasUsers: Usuarios aliasUsers: Usuarios
subRoles: Subroles subRoles: Subroles
inheritedRoles: Roles heredados inheritedRoles: Roles heredados
customers: Clientes
customerCreate: Nuevo cliente
list: Listado
webPayments: Pagos Web
extendedList: Listado extendido
notifications: Notificaciones
defaulter: Morosos
createCustomer: Crear cliente
fiscalData: Datos fiscales
billingData: Forma de pago
consignees: Consignatarios
notes: Notas
credits: Créditos
greuges: Greuges
balance: Balance
recoveries: Recobros
webAccess: Acceso web
sms: Sms
creditManagement: Gestión de crédito
creditContracts: Contratos de crédito
creditOpinion: Opinión de crédito
others: Otros
samples: Plantillas
consumption: Consumo
mandates: Mandatos
contacts: Contactos
webPayment: Pago web
fileManagement: Gestión documental
unpaid: Impago
entries: Entradas
buys: Compras
dms: Gestión documental
entryCreate: Nueva entrada
latestBuys: Últimas compras
tickets: Tickets
ticketCreate: Nuevo ticket
boxing: Encajado
sale: Lineas del pedido
claims: Reclamaciones
claimCreate: Crear reclamación
lines: Líneas
development: Trazabilidad
photos: Fotos
action: Acción
invoiceOuts: Fact. emitidas
negativeBases: Bases Negativas
globalInvoicing: Facturación global
invoiceOutCreate: Crear fact. emitida
order: Cesta
orderList: Listado
orderCreate: Nueva orden
catalog: Catálogo
volume: Volumen
shelving: Carros
shelvingList: Listado de carros
shelvingCreate: Nuevo carro
invoiceIns: Fact. recibidas
invoiceInCreate: Crear fact. recibida
vat: IVA
labeler: Etiquetas
dueDay: Vencimiento
intrastat: Intrastat
corrective: Rectificativa
workers: Trabajadores workers: Trabajadores
workerCreate: Nuevo trabajador
department: Departamentos
pda: PDA
pbx: Centralita
calendar: Calendario
timeControl: Control de horario
locker: Taquilla
wagons: Vagones
wagonsList: Listado vagones
wagonCreate: Crear tipo
wagonEdit: Editar tipo
typesList: Listado tipos
typeCreate: Crear tipo
typeEdit: Editar tipo
wagonCounter: Contador de carros
roadmap: Troncales
stops: Paradas
routes: Rutas
cmrsList: Listado de CMRs
RouteList: Listado
routeCreate: Nueva ruta
RouteRoadmap: Troncales
RouteRoadmapCreate: Crear troncal
autonomous: Autónomos
suppliers: Proveedores
supplier: Proveedor
supplierCreate: Nuevo proveedor
accounts: Cuentas
addresses: Direcciones
agencyTerm: Acuerdo agencia
travel: Envíos
create: Crear
extraCommunity: Extra comunitarios
travelCreate: Nuevo envío
history: Historial
thermographs: Termógrafos
items: Artículos
diary: Histórico
tags: Etiquetas
fixedPrice: Precios fijados
buyRequest: Peticiones de compra
wasteBreakdown: Deglose de mermas
itemCreate: Nuevo artículo
tax: 'IVA'
botanical: 'Botánico'
barcode: 'Código de barras'
itemTypeCreate: Nueva familia
family: Familia
lastEntries: Últimas entradas
itemType: Familia
monitors: Monitores
dashboard: Tablón
users: Usuarios
createTicket: Crear ticket
ticketAdvance: Adelantar tickets
futureTickets: Tickets a futuro
purchaseRequest: Petición de compra
weeklyTickets: Tickets programados
formation: Formación
locations: Ubicaciones
warehouses: Almacenes
roles: Roles
connections: Conexiones
acls: ACLs
mailForwarding: Reenvío de correo
mailAlias: Alias de correo
privileges: Privilegios
created: Fecha creación created: Fecha creación
worker: Trabajador worker: Trabajador
now: Ahora now: Ahora
@ -115,6 +246,7 @@ globals:
new: Nuevo new: Nuevo
comment: Comentario comment: Comentario
observations: Observaciones observations: Observaciones
goToModuleIndex: Ir al índice del módulo
errors: errors:
statusUnauthorized: Acceso denegado statusUnauthorized: Acceso denegado
statusInternalServerError: Ha ocurrido un error interno del servidor statusInternalServerError: Ha ocurrido un error interno del servidor
@ -133,8 +265,6 @@ login:
loginError: Nombre de usuario o contraseña incorrectos loginError: Nombre de usuario o contraseña incorrectos
fieldRequired: Este campo es obligatorio fieldRequired: Este campo es obligatorio
twoFactorRequired: Verificación de doble factor requerida twoFactorRequired: Verificación de doble factor requerida
pageTitles:
logIn: Inicio de sesión
twoFactor: twoFactor:
code: Código code: Código
validate: Validar validate: Validar
@ -147,41 +277,8 @@ verifyEmail:
verifyEmail: Verificación de correo verifyEmail: Verificación de correo
dashboard: dashboard:
pageTitles: pageTitles:
dashboard: Tablón
customer: customer:
pageTitles:
customers: Clientes
customerCreate: Nuevo cliente
list: Listado
webPayments: Pagos Web
extendedList: Listado extendido
notifications: Notificaciones
defaulter: Morosos
createCustomer: Crear cliente
summary: Resumen
basicData: Datos básicos
fiscalData: Datos fiscales
billingData: Forma de pago
consignees: Consignatarios
notes: Notas
credits: Créditos
greuges: Greuges
balance: Balance
recoveries: Recobros
webAccess: Acceso web
log: Historial
sms: Sms
creditManagement: Gestión de crédito
creditContracts: Contratos de crédito
creditOpinion: Opinión de crédito
others: Otros
samples: Plantillas
consumption: Consumo
mandates: Mandatos
contacts: Contactos
webPayment: Pago web
fileManagement: Gestión documental
unpaid: Impago
list: list:
phone: Teléfono phone: Teléfono
email: Email email: Email
@ -310,17 +407,6 @@ customer:
hasCoreVnl: Recibido core VNL hasCoreVnl: Recibido core VNL
hasSepaVnl: Recibido B2B VNL hasSepaVnl: Recibido B2B VNL
entry: entry:
pageTitles:
entries: Entradas
list: Listado
summary: Resumen
basicData: Datos básicos
buys: Compras
notes: Notas
dms: Gestión documental
log: Historial
entryCreate: Nueva entrada
latestBuys: Últimas compras
list: list:
newEntry: Nueva entrada newEntry: Nueva entrada
landed: F. entrega landed: F. entrega
@ -329,6 +415,18 @@ entry:
booked: Asentado booked: Asentado
confirmed: Confirmado confirmed: Confirmado
ordered: Pedida ordered: Pedida
tableVisibleColumns:
id: Id
reference: Referencia
created: Creación
supplierFk: Proveedor
isBooked: Asentado
isConfirmed: Confirmado
isOrdered: Pedida
companyFk: Empresa
travelFk: Envio
isExcludedFromAvailable: Inventario
isRaid: Redada
summary: summary:
commission: Comisión commission: Comisión
currency: Moneda currency: Moneda
@ -404,34 +502,37 @@ entry:
landed: F. entrega landed: F. entrega
warehouseOut: Alm. salida warehouseOut: Alm. salida
latestBuys: latestBuys:
picture: Foto tableVisibleColumns:
itemFk: ID Artículo image: Foto
packing: Packing itemFk: Id Artículo
grouping: Grouping packing: packing
quantity: Cantidad grouping: Grouping
size: Medida quantity: Cantidad
tags: Etiquetas size: Medida
type: Tipo tags: Etiquetas
intrastat: Intrastat type: Tipo
origin: Origen intrastat: Intrastat
weightByPiece: Peso (gramos)/tallo origin: Origen
isActive: Activo weightByPiece: Peso (gramos)/tallo
family: Familia isActive: Activo
entryFk: Entrada family: Familia
buyingValue: Coste entryFk: Entrada
freightValue: Porte buyingValue: Coste
comissionValue: Comisión freightValue: Porte
description: Descripción comissionValue: Comisión
packageValue: Embalaje description: Descripción
isIgnored: Ignorado packageValue: Embalaje
price2: Grouping isIgnored: Ignorado
price3: Packing price2: Grouping
minPrice: Min price3: Packing
ektFk: Ekt minPrice: Min
weight: Peso ektFk: Ekt
packagingFk: Embalaje weight: Peso
packingOut: Embalaje envíos packagingFk: Embalaje
landing: Llegada packingOut: Embalaje envíos
landing: Llegada
isExcludedFromAvailable: Es inventario
isRaid: Redada
ticket: ticket:
pageTitles: pageTitles:
tickets: Tickets tickets: Tickets
@ -443,10 +544,14 @@ ticket:
sms: Sms sms: Sms
notes: Notas notes: Notas
sale: Lineas del pedido sale: Lineas del pedido
volume: Volumen
observation: Notas
ticketAdvance: Adelantar tickets ticketAdvance: Adelantar tickets
futureTickets: Tickets a futuro futureTickets: Tickets a futuro
purchaseRequest: Petición de compra purchaseRequest: Petición de compra
weeklyTickets: Tickets programados weeklyTickets: Tickets programados
services: Servicios
tracking: Estados
list: list:
nickname: Alias nickname: Alias
state: Estado state: Estado
@ -521,18 +626,6 @@ ticket:
warehouse: Almacén warehouse: Almacén
agency: Agencia agency: Agencia
claim: claim:
pageTitles:
claims: Reclamaciones
list: Listado
claimCreate: Crear reclamación
summary: Resumen
basicData: Datos básicos
lines: Líneas
development: Trazabilidad
photos: Fotos
log: Historial
notes: Notas
action: Acción
list: list:
customer: Cliente customer: Cliente
assignedTo: Asignada a assignedTo: Asignada a
@ -596,14 +689,6 @@ claim:
noData: No hay imágenes/videos haz click aquí o arrastra y suelta el archivo noData: No hay imágenes/videos haz click aquí o arrastra y suelta el archivo
dragDrop: Arrástralo y sueltalo aquí dragDrop: Arrástralo y sueltalo aquí
invoiceOut: invoiceOut:
pageTitles:
invoiceOuts: Fact. emitidas
list: Listado
negativeBases: Bases Negativas
globalInvoicing: Facturación global
invoiceOutCreate: Crear fact. emitida
summary: Resumen
basicData: Datos básicos
list: list:
ref: Referencia ref: Referencia
issued: Fecha emisión issued: Fecha emisión
@ -671,15 +756,6 @@ invoiceOut:
errors: errors:
downloadCsvFailed: Error al descargar CSV downloadCsvFailed: Error al descargar CSV
order: order:
pageTitles:
order: Cesta
orderList: Listado
orderCreate: Nueva orden
summary: Resumen
basicData: Datos básicos
catalog: Catálogo
volume: Volumen
lines: Líneas
field: field:
salesPersonFk: Comercial salesPersonFk: Comercial
clientFk: Cliente clientFk: Cliente
@ -721,13 +797,6 @@ order:
price: Precio price: Precio
amount: Monto amount: Monto
shelving: shelving:
pageTitles:
shelving: Carros
shelvingList: Listado de carros
shelvingCreate: Nuevo carro
summary: Resumen
basicData: Datos básicos
log: Historial
list: list:
parking: Parking parking: Parking
priority: Prioridad priority: Prioridad
@ -753,17 +822,6 @@ parking:
info: Puedes buscar por código de parking info: Puedes buscar por código de parking
label: Buscar parking... label: Buscar parking...
invoiceIn: invoiceIn:
pageTitles:
invoiceIns: Fact. recibidas
list: Listado
invoiceInCreate: Crear fact. recibida
summary: Resumen
basicData: Datos básicos
vat: IVA
dueDay: Vencimiento
intrastat: Intrastat
corrective: Rectificativa
log: Historial
list: list:
ref: Referencia ref: Referencia
supplier: Proveedor supplier: Proveedor
@ -846,7 +904,6 @@ worker:
timeControl: Control de horario timeControl: Control de horario
locker: Taquilla locker: Taquilla
balance: Balance balance: Balance
formation: Formación
list: list:
name: Nombre name: Nombre
email: Email email: Email
@ -926,15 +983,6 @@ worker:
credit: Haber credit: Haber
concept: Concepto concept: Concepto
wagon: wagon:
pageTitles:
wagons: Vagones
wagonsList: Listado vagones
wagonCreate: Crear tipo
wagonEdit: Editar tipo
typesList: Listado tipos
typeCreate: Crear tipo
typeEdit: Editar tipo
wagonCounter: Contador de carros
type: type:
name: Nombre name: Nombre
submit: Guardar submit: Guardar
@ -963,36 +1011,12 @@ wagon:
minHeightBetweenTrays: 'La distancia mínima entre bandejas es ' minHeightBetweenTrays: 'La distancia mínima entre bandejas es '
maxWagonHeight: 'La altura máxima del vagón es ' maxWagonHeight: 'La altura máxima del vagón es '
uncompleteTrays: Hay bandejas sin completar uncompleteTrays: Hay bandejas sin completar
route/roadmap:
pageTitles:
roadmap: Troncales
summary: Resumen
basicData: Datos básicos
stops: Paradas
roadmap:
pageTitles:
roadmap: Troncales
summary: Resumen
basicData: Datos básicos
stops: Paradas
route: route:
pageTitles:
routes: Rutas
cmrsList: Listado de CMRs
RouteList: Listado
routeCreate: Nueva ruta
basicData: Datos básicos
summary: Resumen
RouteRoadmap: Troncales
RouteRoadmapCreate: Crear troncal
tickets: Tickets
log: Historial
autonomous: Autónomos
cmr: cmr:
list: list:
results: resultados results: resultados
cmrFk: Id CMR cmrFk: Id CMR
hasCmrDms: Adjuntado en gestdoc hasCmrDms: Gestdoc
'true': 'true':
'false': 'No' 'false': 'No'
ticketFk: Id ticket ticketFk: Id ticket
@ -1015,28 +1039,20 @@ route:
volume: Volumen volume: Volumen
finished: Finalizada finished: Finalizada
supplier: supplier:
pageTitles:
suppliers: Proveedores
supplier: Proveedor
list: Listado
supplierCreate: Nuevo proveedor
summary: Resumen
basicData: Datos básicos
fiscalData: Datos fiscales
billingData: Forma de pago
log: Historial
accounts: Cuentas
contacts: Contactos
addresses: Direcciones
consumption: Consumo
agencyTerm: Acuerdo agencia
dms: Gestión documental
list: list:
payMethod: Método de pago payMethod: Método de pago
payDeadline: Plazo de pago payDeadline: Plazo de pago
payDay: Día de pago payDay: Día de pago
account: Cuenta account: Cuenta
newSupplier: Nuevo proveedor newSupplier: Nuevo proveedor
tableVisibleColumns:
id: Id
name: Nombre
nif: NIF/CIF
nickname: Alias
account: Cuenta
payMethod: Método de pago
payDay: Dia de pago
summary: summary:
responsible: Responsable responsible: Responsable
notes: Notas notes: Notas
@ -1122,16 +1138,16 @@ supplier:
date: Fecha date: Fecha
reference: Referencia reference: Referencia
travel: travel:
pageTitles: travelList:
travel: Envíos tableVisibleColumns:
list: Listado id: Id
create: Crear ref: Referencia
summary: Resumen agency: Agencia
extraCommunity: Extra comunitarios shipped: Enviado
travelCreate: Nuevo envío landed: Llegada
basicData: Datos básicos warehouseIn: Almacén de salida
history: Historial warehouseOut: Almacén de entrada
thermographs: Termógrafos totalEntries: Total de entradas
summary: summary:
confirmed: Confirmado confirmed: Confirmado
entryId: Id entrada entryId: Id entrada
@ -1178,24 +1194,6 @@ travel:
travelFileDescription: 'Id envío { travelId }' travelFileDescription: 'Id envío { travelId }'
file: Fichero file: Fichero
item: item:
pageTitles:
items: Artículos
list: Listado
diary: Histórico
tags: Etiquetas
fixedPrice: Precios fijados
buyRequest: Peticiones de compra
wasteBreakdown: Deglose de mermas
itemCreate: Nuevo artículo
basicData: 'Datos básicos'
tax: 'IVA'
botanical: 'Botánico'
barcode: 'Código de barras'
log: Historial
shelving: Carros
itemTypeCreate: Nueva familia
family: Familia
lastEntries: Últimas entradas
descriptor: descriptor:
item: Artículo item: Artículo
buyer: Comprador buyer: Comprador
@ -1281,27 +1279,6 @@ item:
achieved: 'Conseguido' achieved: 'Conseguido'
concept: 'Concepto' concept: 'Concepto'
state: 'Estado' state: 'Estado'
item/itemType:
pageTitles:
itemType: Familia
basicData: Datos básicos
summary: Resumen
zone:
pageTitles:
zones: Zonas
list: Zonas
deliveryList: Días de entrega
upcomingList: Próximos repartos
role:
pageTitles:
zones: Zonas
list: Zonas
deliveryList: Días de entrega
upcomingList: Próximos repartos
monitor:
pageTitles:
monitors: Monitores
list: Listado
components: components:
topbar: {} topbar: {}
itemsFilterPanel: itemsFilterPanel:

View File

@ -49,20 +49,6 @@ const hasAccount = ref(false);
:title="data.title" :title="data.title"
:subtitle="data.subtitle" :subtitle="data.subtitle"
> >
<template #header-extra-action>
<QBtn
round
flat
size="md"
color="white"
icon="face"
:to="{ name: 'AccountList' }"
>
<QTooltip>
{{ t('Go to module index') }}
</QTooltip>
</QBtn>
</template>
<template #menu> <template #menu>
<AccountDescriptorMenu :has-account="hasAccount" /> <AccountDescriptorMenu :has-account="hasAccount" />
</template> </template>

View File

@ -15,6 +15,10 @@ const $props = defineProps({
required: false, required: false,
default: null, default: null,
}, },
summary: {
type: Object,
default: null,
},
}); });
const route = useRoute(); const route = useRoute();
@ -60,14 +64,14 @@ const removeRole = () => {
<template> <template>
<CardDescriptor <CardDescriptor
ref="descriptor" :url="`VnRoles/${entityId}`"
:url="`VnRoles`"
:filter="filter" :filter="filter"
module="Role" module="Role"
@on-fetch="setData" @on-fetch="setData"
data-key="accountData" data-key="accountData"
:title="data.title" :title="data.title"
:subtitle="data.subtitle" :subtitle="data.subtitle"
:summary="$props.summary"
> >
<template #menu> <template #menu>
<QItem v-ripple clickable @click="removeRole()"> <QItem v-ripple clickable @click="removeRole()">

View File

@ -0,0 +1,17 @@
<script setup>
import RoleDescriptor from './RoleDescriptor.vue';
import RoleSummary from './RoleSummary.vue';
const $props = defineProps({
id: {
type: Number,
required: true,
},
});
</script>
<template>
<QPopupProxy>
<RoleDescriptor v-if="$props.id" :id="$props.id" :summary="RoleSummary" />
</QPopupProxy>
</template>

View File

@ -30,6 +30,7 @@ const filter = {
:url="`VnRoles`" :url="`VnRoles`"
:filter="filter" :filter="filter"
@on-fetch="(data) => (role = data)" @on-fetch="(data) => (role = data)"
data-key="RoleSummary"
> >
<template #header> {{ role.id }} - {{ role.name }} </template> <template #header> {{ role.id }} - {{ role.name }} </template>
<template #body> <template #body>

View File

@ -1,72 +0,0 @@
<script setup>
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import CardList from 'src/components/ui/CardList.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const router = useRouter();
function navigate(id) {
router.push({ path: `/agency/${id}` });
}
function exprBuilder(param, value) {
if (!value) return;
if (param !== 'search') return;
if (!isNaN(value)) return { id: value };
return { name: { like: `%${value}%` } };
}
</script>
<template>
<VnSearchbar
:info="t('You can search by name')"
:label="t('Search agency')"
data-key="AgencyList"
url="Agencies"
/>
<QPage class="column items-center q-pa-md">
<div class="vn-card-list">
<VnPaginate
data-key="AgencyList"
url="Agencies"
order="name"
:expr-builder="exprBuilder"
>
<template #body="{ rows }">
<CardList
:id="row.id"
:key="row.id"
:title="row.name"
@click="navigate(row.id)"
v-for="row of rows"
>
<template #list-items>
<QCheckbox
:label="t('isOwn')"
v-model="row.isOwn"
:disable="true"
/>
<QCheckbox
:label="t('isAnyVolumeAllowed')"
v-model="row.isAnyVolumeAllowed"
:disable="true"
/>
</template>
</CardList>
</template>
</VnPaginate>
</div>
</QPage>
</template>
<i18n>
es:
isOwn: Tiene propietario
isAnyVolumeAllowed: Permite cualquier volumen
Search agency: Buscar agencia
You can search by name: Puedes buscar por nombre
en:
isOwn: Has owner
isAnyVolumeAllowed: Allows any volume
</i18n>

View File

@ -56,8 +56,7 @@ onMounted(async () => {
:url="`Claims/${entityId}`" :url="`Claims/${entityId}`"
:filter="filter" :filter="filter"
module="Claim" module="Claim"
:title="data.title" title="client.name"
:subtitle="data.subtitle"
@on-fetch="setData" @on-fetch="setData"
data-key="Claim" data-key="Claim"
> >

View File

@ -224,7 +224,7 @@ async function changeState(value) {
<QCard class="vn-one"> <QCard class="vn-one">
<VnTitle <VnTitle
:url="`#/claim/${entityId}/basic-data`" :url="`#/claim/${entityId}/basic-data`"
:text="t('claim.pageTitles.basicData')" :text="t('globals.pageTitles.basicData')"
/> />
<VnLv <VnLv
:label="t('claim.summary.created')" :label="t('claim.summary.created')"

View File

@ -38,7 +38,7 @@ const contactChannels = ref([]);
<template #form="{ data, validate }"> <template #form="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<VnInput <VnInput
:label="t('Comercial name')" :label="t('globals.name')"
:rules="validate('client.socialName')" :rules="validate('client.socialName')"
autofocus autofocus
clearable clearable

View File

@ -45,20 +45,6 @@ const setData = (entity) => (data.value = useCardDescription(entity.name, entity
:summary="$props.summary" :summary="$props.summary"
data-key="customerData" data-key="customerData"
> >
<template #header-extra-action>
<QBtn
round
flat
size="sm"
icon="vn:Person"
color="white"
:to="{ name: 'CustomerList' }"
>
<QTooltip>
{{ t('Go to module index') }}
</QTooltip>
</QBtn>
</template>
<template #menu="{ entity }"> <template #menu="{ entity }">
<CustomerDescriptorMenu :customer="entity" /> <CustomerDescriptorMenu :customer="entity" />
</template> </template>

View File

@ -13,7 +13,7 @@ const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const stateStore = computed(() => useStateStore()); const stateStore = computed(() => useStateStore());
const rows = ref([]); const rows = ref([]);
const totalAmount = ref(0); const totalAmount = ref();
const filter = { const filter = {
include: [ include: [
@ -75,7 +75,7 @@ const columns = computed(() => [
}, },
{ {
align: 'left', align: 'left',
field: (value) => value.user.name, field: (value) => value?.user?.name,
label: t('Created by'), label: t('Created by'),
name: 'createdBy', name: 'createdBy',
}, },
@ -87,7 +87,7 @@ const columns = computed(() => [
}, },
{ {
align: 'left', align: 'left',
field: (value) => value.greugeType.name, field: (value) => value?.greugeType?.name,
label: t('Type'), label: t('Type'),
name: 'type', name: 'type',
}, },
@ -108,26 +108,9 @@ const setRows = (data) => {
<template> <template>
<FetchData :filter="filter" @on-fetch="setRows" auto-load url="greuges" /> <FetchData :filter="filter" @on-fetch="setRows" auto-load url="greuges" />
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
</template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="300" show-if-above> <QDrawer v-model="stateStore.rightDrawer" side="right" :width="300" show-if-above>
<QCard class="full-width q-pa-sm"> <QCard class="full-width q-pa-sm">
<h6 class="flex justify-end q-my-lg q-pr-lg" v-if="totalAmount"> <h6 class="flex justify-end q-my-lg q-pr-lg" v-if="totalAmount !== undefined">
<span class="color-vn-label q-mr-md">{{ t('Total') }}:</span> <span class="color-vn-label q-mr-md">{{ t('Total') }}:</span>
{{ toCurrency(totalAmount) }} {{ toCurrency(totalAmount) }}
</h6> </h6>

View File

@ -73,7 +73,7 @@ const creditWarning = computed(() => {
:text="t('customer.summary.basicData')" :text="t('customer.summary.basicData')"
/> />
<VnLv :label="t('customer.summary.customerId')" :value="entity.id" /> <VnLv :label="t('customer.summary.customerId')" :value="entity.id" />
<VnLv :label="t('customer.summary.name')" :value="entity.name" /> <VnLv :label="t('globals.name')" :value="entity.name" />
<VnLv :label="t('customer.summary.contact')" :value="entity.contact" /> <VnLv :label="t('customer.summary.contact')" :value="entity.contact" />
<VnLv :value="entity.phone"> <VnLv :value="entity.phone">
<template #label> <template #label>

View File

@ -38,7 +38,7 @@ const columns = computed(() => [
}, },
{ {
align: 'left', align: 'left',
label: t('customer.extendedList.tableVisibleColumns.name'), label: t('globals.name'),
name: 'name', name: 'name',
isTitle: true, isTitle: true,
create: true, create: true,
@ -65,6 +65,8 @@ const columns = computed(() => [
url: 'Workers/activeWithInheritedRole', url: 'Workers/activeWithInheritedRole',
fields: ['id', 'name'], fields: ['id', 'name'],
where: { role: 'salesPerson' }, where: { role: 'salesPerson' },
optionFilter: 'firstName',
useLike: false,
}, },
create: true, create: true,
columnField: { columnField: {
@ -406,6 +408,7 @@ function handleLocation(data, location) {
default-mode="table" default-mode="table"
redirect="customer" redirect="customer"
auto-load auto-load
:disable-option="{ card: true }"
> >
<template #more-create-dialog="{ data }"> <template #more-create-dialog="{ data }">
<VnLocation <VnLocation

View File

@ -91,7 +91,6 @@ const tableColumnComponents = {
props: (prop) => ({ props: (prop) => ({
disable: true, disable: true,
'model-value': prop.value, 'model-value': prop.value,
class: 'disabled-checkbox',
}), }),
event: () => {}, event: () => {},
}, },

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, computed, watch } from 'vue'; import { ref, computed, watch, onMounted } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@ -10,6 +10,7 @@ import useCardDescription from 'src/composables/useCardDescription';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import { toDate } from 'src/filters'; import { toDate } from 'src/filters';
import { usePrintService } from 'composables/usePrintService'; import { usePrintService } from 'composables/usePrintService';
import { getUrl } from 'src/composables/getUrl';
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -17,10 +18,6 @@ const $props = defineProps({
required: false, required: false,
default: null, default: null,
}, },
summary: {
type: Object,
default: null,
},
}); });
const route = useRoute(); const route = useRoute();
@ -28,6 +25,7 @@ const { t } = useI18n();
const { openReport } = usePrintService(); const { openReport } = usePrintService();
const state = useState(); const state = useState();
const entryDescriptorRef = ref(null); const entryDescriptorRef = ref(null);
const url = ref();
const entryFilter = { const entryFilter = {
include: [ include: [
@ -69,6 +67,9 @@ const entryFilter = {
const entityId = computed(() => { const entityId = computed(() => {
return $props.id || route.params.id; return $props.id || route.params.id;
}); });
onMounted(async () => {
url.value = await getUrl('');
});
const data = ref(useCardDescription()); const data = ref(useCardDescription());
const setData = (entity) => const setData = (entity) =>
@ -114,6 +115,7 @@ watch;
:subtitle="data.subtitle" :subtitle="data.subtitle"
@on-fetch="setData" @on-fetch="setData"
data-key="entry" data-key="entry"
:summary="$attrs"
> >
<template #menu="{ entity }"> <template #menu="{ entity }">
<QItem v-ripple clickable @click="showEntryReport(entity)"> <QItem v-ripple clickable @click="showEntryReport(entity)">

View File

@ -1,5 +1,6 @@
<script setup> <script setup>
import EntryDescriptor from './EntryDescriptor.vue'; import EntryDescriptor from './EntryDescriptor.vue';
import EntrySummary from './EntrySummary.vue';
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -11,6 +12,6 @@ const $props = defineProps({
<template> <template>
<QPopupProxy> <QPopupProxy>
<EntryDescriptor v-if="$props.id" :id="$props.id" /> <EntryDescriptor v-if="$props.id" :id="$props.id" :summary="EntrySummary" />
</QPopupProxy> </QPopupProxy>
</template> </template>

View File

@ -11,8 +11,6 @@ import { toDate, toCurrency } from 'src/filters';
import { getUrl } from 'src/composables/getUrl'; import { getUrl } from 'src/composables/getUrl';
import axios from 'axios'; import axios from 'axios';
onUpdated(() => summaryRef.value.fetch());
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();

View File

@ -0,0 +1,120 @@
<script setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { QBtn } from 'quasar';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import { usePrintService } from 'composables/usePrintService';
const { openReport } = usePrintService();
const route = useRoute();
const { t } = useI18n();
const $props = defineProps({
id: {
type: String,
required: false,
default: null,
},
});
const entityId = computed(() => $props.id || route.params.id);
const entriesTableColumns = computed(() => [
{
align: 'left',
name: 'itemFk',
field: 'itemFk',
label: t('globals.id'),
},
{
align: 'left',
name: 'item',
label: t('entry.summary.item'),
field: (row) => row.item.name,
},
{
align: 'left',
name: 'packagingFk',
label: t('entry.summary.package'),
field: 'packagingFk',
},
{
align: 'left',
name: 'stickers',
label: t('entry.summary.stickers'),
field: 'stickers',
},
{
align: 'left',
name: 'packing',
label: t('entry.summary.packing'),
field: 'packing',
},
{
align: 'left',
name: 'grouping',
label: t('entry.summary.grouping'),
field: 'grouping',
},
]);
</script>
<template>
<QDialog ref="dialogRef">
<QCard style="min-width: 800px">
<QCardSection class="row items-center q-pb-none">
<QAvatar
:icon="icon"
color="primary"
text-color="white"
size="xl"
v-if="icon"
/>
<span class="text-h6 text-grey">{{ title }}</span>
<QSpace />
<QBtn icon="close" :disable="isLoading" flat round dense v-close-popup />
</QCardSection>
<QCardActions align="right">
<QBtn
:label="t('Print buys')"
color="primary"
icon="print"
:loading="isLoading"
@click="openReport(`Entries/${entityId}/buy-label`)"
unelevated
autofocus
/>
</QCardActions>
<QCardSection class="row items-center">
<VnPaginate
ref="entryBuysPaginateRef"
:limit="0"
data-key="EntryBuys"
:url="`Entries/${entityId}/getBuys`"
auto-load
>
<template #body="{ rows }">
<QTable
:rows="rows"
:columns="entriesTableColumns"
row-key="id"
flat
dense
class="q-ml-lg"
:grid="$q.screen.lt.md"
:no-data-label="t('globals.noResults')"
>
<template #body="props">
<QTr>
<QTd v-for="col in props.cols" :key="col.name">
{{ col.value }}
</QTd>
</QTr>
</template>
</QTable>
</template>
</VnPaginate>
</QCardSection>
</QCard>
</QDialog>
</template>

View File

@ -1,759 +1,200 @@
<script setup> <script setup>
import { onMounted, ref, computed, reactive, onUnmounted } from 'vue'; import { onMounted, ref, computed, reactive, onUnmounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import FetchData from 'components/FetchData.vue';
import FetchedTags from 'components/ui/FetchedTags.vue';
import EntryDescriptorProxy from './Card/EntryDescriptorProxy.vue';
import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue';
import EditTableCellValueForm from 'src/components/EditTableCellValueForm.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import EntryLatestBuysFilter from './EntryLatestBuysFilter.vue';
import ItemDescriptorProxy from '../Item/Card/ItemDescriptorProxy.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import { useStateStore } from 'stores/useStateStore';
import { toDate, toCurrency } from 'src/filters';
// import { useSession } from 'composables/useSession';
import { dashIfEmpty } from 'src/filters';
import { useArrayData } from 'composables/useArrayData';
import RightMenu from 'src/components/common/RightMenu.vue'; import RightMenu from 'src/components/common/RightMenu.vue';
import VnImg from 'src/components/ui/VnImg.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import EntryLatestBuysFilter from './EntryLatestBuysFilter.vue';
const router = useRouter(); import { useStateStore } from 'stores/useStateStore';
// const { getTokenMultimedia } = useSession();
// const token = getTokenMultimedia();
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
import { toDate } from 'src/filters';
import VnImg from 'src/components/ui/VnImg.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
const rowsFetchDataRef = ref(null); const columns = [
const itemTypesOptions = ref([]);
const originsOptions = ref([]);
const itemFamiliesOptions = ref([]);
const intrastatOptions = ref([]);
const packagingsOptions = ref([]);
const editTableCellDialogRef = ref(null);
const visibleColumns = ref([]);
const allColumnNames = ref([]);
const exprBuilder = (param, value) => {
switch (param) {
case 'id':
case 'size':
case 'weightByPiece':
case 'isActive':
case 'family':
case 'minPrice':
case 'packingOut':
return { [`i.${param}`]: value };
case 'name':
case 'description':
return { [`i.${param}`]: { like: `%${value}%` } };
case 'code':
return { 'it.code': value };
case 'intrastat':
return { 'intr.description': value };
case 'origin':
return { 'ori.code': value };
case 'landing':
return { [`lb.${param}`]: value };
case 'packing':
case 'grouping':
case 'quantity':
case 'entryFk':
case 'buyingValue':
case 'freightValue':
case 'comissionValue':
case 'packageValue':
case 'isIgnored':
case 'price2':
case 'price3':
case 'ektFk':
case 'weight':
case 'packagingFk':
return { [`b.${param}`]: value };
}
};
const params = reactive({});
const arrayData = useArrayData('EntryLatestBuys', {
url: 'Buys/latestBuysFilter',
order: ['itemFk DESC'],
exprBuilder: exprBuilder,
});
const store = arrayData.store;
const rows = computed(() => store.data);
const rowsSelected = ref([]);
const getInputEvents = (col) => {
return col.columnFilter.type === 'select'
? { 'update:modelValue': () => applyColumnFilter(col) }
: {
'keyup.enter': () => applyColumnFilter(col),
};
};
const columns = computed(() => [
{ {
label: t('entry.latestBuys.picture'), align: 'center',
name: 'picture', label: t('entry.latestBuys.tableVisibleColumns.image'),
align: 'left', name: 'image',
columnField: {
component: VnImg,
attrs: (id) => {
return {
id,
width: '50px',
};
},
},
columnFilter: false,
}, },
{ {
label: t('entry.latestBuys.itemFk'), align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.itemFk'),
name: 'itemFk', name: 'itemFk',
field: 'itemFk', isTitle: true,
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.packing'), align: 'left',
field: 'packing', label: t('entry.latestBuys.tableVisibleColumns.packing'),
name: 'packing', name: 'packing',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val),
}, },
{ {
label: t('entry.latestBuys.grouping'), align: 'left',
field: 'grouping', label: t('entry.latestBuys.tableVisibleColumns.grouping'),
name: 'grouping', name: 'grouping',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val),
}, },
{ {
label: t('entry.latestBuys.quantity'), align: 'left',
field: 'quantity', label: t('entry.latestBuys.tableVisibleColumns.quantity'),
name: 'quantity', name: 'quantity',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.description'), align: 'left',
field: 'description', label: t('entry.latestBuys.tableVisibleColumns.description'),
name: 'description', name: 'description',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val),
}, },
{ {
label: t('entry.latestBuys.size'), align: 'left',
field: 'size', label: t('entry.latestBuys.tableVisibleColumns.size'),
name: 'size', name: 'size',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.tags'), align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.tags'),
name: 'tags', name: 'tags',
align: 'left',
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.type'), align: 'left',
field: 'code', label: t('entry.latestBuys.tableVisibleColumns.type'),
name: 'type', name: 'type',
align: 'left',
sortable: true,
columnFilter: {
component: VnSelect,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
options: itemTypesOptions.value,
'option-value': 'code',
'option-label': 'code',
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.intrastat'), align: 'left',
field: 'intrastat', label: t('entry.latestBuys.tableVisibleColumns.intrastat'),
name: 'intrastat', name: 'intrastat',
align: 'left',
sortable: true,
columnFilter: {
component: VnSelect,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
options: intrastatOptions.value,
'option-value': 'description',
'option-label': 'description',
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.origin'), align: 'left',
field: 'origin', label: t('entry.latestBuys.tableVisibleColumns.origin'),
name: 'origin', name: 'origin',
align: 'left',
sortable: true,
columnFilter: {
component: VnSelect,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
options: originsOptions.value,
'option-value': 'code',
'option-label': 'code',
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.weightByPiece'), align: 'left',
field: 'weightByPiece', label: t('entry.latestBuys.tableVisibleColumns.weightByPiece'),
name: 'weightByPiece', name: 'weightByPiece',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val),
}, },
{ {
label: t('entry.latestBuys.isActive'), align: 'left',
field: 'isActive', label: t('entry.latestBuys.tableVisibleColumns.isActive'),
name: 'isActive', name: 'isActive',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.family'), align: 'left',
field: 'family', label: t('entry.latestBuys.tableVisibleColumns.family'),
name: 'family', name: 'family',
align: 'left',
sortable: true,
columnFilter: {
component: VnSelect,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
options: itemFamiliesOptions.value,
'option-value': 'code',
'option-label': 'code',
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.entryFk'), align: 'left',
field: 'entryFk', label: t('entry.latestBuys.tableVisibleColumns.entryFk'),
name: 'entryFk', name: 'entryFk',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.buyingValue'), align: 'left',
field: 'buyingValue', label: t('entry.latestBuys.tableVisibleColumns.buyingValue'),
name: 'buyingValue', name: 'buyingValue',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val),
}, },
{ {
label: t('entry.latestBuys.freightValue'), align: 'left',
field: 'freightValue', label: t('entry.latestBuys.tableVisibleColumns.freightValue'),
name: 'freightValue', name: 'freightValue',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val),
}, },
{ {
label: t('entry.latestBuys.comissionValue'), align: 'left',
field: 'comissionValue', label: t('entry.latestBuys.tableVisibleColumns.comissionValue'),
name: 'comissionValue', name: 'comissionValue',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val),
}, },
{ {
label: t('entry.latestBuys.packageValue'), align: 'left',
field: 'packageValue', label: t('entry.latestBuys.tableVisibleColumns.packageValue'),
name: 'packageValue', name: 'packageValue',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val),
}, },
{ {
label: t('entry.latestBuys.isIgnored'), align: 'left',
field: 'isIgnored', label: t('entry.latestBuys.tableVisibleColumns.isIgnored'),
name: 'isIgnored', name: 'isIgnored',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.price2'), align: 'left',
field: 'price2', label: t('entry.latestBuys.tableVisibleColumns.price2'),
name: 'price2', name: 'price2',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val),
}, },
{ {
label: t('entry.latestBuys.price3'), align: 'left',
field: 'price3', label: t('entry.latestBuys.tableVisibleColumns.price3'),
name: 'price3', name: 'price3',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val),
}, },
{ {
label: t('entry.latestBuys.minPrice'), align: 'left',
field: 'minPrice', label: t('entry.latestBuys.tableVisibleColumns.minPrice'),
name: 'minPrice', name: 'minPrice',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => toCurrency(val),
}, },
{ {
label: t('entry.latestBuys.ektFk'), align: 'left',
field: 'ektFk', label: t('entry.latestBuys.tableVisibleColumns.ektFk'),
name: 'ektFk', name: 'ektFk',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val),
}, },
{ {
label: t('entry.latestBuys.weight'), align: 'left',
field: 'weight', label: t('entry.latestBuys.tableVisibleColumns.weight'),
name: 'weight', name: 'weight',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.packagingFk'), align: 'left',
field: 'packagingFk', label: t('entry.latestBuys.tableVisibleColumns.packagingFk'),
name: 'packagingFk', name: 'packagingFk',
align: 'left',
sortable: true,
columnFilter: {
component: VnSelect,
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: {
options: packagingsOptions.value,
'option-value': 'id',
'option-label': 'id',
dense: true,
},
},
}, },
{ {
label: t('entry.latestBuys.packingOut'), align: 'left',
field: 'packingOut', label: t('entry.latestBuys.tableVisibleColumns.packingOut'),
name: 'packingOut', name: 'packingOut',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val),
}, },
{ {
label: t('entry.latestBuys.landing'),
field: 'landing',
name: 'landing',
align: 'left', align: 'left',
sortable: true, label: t('entry.latestBuys.tableVisibleColumns.landing'),
columnFilter: { name: 'landing',
component: VnInput, component: 'date',
type: 'text', columnField: {
filterValue: null, component: null,
event: getInputEvents,
attrs: {
dense: true,
},
}, },
format: (val) => toDate(val), format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.landing)),
}, },
]);
const editTableCellFormFieldsOptions = [
{ field: 'packing', label: t('entry.latestBuys.packing') },
{ field: 'grouping', label: t('entry.latestBuys.grouping') },
{ field: 'packageValue', label: t('entry.latestBuys.packageValue') },
{ field: 'weight', label: t('entry.latestBuys.weight') },
{ field: 'description', label: t('globals.description') },
{ field: 'size', label: t('entry.latestBuys.size') },
{ field: 'weightByPiece', label: t('entry.latestBuys.weightByPiece') },
{ field: 'packingOut', label: t('entry.latestBuys.packingOut') },
{ field: 'landing', label: t('entry.latestBuys.landing') },
]; ];
const openEditTableCellDialog = () => {
editTableCellDialogRef.value.show();
};
const onEditCellDataSaved = async () => {
rowsSelected.value = [];
await rowsFetchDataRef.value.fetch();
};
const redirectToEntryBuys = (entryFk) => {
router.push({ name: 'EntryBuys', params: { id: entryFk } });
};
const applyColumnFilter = async (col) => {
try {
params[col.field] = col.columnFilter.filterValue;
await arrayData.addFilter({ params });
} catch (err) {
console.error('Error applying column filter', err);
}
};
onMounted(async () => { onMounted(async () => {
stateStore.rightDrawer = true; stateStore.rightDrawer = true;
const filteredColumns = columns.value.filter((col) => col.name !== 'picture');
allColumnNames.value = filteredColumns.map((col) => col.name);
await arrayData.fetch({ append: false });
}); });
onUnmounted(() => (stateStore.rightDrawer = false)); onUnmounted(() => (stateStore.rightDrawer = false));
</script> </script>
<template> <template>
<FetchData
url="ItemTypes"
:filter="{ fields: ['code'], order: 'code ASC', limit: 30 }"
auto-load
@on-fetch="(data) => (itemTypesOptions = data)"
/>
<FetchData
url="Origins"
:filter="{ fields: ['code'], order: 'code ASC', limit: 30 }"
auto-load
@on-fetch="(data) => (originsOptions = data)"
/>
<FetchData
url="ItemFamilies"
:filter="{ fields: ['code'], order: 'code ASC', limit: 30 }"
auto-load
@on-fetch="(data) => (itemFamiliesOptions = data)"
/>
<FetchData
url="Packagings"
:filter="{ fields: ['id'], order: 'id ASC', limit: 30 }"
auto-load
@on-fetch="(data) => (packagingsOptions = data)"
/>
<FetchData
url="Intrastats"
:filter="{ fields: ['description'], order: 'description ASC', limit: 30 }"
auto-load
@on-fetch="(data) => (intrastatOptions = data)"
/>
<VnSubToolbar>
<template #st-data>
<TableVisibleColumns
:all-columns="allColumnNames"
table-code="latestBuys"
labels-traductions-path="entry.latestBuys"
@on-config-saved="visibleColumns = ['picture', ...$event]"
/>
</template>
</VnSubToolbar>
<RightMenu> <RightMenu>
<template #right-panel> <template #right-panel>
<EntryLatestBuysFilter data-key="EntryLatestBuys" /> <EntryLatestBuysFilter data-key="LatestBuys" />
</template> </template>
</RightMenu> </RightMenu>
<Teleport to="#actions-append"> <VnSubToolbar />
<div class="row q-gutter-x-sm"> <VnTable
<QBtn flat @click="stateStore.toggleRightDrawer()" round dense icon="menu"> ref="tableRef"
<QTooltip bottom anchor="bottom right"> data-key="LatestBuys"
{{ t('globals.collapseMenu') }} url="Buys/latestBuysFilter"
</QTooltip> order="id DESC"
</QBtn> :columns="columns"
</div> redirect="entry"
</Teleport> default-mode="table"
<QPage class="column items-center q-pa-md"> auto-load
<QTable :right-search="false"
:rows="rows" />
:columns="columns"
selection="multiple"
row-key="id"
class="full-width q-mt-md"
:visible-columns="visibleColumns"
v-model:selected="rowsSelected"
:no-data-label="t('globals.noResults')"
@row-click="(_, row) => redirectToEntryBuys(row.entryFk)"
>
<template #top-row="{ cols }">
<QTr>
<QTd />
<QTd
v-for="(col, index) in cols"
:key="index"
style="max-width: 100px"
>
<component
:is="col.columnFilter.component"
v-if="col.name !== 'picture'"
v-model="col.columnFilter.filterValue"
v-bind="col.columnFilter.attrs"
v-on="col.columnFilter.event(col)"
dense
/>
</QTd>
</QTr>
</template>
<template #body-cell-picture="{ row }">
<QTd>
<VnImg :id="row.itemFk" size="50x50" class="image" />
</QTd>
</template>
<template #body-cell-itemFk="{ row }">
<QTd @click.stop>
<QBtn flat color="primary">
{{ row.itemFk }}
</QBtn>
<ItemDescriptorProxy :id="row.itemFk" />
</QTd>
</template>
<template #body-cell-tags="{ row }">
<QTd>
<FetchedTags :item="row" :max-length="6" />
</QTd>
</template>
<template #body-cell-entryFk="{ row }">
<QTd @click.stop>
<QBtn flat color="primary">
<EntryDescriptorProxy :id="row.entryFk" />
{{ row.entryFk }}
</QBtn>
</QTd>
</template>
<template #body-cell-isIgnored="{ row }">
<QTd>
<QIcon
:name="row.isIgnored ? `check` : `close`"
:color="row.isIgnored ? `positive` : `negative`"
size="sm"
/>
</QTd>
</template>
<template #body-cell-isActive="{ row }">
<QTd>
<QIcon
:name="row.isActive ? `check` : `close`"
:color="row.isActive ? `positive` : `negative`"
size="sm"
/>
</QTd>
</template>
</QTable>
<QPageSticky v-if="rowsSelected.length > 0" :offset="[20, 20]">
<QBtn @click="openEditTableCellDialog()" color="primary" fab icon="edit" />
<QTooltip>
{{ t('Edit buy(s)') }}
</QTooltip>
</QPageSticky>
<QDialog ref="editTableCellDialogRef">
<EditTableCellValueForm
edit-url="Buys/editLatestBuys"
:rows="rowsSelected"
:fields-options="editTableCellFormFieldsOptions"
@on-data-saved="onEditCellDataSaved()"
/>
</QDialog>
</QPage>
</template> </template>
<i18n> <i18n>

View File

@ -1,31 +1,178 @@
<script setup> <script setup>
import { onMounted } from 'vue'; import { onMounted, ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import VnPaginate from 'src/components/ui/VnPaginate.vue'; import { useRoute } from 'vue-router';
import VnLv from 'src/components/ui/VnLv.vue';
import CardList from 'src/components/ui/CardList.vue';
import EntrySummary from './Card/EntrySummary.vue';
import EntryFilter from './EntryFilter.vue'; import EntryFilter from './EntryFilter.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { toDate } from 'src/filters/index'; import VnTable from 'components/VnTable/VnTable.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import RightMenu from 'src/components/common/RightMenu.vue'; import RightMenu from 'src/components/common/RightMenu.vue';
import { toDate } from 'src/filters';
const stateStore = useStateStore(); const stateStore = useStateStore();
const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const { viewSummary } = useSummaryDialog(); const route = useRoute();
const entityId = computed(() => route.params.id);
const tableRef = ref();
function navigate(id) { const entryFilter = {
router.push({ path: `/entry/${id}` }); include: [
} {
relation: 'suppliers',
const redirectToCreateView = () => { scope: {
router.push({ name: 'EntryCreate' }); fields: ['id', 'name'],
},
},
{
relation: 'travels',
scope: {
fields: ['id', 'ref'],
},
},
{
relation: 'companies',
scope: {
fields: ['id', 'code'],
},
},
],
}; };
const columns = computed(() => [
{
align: 'left',
label: t('entry.list.tableVisibleColumns.id'),
name: 'id',
isTitle: true,
cardVisible: true,
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.reference'),
name: 'reference',
isTitle: true,
component: 'input',
columnField: {
component: null,
},
create: true,
cardVisible: true,
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.created'),
name: 'created',
create: true,
cardVisible: true,
component: 'date',
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.created)),
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.supplierFk'),
name: 'supplierFk',
create: true,
cardVisible: true,
component: 'select',
attrs: {
url: 'suppliers',
fields: ['id', 'name'],
},
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(row.supplierName),
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.isBooked'),
name: 'isBooked',
cardVisible: true,
create: true,
component: 'checkbox',
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.isConfirmed'),
name: 'isConfirmed',
cardVisible: true,
create: true,
component: 'checkbox',
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.isOrdered'),
name: 'isOrdered',
cardVisible: true,
create: true,
component: 'checkbox',
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.companyFk'),
name: 'companyFk',
component: 'select',
attrs: {
url: 'companies',
fields: ['id', 'code'],
optionLabel: 'code',
optionValue: 'id',
},
columnField: {
component: null,
},
create: true,
format: (row, dashIfEmpty) => dashIfEmpty(row.companyCode),
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.travelFk'),
name: 'travelFk',
component: 'select',
attrs: {
url: 'travels',
fields: ['id', 'ref'],
optionLabel: 'ref',
optionValue: 'id',
},
columnField: {
component: null,
},
create: true,
format: (row, dashIfEmpty) => dashIfEmpty(row.travelRef),
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.isExcludedFromAvailable'),
name: 'isExcludedFromAvailable',
chip: {
color: null,
condition: (value) => value,
icon: 'vn:inventory',
},
columnFilter: {
inWhere: true,
},
},
{
align: 'left',
label: t('entry.list.tableVisibleColumns.isRaid'),
name: 'isRaid',
chip: {
color: null,
condition: (value) => value,
icon: 'vn:net',
},
columnFilter: {
inWhere: true,
},
},
]);
onMounted(async () => { onMounted(async () => {
stateStore.rightDrawer = true; stateStore.rightDrawer = true;
}); });
@ -42,83 +189,24 @@ onMounted(async () => {
<EntryFilter data-key="EntryList" /> <EntryFilter data-key="EntryList" />
</template> </template>
</RightMenu> </RightMenu>
<QPage class="column items-center q-pa-md"> <VnTable
<div class="vn-card-list"> ref="tableRef"
<VnPaginate data-key="EntryList"
data-key="EntryList" url="Entries/filter"
url="Entries/filter" :filter="entryFilter"
:order="['landed DESC', 'id DESC']" :create="{
auto-load urlCreate: 'Entries',
> title: 'Create entry',
<template #body="{ rows }"> onDataSaved: ({ id }) => tableRef.redirect(id),
<CardList formInitialData: {},
v-for="row of rows" }"
:key="row.id" order="id DESC"
:title="row.reference" :columns="columns"
@click="navigate(row.id)" redirect="entry"
:id="row.id" default-mode="table"
:has-info-icons="!!row.isExcludedFromAvailable || !!row.isRaid" auto-load
> :right-search="false"
<template #info-icons> />
<QIcon
v-if="row.isExcludedFromAvailable"
name="vn:inventory"
color="primary"
size="xs"
>
<QTooltip>{{ t('Inventory entry') }}</QTooltip>
</QIcon>
<QIcon
v-if="row.isRaid"
name="vn:net"
color="primary"
size="xs"
>
<QTooltip>{{ t('Virtual entry') }}</QTooltip>
</QIcon>
</template>
<template #list-items>
<VnLv :label="t('landed')" :value="toDate(row.landed)" />
<VnLv
:label="t('entry.list.booked')"
:value="!!row.isBooked"
/>
<VnLv
:label="t('entry.list.invoiceNumber')"
:value="row.invoiceNumber"
/>
<VnLv
:label="t('entry.list.confirmed')"
:value="!!row.isConfirmed"
/>
<VnLv
:label="t('entry.list.supplier')"
:value="row.supplierName"
/>
<VnLv
:label="t('entry.list.ordered')"
:value="!!row.isOrdered"
/>
</template>
<template #actions>
<QBtn
:label="t('components.smartCard.openSummary')"
@click.stop="viewSummary(row.id, EntrySummary)"
color="primary"
type="submit"
/>
</template>
</CardList>
</template>
</VnPaginate>
</div>
</QPage>
<QPageSticky :offset="[20, 20]">
<QBtn fab icon="add" color="primary" @click="redirectToCreateView()" />
<QTooltip>
{{ t('entry.list.newEntry') }}
</QTooltip>
</QPageSticky>
</template> </template>
<i18n> <i18n>

View File

@ -0,0 +1,106 @@
<script setup>
import { computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useStateStore } from 'stores/useStateStore';
import { toDate } from 'src/filters/index';
import { useQuasar } from 'quasar';
import EntryBuysTableDialog from './EntryBuysTableDialog.vue';
import VnTable from 'components/VnTable/VnTable.vue';
const stateStore = useStateStore();
const { t } = useI18n();
const quasar = useQuasar();
onMounted(async () => {
stateStore.rightDrawer = true;
});
const columns = computed(() => [
{
align: 'left',
name: 'id',
label: t('customer.extendedList.tableVisibleColumns.id'),
chip: {
condition: () => true,
},
isId: true,
isTitle: false,
},
{
align: 'left',
label: t('shipped'),
name: 'shipped',
isTitle: false,
create: true,
cardVisible: true,
format: ({ shipped }) => toDate(shipped),
},
{
align: 'left',
label: t('landed'),
name: 'landed',
isTitle: false,
create: true,
cardVisible: false,
format: ({ landed }) => toDate(landed),
},
{
align: 'left',
label: t('globals.wareHouseIn'),
name: 'warehouseInName',
isTitle: false,
cardVisible: true,
create: false,
},
{
align: 'right',
name: 'tableActions',
computed,
actions: [
{
title: t('printBuys'),
icon: 'print',
action: (row) => printBuys(row.id),
},
],
},
]);
const printBuys = (rowId) => {
quasar.dialog({
component: EntryBuysTableDialog,
componentProps: {
id: rowId,
},
});
};
</script>
<template>
<VnSearchbar
data-key="EntryList"
url="Entries/filter"
:label="t('Search entries')"
:info="t('You can search by entry reference')"
/>
<QPage class="column items-center q-pa-md">
<div class="vn-card-list">
<VnTable
ref="myEntriesRef"
data-key="myEntriesList"
url="Entries/filter"
:order="['landed DESC', 'id DESC']"
:columns="columns"
default-mode="card"
auto-load
>
</VnTable>
</div>
</QPage>
</template>
<i18n>
es:
Search entries: Buscar entradas
You can search by entry reference: Puedes buscar por referencia de la entrada
</i18n>

View File

@ -8,3 +8,4 @@ entryFilter:
reference: Reference reference: Reference
landed: Landed landed: Landed
shipped: Shipped shipped: Shipped
printBuys: Print buys

View File

@ -1,5 +1,6 @@
Search entries: Buscar entradas Search entries: Buscar entradas
You can search by entry reference: Puedes buscar por referencia de la entrada You can search by entry reference: Puedes buscar por referencia de la entrada
entryList: entryList:
list: list:
inventoryEntry: Es inventario inventoryEntry: Es inventario
@ -11,3 +12,4 @@ entryFilter:
landed: F. llegada landed: F. llegada
shipped: F. salida shipped: F. salida
Print buys: Imprimir etiquetas

View File

@ -1,5 +1,6 @@
<script setup> <script setup>
import InvoiceInDescriptor from "pages/InvoiceIn/Card/InvoiceInDescriptor.vue"; import InvoiceInDescriptor from 'pages/InvoiceIn/Card/InvoiceInDescriptor.vue';
import InvoiceInSummary from './InvoiceInSummary.vue';
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -10,6 +11,10 @@ const $props = defineProps({
</script> </script>
<template> <template>
<QPopupProxy> <QPopupProxy>
<InvoiceInDescriptor v-if="$props.id" :id="$props.id" /> <InvoiceInDescriptor
v-if="$props.id"
:id="$props.id"
:summary="InvoiceInSummary"
/>
</QPopupProxy> </QPopupProxy>
</template> </template>

View File

@ -209,7 +209,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
<QCardSection class="q-pa-none"> <QCardSection class="q-pa-none">
<VnTitle <VnTitle
:url="getLink('basic-data')" :url="getLink('basic-data')"
:text="t('invoiceIn.pageTitles.basicData')" :text="t('globals.pageTitles.basicData')"
/> />
</QCardSection> </QCardSection>
<VnLv <VnLv
@ -240,7 +240,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
<QCardSection class="q-pa-none"> <QCardSection class="q-pa-none">
<VnTitle <VnTitle
:url="getLink('basic-data')" :url="getLink('basic-data')"
:text="t('invoiceIn.pageTitles.basicData')" :text="t('globals.pageTitles.basicData')"
/> />
</QCardSection> </QCardSection>
<VnLv <VnLv
@ -265,7 +265,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
<QCardSection class="q-pa-none"> <QCardSection class="q-pa-none">
<VnTitle <VnTitle
:url="getLink('basic-data')" :url="getLink('basic-data')"
:text="t('invoiceIn.pageTitles.basicData')" :text="t('globals.pageTitles.basicData')"
/> />
</QCardSection> </QCardSection>
<VnLv <VnLv
@ -289,7 +289,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
<QCardSection class="q-pa-none"> <QCardSection class="q-pa-none">
<VnTitle <VnTitle
:url="getLink('basic-data')" :url="getLink('basic-data')"
:text="t('invoiceIn.pageTitles.basicData')" :text="t('globals.pageTitles.basicData')"
/> />
</QCardSection> </QCardSection>
<QCardSection class="q-pa-none"> <QCardSection class="q-pa-none">

View File

@ -150,21 +150,19 @@ const downloadCSV = async () => {
> >
<template #body-cell-clientId="{ row }"> <template #body-cell-clientId="{ row }">
<QTd> <QTd>
<QBtn flat dense color="blue"> {{ row.clientId }}</QBtn> <QBtn flat dense class="link"> {{ row.clientId }}</QBtn>
<CustomerDescriptorProxy :id="row.clientId" /> <CustomerDescriptorProxy :id="row.clientId" />
</QTd> </QTd>
</template> </template>
<template #body-cell-ticketId="{ row }"> <template #body-cell-ticketId="{ row }">
<QTd> <QTd>
<QBtn flat dense color="blue"> {{ row.ticketFk }}</QBtn> <QBtn flat dense class="link"> {{ row.ticketFk }}</QBtn>
<TicketDescriptorProxy :id="row.ticketFk" /> <TicketDescriptorProxy :id="row.ticketFk" />
</QTd> </QTd>
</template> </template>
<template #body-cell-worker="{ row }"> <template #body-cell-worker="{ row }">
<QTd> <QTd>
<QBtn class="no-uppercase" flat dense color="blue">{{ <QBtn class="no-uppercase link" flat dense>{{ row.workerName }}</QBtn>
row.workerName
}}</QBtn>
<WorkerDescriptorProxy :id="row.comercialId" /> <WorkerDescriptorProxy :id="row.comercialId" />
</QTd> </QTd>
</template> </template>

View File

@ -20,6 +20,7 @@ const props = defineProps({
:data-key="props.dataKey" :data-key="props.dataKey"
:search-button="true" :search-button="true"
:unremovable-params="['from', 'to']" :unremovable-params="['from', 'to']"
:hidden-tags="['from', 'to']"
> >
<template #tags="{ tag, formatFn }"> <template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs"> <div class="q-gutter-x-xs">

View File

@ -200,16 +200,10 @@ onUnmounted(() => (stateStore.rightDrawer = false));
<VnInputDate <VnInputDate
:label="t('lastEntries.since')" :label="t('lastEntries.since')"
dense dense
emit-date-format
v-model="from" v-model="from"
class="q-mr-lg" class="q-mr-lg"
/> />
<VnInputDate <VnInputDate :label="t('lastEntries.to')" dense v-model="to" />
:label="t('lastEntries.to')"
dense
emit-date-format
v-model="to"
/>
</div> </div>
<QSpace /> <QSpace />
<div id="st-actions"></div> <div id="st-actions"></div>

View File

@ -192,7 +192,6 @@ const decrement = (paramsObj, key) => {
v-model="params.from" v-model="params.from"
@update:model-value="searchFn()" @update:model-value="searchFn()"
is-outlined is-outlined
emit-date-format
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -203,7 +202,6 @@ const decrement = (paramsObj, key) => {
v-model="params.to" v-model="params.to"
@update:model-value="searchFn()" @update:model-value="searchFn()"
is-outlined is-outlined
emit-date-format
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>

View File

@ -51,20 +51,6 @@ const setData = (entity) => (data.value = useCardDescription(entity.code, entity
@on-fetch="setData" @on-fetch="setData"
data-key="entry" data-key="entry"
> >
<template #header-extra-action>
<QBtn
round
flat
size="sm"
icon="vn:item"
color="white"
:to="{ name: 'ItemTypeList' }"
>
<QTooltip>
{{ t('Go to module index') }}
</QTooltip>
</QBtn>
</template>
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('shared.code')" :value="entity.code" /> <VnLv :label="t('shared.code')" :value="entity.code" />
<VnLv :label="t('shared.name')" :value="entity.name" /> <VnLv :label="t('shared.name')" :value="entity.name" />

View File

@ -169,7 +169,6 @@ const columns = computed(() => [
<VnInputDate <VnInputDate
:label="t('salesClientsTable.from')" :label="t('salesClientsTable.from')"
dense dense
emit-date-format
v-model="from" v-model="from"
class="q-mr-lg" class="q-mr-lg"
style="width: 150px" style="width: 150px"
@ -177,7 +176,6 @@ const columns = computed(() => [
<VnInputDate <VnInputDate
:label="t('salesClientsTable.to')" :label="t('salesClientsTable.to')"
dense dense
emit-date-format
v-model="to" v-model="to"
style="width: 150px" style="width: 150px"
/> />

View File

@ -37,6 +37,10 @@ const selectedOrder = ref(null);
const selectedOrderField = ref(null); const selectedOrderField = ref(null);
const moreFields = ref([]); const moreFields = ref([]);
const moreFieldsOrder = ref([]); const moreFieldsOrder = ref([]);
const selectedTag = ref(null);
const tagValues = ref([{}]);
const tagOptions = ref([]);
const createValue = (val, done) => { const createValue = (val, done) => {
if (val.length > 2) { if (val.length > 2) {
if (!tagOptions.value.includes(val)) { if (!tagOptions.value.includes(val)) {
@ -95,10 +99,6 @@ function exprBuilder(param, value) {
} }
} }
const selectedTag = ref(null);
const tagValues = ref([{}]);
const tagOptions = ref([]);
const applyTagFilter = (params, search) => { const applyTagFilter = (params, search) => {
if (!tagValues.value?.length) { if (!tagValues.value?.length) {
params.tagGroups = null; params.tagGroups = null;
@ -139,39 +139,23 @@ const onOrderChange = (value, params) => {
}; };
const onOrderFieldChange = (value, params) => { const onOrderFieldChange = (value, params) => {
const tagObj = JSON.parse(params.orderBy); // esto donde va const tagObj = JSON.parse(params.orderBy);
const fields = {
Relevancy: (value) => value + ' DESC, name',
ColorAndPrice: 'showOrder, price',
Name: 'name',
Price: 'price',
};
let tagField = fields[value];
if (!tagField) return;
if (typeof tagField === 'function') tagField = tagField(value);
tagObj.field = tagField;
params.orderBy = JSON.stringify(tagObj);
switch (value) { switch (value) {
case 'Relevancy': case 'Relevancy':
tagObj.field = value + ' DESC, name'; tagObj.name = value + ' DESC, name';
params.orderBy = JSON.stringify(tagObj); params.orderBy = JSON.stringify(tagObj);
console.log('params: ', params);
break; break;
case 'ColorAndPrice': case 'ColorAndPrice':
tagObj.field = 'showOrder, price'; tagObj.name = 'showOrder, price';
params.orderBy = JSON.stringify(tagObj); params.orderBy = JSON.stringify(tagObj);
console.log('params: ', params);
break; break;
case 'Name': case 'Name':
tagObj.field = 'name'; tagObj.name = 'name';
params.orderBy = JSON.stringify(tagObj); params.orderBy = JSON.stringify(tagObj);
console.log('params: ', params);
break; break;
case 'Price': case 'Price':
tagObj.field = 'price'; tagObj.name = 'price';
params.orderBy = JSON.stringify(tagObj); params.orderBy = JSON.stringify(tagObj);
console.log('params: ', params);
break; break;
} }
}; };
@ -312,6 +296,7 @@ const useLang = (values) => {
v-model="selectedOrder" v-model="selectedOrder"
:options="moreFields" :options="moreFields"
option-label="label" option-label="label"
option-value="way"
dense dense
outlined outlined
rounded rounded

View File

@ -27,7 +27,7 @@ const dialog = ref(null);
<div class="container order-catalog-item overflow-hidden"> <div class="container order-catalog-item overflow-hidden">
<QCard class="card shadow-6"> <QCard class="card shadow-6">
<div class="img-wrapper"> <div class="img-wrapper">
<VnImg :id="item.id" class="image" /> <VnImg :id="item.id" zoom-size="lg" class="image" />
<div v-if="item.hex" class="item-color-container"> <div v-if="item.hex" class="item-color-container">
<div <div
class="item-color" class="item-color"

View File

@ -122,8 +122,6 @@ const orderFilter = {
const onClientChange = async (clientId) => { const onClientChange = async (clientId) => {
try { try {
const { data } = await axios.get(`Clients/${clientId}`); const { data } = await axios.get(`Clients/${clientId}`);
console.log('info cliente: ', data);
await fetchAddressList(data.defaultAddressFk); await fetchAddressList(data.defaultAddressFk);
} catch (error) { } catch (error) {
console.error('Error al cambiar el cliente:', error); console.error('Error al cambiar el cliente:', error);

View File

@ -7,6 +7,8 @@ import VnLv from 'components/ui/VnLv.vue';
import CardSummary from 'components/ui/CardSummary.vue'; import CardSummary from 'components/ui/CardSummary.vue';
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
import FetchedTags from 'components/ui/FetchedTags.vue'; import FetchedTags from 'components/ui/FetchedTags.vue';
import VnTitle from 'src/components/common/VnTitle.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
@ -62,6 +64,10 @@ const detailsColumns = ref([
</template> </template>
<template #body="{ entity }"> <template #body="{ entity }">
<QCard class="vn-one"> <QCard class="vn-one">
<VnTitle
:url="`#/order/${entity.id}/basic-data`"
:text="t('globals.pageTitles.basicData')"
/>
<VnLv label="ID" :value="entity.id" /> <VnLv label="ID" :value="entity.id" />
<VnLv :label="t('order.summary.nickname')" dash> <VnLv :label="t('order.summary.nickname')" dash>
<template #value> <template #value>
@ -81,6 +87,10 @@ const detailsColumns = ref([
/> />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<VnTitle
:url="`#/order/${entity.id}/basic-data`"
:text="t('globals.pageTitles.basicData')"
/>
<VnLv <VnLv
:label="t('order.summary.created')" :label="t('order.summary.created')"
:value="toDateHourMinSec(entity?.created)" :value="toDateHourMinSec(entity?.created)"
@ -116,14 +126,13 @@ const detailsColumns = ref([
/> />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<p class="header"> <VnTitle :text="t('globals.pageTitles.notes')" />
{{ t('order.summary.notes') }}
</p>
<p v-if="entity?.note" class="no-margin"> <p v-if="entity?.note" class="no-margin">
{{ entity?.note }} {{ entity?.note }}
</p> </p>
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<VnTitle :text="t('order.summary.total')" />
<VnLv> <VnLv>
<template #label> <template #label>
<span class="text-h6">{{ t('order.summary.subtotal') }}</span> <span class="text-h6">{{ t('order.summary.subtotal') }}</span>
@ -152,9 +161,7 @@ const detailsColumns = ref([
</VnLv> </VnLv>
</QCard> </QCard>
<QCard> <QCard>
<p class="header"> <VnTitle :text="t('order.summary.details')" />
{{ t('order.summary.details') }}
</p>
<QTable :columns="detailsColumns" :rows="entity?.rows" flat> <QTable :columns="detailsColumns" :rows="entity?.rows" flat>
<template #header="props"> <template #header="props">
<QTr :props="props"> <QTr :props="props">
@ -168,7 +175,10 @@ const detailsColumns = ref([
<template #body="props"> <template #body="props">
<QTr :props="props"> <QTr :props="props">
<QTd key="item" :props="props" class="item"> <QTd key="item" :props="props" class="item">
{{ props.row.item?.id }} <span class="link">
{{ props.row.item?.id }}
<ItemDescriptorProxy :id="props.row.item?.id" />
</span>
</QTd> </QTd>
<QTd key="description" :props="props" class="description"> <QTd key="description" :props="props" class="description">
<div class="name"> <div class="name">

View File

@ -70,6 +70,7 @@ function extractValueTags(items) {
:user-params="catalogParams" :user-params="catalogParams"
auto-load auto-load
@on-fetch="extractTags" @on-fetch="extractTags"
:update-router="false"
> >
<template #body="{ rows }"> <template #body="{ rows }">
<div class="catalog-list"> <div class="catalog-list">

View File

@ -9,7 +9,7 @@ import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue';
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
import VnPaginate from 'components/ui/VnPaginate.vue'; import VnPaginate from 'components/ui/VnPaginate.vue';
import VnLv from 'components/ui/VnLv.vue'; import VnLv from 'components/ui/VnLv.vue';
import OrderSearchbar from 'pages/Order/Card/OrderSearchbar.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import OrderFilter from 'pages/Order/Card/OrderFilter.vue'; import OrderFilter from 'pages/Order/Card/OrderFilter.vue';
import OrderSummary from 'pages/Order/Card/OrderSummary.vue'; import OrderSummary from 'pages/Order/Card/OrderSummary.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
@ -28,7 +28,11 @@ function navigate(id) {
} }
</script> </script>
<template> <template>
<OrderSearchbar /> <VnSearchbar
data-key="OrderList"
:label="t('Search order')"
:info="t('You can search orders by reference')"
/>
<RightMenu> <RightMenu>
<template #right-panel> <template #right-panel>
<OrderFilter data-key="OrderList" /> <OrderFilter data-key="OrderList" />

View File

@ -2,7 +2,6 @@
import { computed } from 'vue'; import { computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useArrayData } from 'src/composables/useArrayData';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'components/ui/VnLv.vue'; import VnLv from 'components/ui/VnLv.vue';
@ -17,8 +16,7 @@ const props = defineProps({
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const entityId = computed(() => props.id || route.params.id); const entityId = computed(() => props.id || route.params.id);
const { store } = useArrayData('Parking');
const parking = computed(() => store.data);
const filter = { const filter = {
fields: ['id', 'sectorFk', 'code', 'pickingOrder', 'row', 'column'], fields: ['id', 'sectorFk', 'code', 'pickingOrder', 'row', 'column'],
include: [{ relation: 'sector', scope: { fields: ['id', 'description'] } }], include: [{ relation: 'sector', scope: { fields: ['id', 'description'] } }],
@ -29,15 +27,13 @@ const filter = {
module="Parking" module="Parking"
data-key="Parking" data-key="Parking"
:url="`Parkings/${entityId}`" :url="`Parkings/${entityId}`"
:title="parking?.code" title="code"
:subtitle="parking?.id"
:filter="filter" :filter="filter"
@on-fetch="(data) => (parking = data)"
> >
<template #body> <template #body="{ entity }">
<VnLv :label="t('globals.code')" :value="parking.code" /> <VnLv :label="t('globals.code')" :value="entity.code" />
<VnLv :label="t('parking.pickingOrder')" :value="parking.pickingOrder" /> <VnLv :label="t('parking.pickingOrder')" :value="entity.pickingOrder" />
<VnLv :label="t('parking.sector')" :value="parking.sector?.description" /> <VnLv :label="t('parking.sector')" :value="entity.sector?.description" />
</template> </template>
</CardDescriptor> </CardDescriptor>
</template> </template>

View File

@ -1,10 +1,9 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import CardSummary from 'components/ui/CardSummary.vue'; import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'components/ui/VnLv.vue'; import VnLv from 'components/ui/VnLv.vue';
import { useArrayData } from 'src/composables/useArrayData';
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -15,9 +14,7 @@ const $props = defineProps({
const router = useRoute(); const router = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const entityId = computed(() => $props.id || router.params.id); const entityId = computed(() => $props.id || router.params.id);
const { store } = useArrayData('Parking');
const parking = ref(store.data);
const filter = { const filter = {
fields: ['id', 'sectorFk', 'code', 'pickingOrder', 'row', 'column'], fields: ['id', 'sectorFk', 'code', 'pickingOrder', 'row', 'column'],
include: [{ relation: 'sector', scope: { fields: ['id', 'description'] } }], include: [{ relation: 'sector', scope: { fields: ['id', 'description'] } }],
@ -26,14 +23,9 @@ const filter = {
<template> <template>
<div class="q-pa-md"> <div class="q-pa-md">
<CardSummary <CardSummary :url="`Parkings/${entityId}`" data-key="Parking" :filter="filter">
:url="`Parkings/${entityId}`" <template #header="{ entity }">{{ entity.code }}</template>
:filter="filter" <template #body="{ entity }">
@on-fetch="(data) => (parking = data)"
data-key="Parking"
>
<template #header>{{ parking.code }}</template>
<template #body>
<QCard class="vn-one"> <QCard class="vn-one">
<QCardSection class="q-pa-none"> <QCardSection class="q-pa-none">
<a <a
@ -44,17 +36,17 @@ const filter = {
<QIcon name="open_in_new" /> <QIcon name="open_in_new" />
</a> </a>
</QCardSection> </QCardSection>
<VnLv :label="t('globals.code')" :value="parking.code" /> <VnLv :label="t('globals.code')" :value="entity.code" />
<VnLv <VnLv
:label="t('parking.pickingOrder')" :label="t('parking.pickingOrder')"
:value="parking.pickingOrder" :value="entity.pickingOrder"
/> />
<VnLv <VnLv
:label="t('parking.sector')" :label="t('parking.sector')"
:value="parking.sector?.description" :value="entity.sector?.description"
/> />
<VnLv :label="t('parking.row')" :value="parking.row" /> <VnLv :label="t('parking.row')" :value="entity.row" />
<VnLv :label="t('parking.column')" :value="parking.column" /> <VnLv :label="t('parking.column')" :value="entity.column" />
</QCard> </QCard>
</template> </template>
</CardSummary> </CardSummary>

View File

@ -0,0 +1,92 @@
<script setup>
import { computed } from 'vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import VnTable from 'components/VnTable/VnTable.vue';
const { t } = useI18n();
const router = useRouter();
function navigate(id) {
router.push({ path: `/agency/${id}` });
}
const exprBuilder = (param, value) => {
if (!value) return;
if (param !== 'search') return;
if (!isNaN(value)) return { id: value };
return { name: { like: `%${value}%` } };
};
const columns = computed(() => [
{
align: 'left',
name: 'id',
label: 'Id',
chip: {
condition: () => true,
},
isId: true,
},
{
align: 'left',
label: t('globals.name'),
name: 'name',
isTitle: true,
},
{
align: 'left',
label: t('isOwn'),
name: 'isOwn',
component: 'checkbox',
cardVisible: true,
},
{
align: 'left',
label: t('isAnyVolumeAllowed'),
name: 'isAnyVolumeAllowed',
component: 'checkbox',
cardVisible: true,
disable: true,
},
{
align: 'right',
label: '',
name: 'tableActions',
actions: [
{
title: t('Client ticket list'),
icon: 'preview',
action: (row) => navigate(row.id),
},
],
},
]);
</script>
<template>
<VnSearchbar
:info="t('You can search by name')"
:label="t('Search agency')"
data-key="AgencyList"
:expr-builder="exprBuilder"
/>
<VnTable
ref="tableRef"
data-key="AgencyList"
url="Agencies"
order="name"
:columns="columns"
:right-search="false"
:use-model="true"
/>
</template>
<i18n>
es:
isOwn: Tiene propietario
isAnyVolumeAllowed: Permite cualquier volumen
Search agency: Buscar agencia
You can search by name: Puedes buscar por nombre
en:
isOwn: Has owner
isAnyVolumeAllowed: Allows any volume
</i18n>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import AgencyDescriptor from 'pages/Agency/Card/AgencyDescriptor.vue'; import AgencyDescriptor from 'pages/Route/Agency/Card/AgencyDescriptor.vue';
import VnCard from 'components/common/VnCard.vue'; import VnCard from 'components/common/VnCard.vue';
</script> </script>
<template> <template>

View File

@ -18,12 +18,10 @@ const entityId = computed(() => $props.id || useRoute().params.id);
<template #header="{ entity: agency }">{{ agency.name }}</template> <template #header="{ entity: agency }">{{ agency.name }}</template>
<template #body="{ entity: agency }"> <template #body="{ entity: agency }">
<QCard class="vn-one"> <QCard class="vn-one">
<QCardSection class="q-pa-none"> <VnTitle
<VnTitle :url="`#/agency/${entityId}/basic-data`"
:url="`#/agency/${entityId}/basic-data`" :text="t('globals.pageTitles.basicData')"
:text="t('globals.pageTitles.basicData')" />
/>
</QCardSection>
<VnLv :label="t('globals.name')" :value="agency.name" /> <VnLv :label="t('globals.name')" :value="agency.name" />
<QCheckbox <QCheckbox
:label="t('agency.isOwn')" :label="t('agency.isOwn')"

View File

@ -74,8 +74,9 @@ const setData = (entity) => (data.value = useCardDescription(entity.code, entity
:filter="filter" :filter="filter"
:title="data.title" :title="data.title"
:subtitle="data.subtitle" :subtitle="data.subtitle"
data-key="Routes" data-key="routeData"
@on-fetch="setData" @on-fetch="setData"
:summary="$attrs"
> >
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('Date')" :value="toDate(entity?.created)" /> <VnLv :label="t('Date')" :value="toDate(entity?.created)" />

View File

@ -1,5 +1,6 @@
<script setup> <script setup>
import RouteDescriptor from 'pages/Route/Card/RouteDescriptor.vue'; import RouteDescriptor from 'pages/Route/Card/RouteDescriptor.vue';
import RouteSummary from './RouteSummary.vue';
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -10,6 +11,6 @@ const $props = defineProps({
</script> </script>
<template> <template>
<QPopupProxy> <QPopupProxy>
<RouteDescriptor v-if="$props.id" :id="$props.id" /> <RouteDescriptor v-if="$props.id" :id="$props.id" :summary="RouteSummary" />
</QPopupProxy> </QPopupProxy>
</template> </template>

View File

@ -9,7 +9,6 @@ const { t } = useI18n();
data-key="RouteList" data-key="RouteList"
:label="t('Search route')" :label="t('Search route')"
:info="t('You can search by route reference')" :info="t('You can search by route reference')"
custom-route-redirect-name="RouteList"
/> />
</template> </template>

View File

@ -154,7 +154,7 @@ const warehouses = ref();
es: es:
params: params:
cmrFk: Id cmr cmrFk: Id cmr
hasCmrDms: Adjuntado en gestdoc hasCmrDms: Gestdoc
ticketFk: Id ticket ticketFk: Id ticket
country: País country: País
clientFk: Id cliente clientFk: Id cliente

View File

@ -12,6 +12,8 @@ import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy
import RightMenu from 'src/components/common/RightMenu.vue'; import RightMenu from 'src/components/common/RightMenu.vue';
import { useStateStore } from 'src/stores/useStateStore'; import { useStateStore } from 'src/stores/useStateStore';
import VnTable from 'components/VnTable/VnTable.vue';
const { t } = useI18n(); const { t } = useI18n();
const { getTokenMultimedia } = useSession(); const { getTokenMultimedia } = useSession();
const token = getTokenMultimedia(); const token = getTokenMultimedia();
@ -107,88 +109,15 @@ function downloadPdfs() {
} }
</script> </script>
<template> <template>
<RightMenu> <VnTable
<template #right-panel> ref="tableRef"
<CmrFilter data-key="CmrList" /> data-key="CmrList"
</template> url="Routes/cmrs"
</RightMenu> order="cmrFk DESC"
<div class="column items-center"> :columns="columns"
<div class="list"> :right-search="true"
<VnPaginate data-key="CmrList" :url="`Routes/cmrs`" order="cmrFk DESC"> :use-model="true"
<template #body="{ rows }"> />
<QTable
:columns="columns"
:rows="rows"
:dense="$q.screen.lt.md"
row-key="cmrFk"
selection="multiple"
v-model:selected="selected"
:grid="$q.screen.lt.md"
auto-load
>
<template #top>
<div style="width: 100%; display: table">
<div style="float: right; color: lightgray">
{{ `${rows.length} ${t('route.cmr.list.results')}` }}
</div>
</div>
</template>
<template #body-cell-hasCmrDms="{ value }">
<QTd align="center">
<QBadge
text-color="black"
:id="value ? 'true' : 'false'"
:label="
value
? t('route.cmr.list.true')
: t('route.cmr.list.false')
"
/>
</QTd>
</template>
<template #body-cell-ticketFk="{ value }">
<QTd align="right" class="text-primary">
<span class="text-primary link">{{ value }}</span>
<TicketDescriptorProxy :id="value" />
</QTd>
</template>
<template #body-cell-clientFk="{ value }">
<QTd align="right" class="text-primary">
<span class="text-primary link">{{ value }}</span>
<CustomerDescriptorProxy :id="value" />
</QTd>
</template>
<template #body-cell-warehouseFk="{ value }">
<QTd align="center">
{{ warehouses.find(({ id }) => id === value)?.name }}
</QTd>
</template>
<template #body-cell-icons="{ value }">
<QTd align="center">
<a :href="getCmrUrl(value)" target="_blank">
<QIcon
name="visibility"
color="primary"
size="md"
class="q-mr-sm q-ml-sm"
/>
<QTooltip>
{{ t('route.cmr.list.viewCmr') }}
</QTooltip>
</a>
</QTd>
</template>
</QTable>
</template>
</VnPaginate>
</div>
<QPageSticky :offset="[20, 20]">
<QBtn @click="downloadPdfs" fab icon="cloud_download" color="primary" />
<QTooltip>
{{ t('route.cmr.list.downloadCmrs') }}
</QTooltip>
</QPageSticky>
</div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.list { .list {

View File

@ -1,29 +1,32 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n'; import { useArrayData } from 'composables/useArrayData';
import { computed, onMounted, ref } from 'vue';
import { dashIfEmpty, toHour } from 'src/filters'; import { dashIfEmpty, toHour } from 'src/filters';
import { computed, onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useValidator } from 'composables/useValidator'; import { useValidator } from 'composables/useValidator';
import { useSession } from 'composables/useSession'; import { useSession } from 'composables/useSession';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { useArrayData } from 'composables/useArrayData';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import axios from 'axios'; import axios from 'axios';
import RouteSearchbar from 'pages/Route/Card/RouteSearchbar.vue'; import RouteSearchbar from 'pages/Route/Card/RouteSearchbar.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue';
import RouteSummary from 'pages/Route/Card/RouteSummary.vue';
import RouteFilter from 'pages/Route/Card/RouteFilter.vue';
import RouteListTicketsDialog from 'pages/Route/Card/RouteListTicketsDialog.vue'; import RouteListTicketsDialog from 'pages/Route/Card/RouteListTicketsDialog.vue';
import { useRouter } from 'vue-router';
import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue';
import RouteFilter from 'pages/Route/Card/RouteFilter.vue';
import RightMenu from 'src/components/common/RightMenu.vue'; import RightMenu from 'src/components/common/RightMenu.vue';
import RouteSummary from 'pages/Route/Card/RouteSummary.vue';
import VnPaginate from 'components/ui/VnPaginate.vue'; import VnPaginate from 'components/ui/VnPaginate.vue';
import VnSelect from 'components/common/VnSelect.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'components/common/VnInput.vue'; import VnInput from 'components/common/VnInput.vue';
import VnInputTime from 'components/common/VnInputTime.vue'; import VnInputTime from 'components/common/VnInputTime.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnSelect from 'components/common/VnSelect.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import VnTable from 'components/VnTable/VnTable.vue';
import { useStateStore } from 'src/stores/useStateStore';
const { t } = useI18n(); const { t } = useI18n();
const { validate } = useValidator(); const { validate } = useValidator();
@ -40,90 +43,100 @@ const allColumnNames = ref([]);
const confirmationDialog = ref(false); const confirmationDialog = ref(false);
const startingDate = ref(null); const startingDate = ref(null);
const refreshKey = ref(0); const refreshKey = ref(0);
const router = useRouter();
const stateStore = useStateStore();
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'Id', align: 'left',
label: t('Id'), name: 'id',
field: (row) => row.id, label: 'Id',
sortable: true, chip: {
align: 'center', condition: () => true,
},
isId: true,
columnFilter: {
name: 'search',
},
}, },
{ {
align: 'left',
name: 'worker', name: 'worker',
label: t('Worker'), label: t('Worker'),
field: (row) => row.workerUserName, create: true,
sortable: true, component: 'select',
align: 'left', attrs: {
url: 'payrollComponents',
fields: ['id', 'name'],
},
cardVisible: true,
}, },
{ {
name: 'agency', name: 'agency',
label: t('Agency'), label: t('Agency'),
field: (row) => row.agencyName,
sortable: true,
align: 'left', align: 'left',
isTitle: true,
cardVisible: true,
create: true,
}, },
{ {
name: 'vehicle', name: 'vehicle',
label: t('Vehicle'), label: t('Vehicle'),
field: (row) => row.vehiclePlateNumber,
sortable: true,
align: 'left', align: 'left',
cardVisible: true,
create: true,
}, },
{ {
name: 'date', name: 'date',
label: t('Date'), label: t('Date'),
field: (row) => row.created,
sortable: true,
align: 'left', align: 'left',
cardVisible: true,
create: true,
}, },
{ {
name: 'volume', name: 'volume',
label: 'm³', label: 'm³',
field: (row) => dashIfEmpty(row.m3),
sortable: true,
align: 'center', align: 'center',
cardVisible: true,
}, },
{ {
name: 'description', name: 'description',
label: t('Description'), label: t('Description'),
field: (row) => row.description,
sortable: true,
align: 'left', align: 'left',
isTitle: true,
create: true,
}, },
{ {
name: 'started', name: 'started',
label: t('hourStarted'), label: t('hourStarted'),
field: (row) => toHour(row.started),
sortable: true,
align: 'left', align: 'left',
}, },
{ {
name: 'finished', name: 'finished',
label: t('hourFinished'), label: t('hourFinished'),
field: (row) => toHour(row.finished),
sortable: true,
align: 'left', align: 'left',
}, },
{ {
name: 'isServed', name: 'isServed',
label: t('Served'), label: t('Served'),
field: (row) => Boolean(row.isOk),
sortable: true,
align: 'left', align: 'left',
}, },
{ {
name: 'actions',
label: '',
sortable: false,
align: 'right', align: 'right',
label: 'asdasd',
name: 'tableActions',
actions: [
{
title: t('Client ticket list'),
icon: 'preview',
action: (row) => navigate(row.id),
},
],
}, },
]); ]);
const arrayData = useArrayData('EntryLatestBuys', { function navigate(id) {
url: 'Buys/latestBuysFilter', router.push({ path: `/route/${id}` });
order: ['itemFk DESC'], }
});
const updateRoute = async (route) => { const updateRoute = async (route) => {
try { try {
@ -181,20 +194,10 @@ const openTicketsDialog = (id) => {
}) })
.onOk(() => refreshKey.value++); .onOk(() => refreshKey.value++);
}; };
onMounted(async () => {
allColumnNames.value = columns.value.map((col) => col.name);
await arrayData.fetch({ append: false });
});
</script> </script>
<template> <template>
<RouteSearchbar /> <RouteSearchbar />
<RightMenu>
<template #right-panel>
<RouteFilter data-key="RouteList" />
</template>
</RightMenu>
<QDialog v-model="confirmationDialog"> <QDialog v-model="confirmationDialog">
<QCard style="min-width: 350px"> <QCard style="min-width: 350px">
<QCardSection> <QCardSection>
@ -227,256 +230,53 @@ onMounted(async () => {
/> />
<FetchData url="AgencyModes" @on-fetch="(data) => (agencyList = data)" auto-load /> <FetchData url="AgencyModes" @on-fetch="(data) => (agencyList = data)" auto-load />
<FetchData url="Vehicles" @on-fetch="(data) => (vehicleList = data)" auto-load /> <FetchData url="Vehicles" @on-fetch="(data) => (vehicleList = data)" auto-load />
<QPage class="column items-center"> <VnSubToolbar />
<VnSubToolbar> <VnTable
<template #st-data> ref="tableRef"
<TableVisibleColumns data-key="RouteList"
class="LeftIcon" url="Routes/filter"
:all-columns="allColumnNames" :columns="columns"
table-code="routesList" :right-search="true"
labels-traductions-path="route.columnLabels" default-mode="table"
@on-config-saved="visibleColumns = [...$event]" :is-editable="true"
/> :create="{
</template> urlCreate: 'Routes',
<template #st-actions> title: t('Create route'),
<QBtn onDataSaved: () => tableRef.reload(),
icon="vn:clone" }"
color="primary" save-url="routes"
class="q-mr-sm" :disable-option="{ card: true }"
:disable="!selectedRows?.length" >
@click="confirmationDialog = true" <template #moreBeforeActions>
> <QBtn
<QTooltip>{{ t('Clone Selected Routes') }}</QTooltip> icon="vn:clone"
</QBtn> color="primary"
<QBtn class="q-mr-sm"
icon="cloud_download" :disable="!selectedRows?.length"
color="primary" @click="confirmationDialog = true"
class="q-mr-sm"
:disable="!selectedRows?.length"
@click="showRouteReport"
>
<QTooltip>{{ t('Download selected routes as PDF') }}</QTooltip>
</QBtn>
<QBtn
icon="check"
color="primary"
class="q-mr-sm"
:disable="!selectedRows?.length"
@click="markAsServed()"
>
<QTooltip>{{ t('Mark as served') }}</QTooltip>
</QBtn>
</template>
</VnSubToolbar>
<div class="route-list">
<VnPaginate
:key="refreshKey"
data-key="RouteList"
url="Routes/filter"
:order="['created ASC', 'started ASC', 'id ASC']"
:limit="20"
> >
<template #body="{ rows }"> <QTooltip>{{ t('Clone Selected Routes') }}</QTooltip>
<div class="q-pa-md route-table"> </QBtn>
<QTable <QBtn
v-model:selected="selectedRows" icon="cloud_download"
:columns="columns" color="primary"
:rows="rows" class="q-mr-sm"
flat :disable="!selectedRows?.length"
row-key="id" @click="showRouteReport"
selection="multiple" >
:rows-per-page-options="[0]" <QTooltip>{{ t('Download selected routes as PDF') }}</QTooltip>
:visible-columns="visibleColumns" </QBtn>
hide-pagination <QBtn
:no-data-label="t('globals.noResults')" icon="check"
style="max-height: 82vh" color="primary"
> class="q-mr-sm"
<template #body-cell-worker="{ row }"> :disable="!selectedRows?.length"
<QTd class="table-input-cell"> @click="markAsServed()"
<VnSelect >
:label="t('Worker')" <QTooltip>{{ t('Mark as served') }}</QTooltip>
v-model="row.workerFk" </QBtn>
:options="workers" </template>
option-value="id" </VnTable>
option-label="nickname"
hide-selected
dense
:emit-value="false"
:rules="validate('Route.workerFk')"
:is-clearable="false"
@update:model-value="updateRoute(row)"
>
<template #option="{ opt, itemProps }">
<QItem
v-bind="itemProps"
class="q-pa-xs row items-center"
>
<QItemSection
class="col-9 justify-center"
>
<span>{{ opt.name }}</span>
<span class="text-grey">{{
opt.nickname
}}</span>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QTd>
</template>
<template #body-cell-agency="{ row }">
<QTd class="table-input-cell">
<VnSelect
:label="t('Agency')"
v-model="row.agencyModeFk"
:options="agencyList"
option-value="id"
option-label="name"
hide-selected
dense
:emit-value="false"
:rules="validate('route.agencyFk')"
:is-clearable="false"
@update:model-value="updateRoute(row)"
/>
</QTd>
</template>
<template #body-cell-vehicle="{ row }">
<QTd class="table-input-cell small-column">
<VnSelect
:label="t('Vehicle')"
v-model="row.vehicleFk"
:options="vehicleList"
option-value="id"
option-label="numberPlate"
hide-selected
dense
:emit-value="false"
:rules="validate('route.vehicleFk')"
:is-clearable="false"
@update:model-value="updateRoute(row)"
/>
</QTd>
</template>
<template #body-cell-date="{ row }">
<QTd class="table-input-cell small-column">
<VnInputDate
v-model="row.created"
hide-bottom-space
dense
:label="t('Date')"
:rules="validate('route.created')"
:is-clearable="false"
@update:model-value="updateRoute(row)"
/>
</QTd>
</template>
<template #body-cell-description="{ row }">
<QTd class="table-input-cell">
<VnInput
v-model="row.description"
:label="t('Description')"
:rules="validate('route.description')"
:is-clearable="false"
dense
@update:model-value="updateRoute(row)"
/>
</QTd>
</template>
<template #body-cell-started="{ row }">
<QTd class="table-input-cell small-column">
<VnInputTime
v-model="row.started"
:label="t('hourStarted')"
:rules="validate('route.started')"
:is-clearable="false"
hide-bottom-space
dense
@update:model-value="updateRoute(row)"
/>
</QTd>
</template>
<template #body-cell-finished="{ row }">
<QTd class="table-input-cell small-column">
<VnInputTime
v-model="row.finished"
autofocus
:label="t('hourFinished')"
:rules="validate('route.finished')"
:is-clearable="false"
hide-bottom-space
dense
@update:model-value="updateRoute(row)"
/>
</QTd>
</template>
<template #body-cell-isServed="props">
<QTd class="table-input-cell small-column">
<QCheckbox v-model="props.value" disable>
<QTooltip>
{{
props.value
? t('Route is closed')
: t('Route is not served')
}}
</QTooltip>
</QCheckbox>
</QTd>
</template>
<template #body-cell-actions="props">
<QTd :props="props">
<div class="flex items-center no-wrap table-actions">
<QIcon
name="vn:ticketAdd"
size="sm"
color="primary"
class="cursor-pointer"
@click="openTicketsDialog(props?.row?.id)"
>
<QTooltip>{{ t('Add ticket') }}</QTooltip>
</QIcon>
<QIcon
name="preview"
size="sm"
color="primary"
@click="
viewSummary(props?.row?.id, RouteSummary)
"
class="cursor-pointer"
>
<QTooltip>{{ t('Preview') }}</QTooltip>
</QIcon>
<RouterLink
:to="{
name: 'RouteSummary',
params: { id: props?.row?.id },
}"
>
<QIcon
name="vn:eye"
size="xs"
color="primary"
>
<QTooltip>{{ t('Summary') }}</QTooltip>
</QIcon>
</RouterLink>
</div>
</QTd>
</template>
</QTable>
</div>
</template>
</VnPaginate>
</div>
<QPageSticky :offset="[20, 20]">
<RouterLink :to="{ name: 'RouteCreate' }">
<QBtn fab icon="add" color="primary" />
<QTooltip>
{{ t('newRoute') }}
</QTooltip>
</RouterLink>
</QPageSticky>
</QPage>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -2,7 +2,7 @@
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import LeftMenu from 'src/components/LeftMenu.vue'; import LeftMenu from 'src/components/LeftMenu.vue';
import { onMounted } from 'vue'; import { onMounted } from 'vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
const stateStore = useStateStore(); const stateStore = useStateStore();
onMounted(() => (stateStore.leftDrawer = false)); onMounted(() => (stateStore.leftDrawer = false));
</script> </script>

View File

@ -51,6 +51,7 @@ const setData = (entity) => (data.value = useCardDescription(entity.code, entity
:subtitle="data.subtitle" :subtitle="data.subtitle"
data-key="Shelvings" data-key="Shelvings"
@on-fetch="setData" @on-fetch="setData"
:summary="$attrs"
> >
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('shelving.summary.code')" :value="entity.code" /> <VnLv :label="t('shelving.summary.code')" :value="entity.code" />

View File

@ -1,5 +1,6 @@
<script setup> <script setup>
import ShelvingDescriptor from "pages/Shelving/Card/ShelvingDescriptor.vue"; import ShelvingDescriptor from 'pages/Shelving/Card/ShelvingDescriptor.vue';
import ShelvingSummary from './ShelvingSummary.vue';
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -10,6 +11,6 @@ const $props = defineProps({
</script> </script>
<template> <template>
<QPopupProxy> <QPopupProxy>
<ShelvingDescriptor v-if="$props.id" :id="$props.id" /> <ShelvingDescriptor v-if="$props.id" :id="$props.id" :summary="ShelvingSummary" />
</QPopupProxy> </QPopupProxy>
</template> </template>

View File

@ -51,7 +51,7 @@ const filter = {
class="header header-link" class="header header-link"
:to="{ name: 'ShelvingBasicData', params: { id: entityId } }" :to="{ name: 'ShelvingBasicData', params: { id: entityId } }"
> >
{{ t('shelving.pageTitles.basicData') }} {{ t('globals.pageTitles.basicData') }}
<QIcon name="open_in_new" /> <QIcon name="open_in_new" />
</RouterLink> </RouterLink>
<VnLv :label="t('shelving.summary.code')" :value="entity.code" /> <VnLv :label="t('shelving.summary.code')" :value="entity.code" />

View File

@ -112,22 +112,6 @@ const getEntryQueryParams = (supplier) => {
data-key="supplier" data-key="supplier"
:summary="$props.summary" :summary="$props.summary"
> >
<template #header-extra-action>
<QBtn
round
flat
dense
size="md"
icon="vn:supplier"
color="white"
class="link"
:to="{ name: 'SupplierList' }"
>
<QTooltip>
{{ t('Go to module index') }}
</QTooltip>
</QBtn>
</template>
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('supplier.summary.taxNumber')" :value="entity.nif" /> <VnLv :label="t('supplier.summary.taxNumber')" :value="entity.nif" />
<VnLv label="Alias" :value="entity.nickname" /> <VnLv label="Alias" :value="entity.nickname" />

View File

@ -9,8 +9,6 @@ import { dashIfEmpty } from 'src/filters';
import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue';
import VnTitle from 'src/components/common/VnTitle.vue'; import VnTitle from 'src/components/common/VnTitle.vue';
onUpdated(() => summaryRef.value.fetch());
const route = useRoute(); const route = useRoute();
const roleState = useRole(); const roleState = useRole();
const { t } = useI18n(); const { t } = useI18n();

View File

@ -1,26 +1,81 @@
<script setup> <script setup>
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import VnTable from 'components/VnTable/VnTable.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue';
import VnPaginate from 'src/components/ui/VnPaginate.vue'; import RightMenu from 'components/common/RightMenu.vue';
import CardList from 'src/components/ui/CardList.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import SupplierSummary from './Card/SupplierSummary.vue';
import SupplierListFilter from './SupplierListFilter.vue'; import SupplierListFilter from './SupplierListFilter.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import RightMenu from 'src/components/common/RightMenu.vue';
const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const { viewSummary } = useSummaryDialog(); const tableRef = ref();
function navigate(id) { const columns = computed(() => [
router.push({ path: `/supplier/${id}` }); {
} align: 'left',
label: t('supplier.list.tableVisibleColumns.id'),
const redirectToCreateView = () => { name: 'id',
router.push({ name: 'SupplierCreate' }); isTitle: true,
}; },
{
align: 'left',
label: t('supplier.list.tableVisibleColumns.name'),
name: 'socialName',
create: true,
component: 'input',
columnField: {
component: null,
},
},
{
align: 'left',
label: t('supplier.list.tableVisibleColumns.nif'),
name: 'nif',
component: 'input',
columnField: {
component: null,
},
},
{
align: 'left',
label: t('supplier.list.tableVisibleColumns.nickname'),
name: 'alias',
component: 'input',
columnField: {
component: null,
},
},
{
align: 'left',
label: t('supplier.list.tableVisibleColumns.account'),
name: 'account',
component: 'input',
columnField: {
component: null,
},
},
{
align: 'left',
label: t('supplier.list.tableVisibleColumns.payMethod'),
name: 'payMethod',
component: 'select',
attrs: {
url: 'payMethods',
fields: ['id', 'name'],
},
columnField: {
component: null,
},
},
{
align: 'left',
label: t('supplier.list.tableVisibleColumns.payDay'),
name: 'payDat',
component: 'input',
columnField: {
component: null,
},
},
]);
</script> </script>
<template> <template>
@ -30,56 +85,25 @@ const redirectToCreateView = () => {
<SupplierListFilter data-key="SuppliersList" /> <SupplierListFilter data-key="SuppliersList" />
</template> </template>
</RightMenu> </RightMenu>
<QPage class="column items-center q-pa-md"> <VnTable
<div class="vn-card-list"> ref="tableRef"
<VnPaginate data-key="SuppliersList" url="Suppliers/filter"> data-key="SuppliersList"
<template #body="{ rows }"> url="Suppliers/filter"
<CardList save-url="Suppliers/crud"
v-for="row of rows" redirect="supplier"
:key="row.id" :create="{
:title="row.socialName" urlCreate: 'Suppliers/newSupplier',
:id="row.id" title: t('Create Supplier'),
@click="navigate(row.id)" onDataSaved: ({ id }) => tableRef.redirect(id),
> formInitialData: {},
<template #list-items> }"
<VnLv label="NIF/CIF" :value="row.nif" /> order="id ASC"
<VnLv label="Alias" :value="row.alias" /> :columns="columns"
<VnLv default-mode="table"
:label="t('supplier.list.payMethod')" auto-load
:value="row.payMethod" :right-search="false"
/> :use-model="true"
<VnLv />
:label="t('supplier.list.payDeadline')"
:title-label="t('invoiceOut.list.created')"
:value="row.payDem"
/>
<VnLv
:label="t('supplier.list.payDay')"
:value="row.payDay"
/>
<VnLv
:label="t('supplier.list.account')"
:value="row.account"
/>
</template>
<template #actions>
<QBtn
:label="t('components.smartCard.openSummary')"
@click.stop="viewSummary(row.id, SupplierSummary)"
color="primary"
/>
</template>
</CardList>
</template>
</VnPaginate>
</div>
<QPageSticky :offset="[20, 20]">
<QBtn fab icon="add" color="primary" @click="redirectToCreateView()" />
<QTooltip>
{{ t('supplier.list.newSupplier') }}
</QTooltip>
</QPageSticky>
</QPage>
</template> </template>
<i18n> <i18n>

View File

@ -0,0 +1,49 @@
<script setup>
import { reactive, ref, onMounted, nextTick } from 'vue';
import { useI18n } from 'vue-i18n';
import VnInput from 'src/components/common/VnInput.vue';
import VnRow from 'components/ui/VnRow.vue';
import FormModelPopup from 'components/FormModelPopup.vue';
const { t } = useI18n();
const emit = defineEmits(['onDataSaved']);
const nameInputRef = ref(null);
const serviceFormData = reactive({});
const onDataSaved = (formData, requestResponse) => {
emit('onDataSaved', formData, requestResponse);
};
onMounted(async () => {
await nextTick();
nameInputRef.value.focus();
});
</script>
<template>
<FormModelPopup
url-create="TicketServiceTypes"
model="TicketServiceType"
:title="t('New service type')"
:form-initial-data="serviceFormData"
@on-data-saved="onDataSaved"
>
<template #form-inputs="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<VnInput
ref="nameInputRef"
:label="t('service.description')"
v-model="data.name"
:required="true"
/>
</VnRow>
</template>
</FormModelPopup>
</template>
<i18n>
es:
New service type: Nuevo tipo de servicio
</i18n>

View File

@ -0,0 +1,86 @@
<script setup>
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import FormModelPopup from 'components/FormModelPopup.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue';
import { useState } from 'src/composables/useState';
const emit = defineEmits(['onRequestCreated']);
const route = useRoute();
const { t } = useI18n();
const state = useState();
const user = state.getUser();
const stateFetchDataRef = ref(null);
const statesOptions = ref([]);
const workersOptions = ref([]);
const onStateFkChange = (formData) => (formData.userFk = user.value.id);
</script>
<template>
<FetchData
ref="stateFetchDataRef"
url="States"
auto-load
@on-fetch="(data) => (statesOptions = data)"
/>
<FetchData
url="Workers/search"
:filter="{ fields: ['id', 'name'], order: 'name ASC' }"
auto-load
@on-fetch="(data) => (workersOptions = data)"
/>
<FormModelPopup
:title="t('Create tracking')"
url-create="Tickets/state"
model="CreateTicketTracking"
:form-initial-data="{ ticketFk: route.params.id }"
@on-data-saved="() => emit('onRequestCreated')"
>
<template #form-inputs="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<VnSelect
v-model="data.stateFk"
:label="t('tracking.state')"
:options="statesOptions"
@update:model-value="onStateFkChange(data)"
hide-selected
option-label="name"
option-value="id"
/>
<VnSelect
:label="t('tracking.worker')"
v-model="data.userFk"
:options="workersOptions"
hide-selected
option-label="name"
option-value="id"
>
<template #option="{ opt, itemProps }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel>
{{ opt.name }}
</QItemLabel>
<QItemLabel caption>
{{ opt.nickname }}, {{ opt.code }}
</QItemLabel>
</QItemSection>
</QItem>
</template></VnSelect
>
</VnRow>
</template>
</FormModelPopup>
</template>
<i18n>
es:
Create tracking: Crear estado
</i18n>

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