refactor/issue-1/first-setup #9
|
|
@ -1 +0,0 @@
|
||||||
VITE_API_BASE_URL=
|
|
||||||
|
|
@ -509,6 +509,7 @@ declare module 'vue' {
|
||||||
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
|
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
|
||||||
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
|
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
|
||||||
readonly urlValidator: UnwrapRef<typeof import('./src/@core/utils/validators')['urlValidator']>
|
readonly urlValidator: UnwrapRef<typeof import('./src/@core/utils/validators')['urlValidator']>
|
||||||
|
readonly useAbility: UnwrapRef<typeof import('./src/plugins/casl/composables/useAbility')['useAbility']>
|
||||||
readonly useAbs: UnwrapRef<typeof import('@vueuse/math')['useAbs']>
|
readonly useAbs: UnwrapRef<typeof import('@vueuse/math')['useAbs']>
|
||||||
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
|
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
|
||||||
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
|
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
|
||||||
|
|
@ -852,6 +853,7 @@ declare module '@vue/runtime-core' {
|
||||||
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
|
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
|
||||||
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
|
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
|
||||||
readonly urlValidator: UnwrapRef<typeof import('./src/@core/utils/validators')['urlValidator']>
|
readonly urlValidator: UnwrapRef<typeof import('./src/@core/utils/validators')['urlValidator']>
|
||||||
|
readonly useAbility: UnwrapRef<typeof import('./src/plugins/casl/composables/useAbility')['useAbility']>
|
||||||
readonly useAbs: UnwrapRef<typeof import('@vueuse/math')['useAbs']>
|
readonly useAbs: UnwrapRef<typeof import('@vueuse/math')['useAbs']>
|
||||||
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
|
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
|
||||||
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
|
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vuexy - Vuejs Admin Dashboard Template</title>
|
<title>HelpDesk Web POS</title>
|
||||||
<link rel="stylesheet" type="text/css" href="/loader.css" />
|
<link rel="stylesheet" type="text/css" href="/loader.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@
|
||||||
"apexcharts-clevision": "^3.28.5",
|
"apexcharts-clevision": "^3.28.5",
|
||||||
"chart.js": "^4.4.0",
|
"chart.js": "^4.4.0",
|
||||||
"cookie-es": "^1.0.0",
|
"cookie-es": "^1.0.0",
|
||||||
|
"date-fns": "^3.0.6",
|
||||||
|
"export-from-json": "^1.7.4",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"mapbox-gl": "2.15.0",
|
"mapbox-gl": "2.15.0",
|
||||||
"ofetch": "^1.3.3",
|
"ofetch": "^1.3.3",
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,12 @@ dependencies:
|
||||||
cookie-es:
|
cookie-es:
|
||||||
specifier: ^1.0.0
|
specifier: ^1.0.0
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
date-fns:
|
||||||
|
specifier: ^3.0.6
|
||||||
|
version: 3.0.6
|
||||||
|
export-from-json:
|
||||||
|
specifier: ^1.7.4
|
||||||
|
version: 1.7.4
|
||||||
jwt-decode:
|
jwt-decode:
|
||||||
specifier: ^3.1.2
|
specifier: ^3.1.2
|
||||||
version: 3.1.2
|
version: 3.1.2
|
||||||
|
|
@ -3198,6 +3204,10 @@ packages:
|
||||||
/dash-get@1.0.2:
|
/dash-get@1.0.2:
|
||||||
resolution: {integrity: sha512-4FbVrHDwfOASx7uQVxeiCTo7ggSdYZbqs8lH+WU6ViypPlDbe9y6IP5VVUDQBv9DcnyaiPT5XT0UWHgJ64zLeQ==}
|
resolution: {integrity: sha512-4FbVrHDwfOASx7uQVxeiCTo7ggSdYZbqs8lH+WU6ViypPlDbe9y6IP5VVUDQBv9DcnyaiPT5XT0UWHgJ64zLeQ==}
|
||||||
|
|
||||||
|
/date-fns@3.0.6:
|
||||||
|
resolution: {integrity: sha512-W+G99rycpKMMF2/YD064b2lE7jJGUe+EjOES7Q8BIGY8sbNdbgcs9XFTZwvzc9Jx1f3k7LB7gZaZa7f8Agzljg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/de-indent@1.0.2:
|
/de-indent@1.0.2:
|
||||||
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
|
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
@ -4109,6 +4119,10 @@ packages:
|
||||||
strip-final-newline: 3.0.0
|
strip-final-newline: 3.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/export-from-json@1.7.4:
|
||||||
|
resolution: {integrity: sha512-FjmpluvZS2PTYyhkoMfQoyEJMfe2bfAyNpa5Apa6C9n7SWUWyJkG/VFnzERuj3q9Jjo3iwBjwVsDQ7Z7sczthA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/external-editor@3.1.0:
|
/external-editor@3.1.0:
|
||||||
resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
|
resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
|
import flatpickr from 'flatpickr'
|
||||||
|
import { French } from 'flatpickr/dist/l10n/fr'
|
||||||
import { useTheme } from 'vuetify'
|
import { useTheme } from 'vuetify'
|
||||||
import { useConfigStore } from '@core/stores/config'
|
|
||||||
import { cookieRef, namespaceConfig } from '@layouts/stores/config'
|
|
||||||
import { themeConfig } from '@themeConfig'
|
import { themeConfig } from '@themeConfig'
|
||||||
|
import { cookieRef, namespaceConfig } from '@layouts/stores/config'
|
||||||
|
import { useConfigStore } from '@core/stores/config'
|
||||||
|
|
||||||
const _syncAppRtl = () => {
|
const _syncAppRtl = () => {
|
||||||
const configStore = useConfigStore()
|
const configStore = useConfigStore()
|
||||||
|
|
@ -14,6 +16,15 @@ const _syncAppRtl = () => {
|
||||||
if (locale.value !== storedLang.value && storedLang.value)
|
if (locale.value !== storedLang.value && storedLang.value)
|
||||||
locale.value = storedLang.value
|
locale.value = storedLang.value
|
||||||
|
|
||||||
|
if (locale.value === 'fr') {
|
||||||
|
if (flatpickr.l10ns.fr)
|
||||||
|
flatpickr.localize(French)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (flatpickr.l10ns.en)
|
||||||
|
flatpickr.localize(flatpickr.l10ns.en)
|
||||||
|
}
|
||||||
|
|
||||||
// watch and change lang attribute of html on language change
|
// watch and change lang attribute of html on language change
|
||||||
watch(
|
watch(
|
||||||
locale,
|
locale,
|
||||||
|
|
@ -25,6 +36,15 @@ const _syncAppRtl = () => {
|
||||||
// Store selected language in cookie
|
// Store selected language in cookie
|
||||||
storedLang.value = val as string
|
storedLang.value = val as string
|
||||||
|
|
||||||
|
if (storedLang.value === 'fr') {
|
||||||
|
if (flatpickr.l10ns.fr)
|
||||||
|
flatpickr.localize(flatpickr.l10ns.fr)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (flatpickr.l10ns.en)
|
||||||
|
flatpickr.localize(flatpickr.l10ns.en)
|
||||||
|
}
|
||||||
|
|
||||||
// set isAppRtl value based on selected language
|
// set isAppRtl value based on selected language
|
||||||
if (themeConfig.app.i18n.langConfig && themeConfig.app.i18n.langConfig.length) {
|
if (themeConfig.app.i18n.langConfig && themeConfig.app.i18n.langConfig.length) {
|
||||||
themeConfig.app.i18n.langConfig.forEach(lang => {
|
themeConfig.app.i18n.langConfig.forEach(lang => {
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
|
@ -1 +1,4 @@
|
||||||
// Write your overrides
|
// Write your overrides
|
||||||
|
.dt-row-striped tr:nth-of-type(even) {
|
||||||
|
background-color: #f6f2f28d;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { createFetch } from '@vueuse/core'
|
||||||
import { destr } from 'destr'
|
import { destr } from 'destr'
|
||||||
|
|
||||||
export const useApi = createFetch({
|
export const useApi = createFetch({
|
||||||
baseUrl: import.meta.env.VITE_API_BASE_URL || '/api',
|
baseUrl: import.meta.env.VITE_API_BASE_URL,
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -12,26 +12,11 @@
|
||||||
class="mx-1"
|
class="mx-1"
|
||||||
/>
|
/>
|
||||||
By <a
|
By <a
|
||||||
href="https://pixinvent.com"
|
href="https://inetum.com"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="text-primary ms-1"
|
class="text-primary ms-1"
|
||||||
>Pixinvent</a>
|
>Inetum</a>
|
||||||
</span>
|
|
||||||
<!-- 👉 Footer: right content -->
|
|
||||||
<span class="d-md-flex gap-x-4 text-primary d-none">
|
|
||||||
<a
|
|
||||||
href="https://themeforest.net/licenses/standard"
|
|
||||||
target="noopener noreferrer"
|
|
||||||
>License</a>
|
|
||||||
<a
|
|
||||||
href="https://1.envato.market/pixinvent_portfolio"
|
|
||||||
target="noopener noreferrer"
|
|
||||||
>More Themes</a>
|
|
||||||
<a
|
|
||||||
href="https://demos.pixinvent.com/vuexy-vuejs-admin-template/documentation/"
|
|
||||||
target="noopener noreferrer"
|
|
||||||
>Documentation</a>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,24 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import avatar1 from '@images/avatars/avatar-1.png'
|
import { userStore } from '@stores/user.store'
|
||||||
|
|
||||||
|
const useUserStore = userStore()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const logoutAndRedirect = () => {
|
||||||
|
useUserStore.logout()
|
||||||
|
router.push('/login')
|
||||||
|
}
|
||||||
|
|
||||||
|
const avatarColor = computed(() => {
|
||||||
|
switch (useUserStore.role) {
|
||||||
|
// eslint-disable-next-line @stylistic/ts/indent
|
||||||
|
case 'Admin': return 'error'
|
||||||
|
// eslint-disable-next-line @stylistic/ts/indent
|
||||||
|
case 'Support': return 'secondary'
|
||||||
|
// eslint-disable-next-line @stylistic/ts/indent
|
||||||
|
default: return 'info'
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -13,10 +32,18 @@ import avatar1 from '@images/avatars/avatar-1.png'
|
||||||
>
|
>
|
||||||
<VAvatar
|
<VAvatar
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
color="primary"
|
:color="avatarColor"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
>
|
>
|
||||||
<VImg :src="avatar1" />
|
<template v-if="useUserStore.role === 'Admin'">
|
||||||
|
AD
|
||||||
|
</template>
|
||||||
|
<template v-else-if="useUserStore.role === 'Support'">
|
||||||
|
SU
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
IN
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- SECTION Menu -->
|
<!-- SECTION Menu -->
|
||||||
<VMenu
|
<VMenu
|
||||||
|
|
@ -38,80 +65,33 @@ import avatar1 from '@images/avatars/avatar-1.png'
|
||||||
color="success"
|
color="success"
|
||||||
>
|
>
|
||||||
<VAvatar
|
<VAvatar
|
||||||
color="primary"
|
:color="avatarColor"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
>
|
>
|
||||||
<VImg :src="avatar1" />
|
<template v-if="useUserStore.role === 'Admin'">
|
||||||
|
AD
|
||||||
|
</template>
|
||||||
|
<template v-else-if="useUserStore.role === 'Support'">
|
||||||
|
SU
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
IN
|
||||||
|
</template>
|
||||||
</VAvatar>
|
</VAvatar>
|
||||||
</VBadge>
|
</VBadge>
|
||||||
</VListItemAction>
|
</VListItemAction>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<VListItemTitle class="font-weight-semibold">
|
<VListItemTitle class="font-weight-semibold">
|
||||||
John Doe
|
{{ useUserStore.username }}
|
||||||
</VListItemTitle>
|
</VListItemTitle>
|
||||||
<VListItemSubtitle>Admin</VListItemSubtitle>
|
<VListItemSubtitle>{{ useUserStore.role }}</VListItemSubtitle>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
|
|
||||||
<VDivider class="my-2" />
|
<VDivider class="my-2" />
|
||||||
|
|
||||||
<!-- 👉 Profile -->
|
|
||||||
<VListItem link>
|
|
||||||
<template #prepend>
|
|
||||||
<VIcon
|
|
||||||
class="me-2"
|
|
||||||
icon="tabler-user"
|
|
||||||
size="22"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<VListItemTitle>Profile</VListItemTitle>
|
|
||||||
</VListItem>
|
|
||||||
|
|
||||||
<!-- 👉 Settings -->
|
|
||||||
<VListItem link>
|
|
||||||
<template #prepend>
|
|
||||||
<VIcon
|
|
||||||
class="me-2"
|
|
||||||
icon="tabler-settings"
|
|
||||||
size="22"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<VListItemTitle>Settings</VListItemTitle>
|
|
||||||
</VListItem>
|
|
||||||
|
|
||||||
<!-- 👉 Pricing -->
|
|
||||||
<VListItem link>
|
|
||||||
<template #prepend>
|
|
||||||
<VIcon
|
|
||||||
class="me-2"
|
|
||||||
icon="tabler-currency-dollar"
|
|
||||||
size="22"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<VListItemTitle>Pricing</VListItemTitle>
|
|
||||||
</VListItem>
|
|
||||||
|
|
||||||
<!-- 👉 FAQ -->
|
|
||||||
<VListItem link>
|
|
||||||
<template #prepend>
|
|
||||||
<VIcon
|
|
||||||
class="me-2"
|
|
||||||
icon="tabler-help"
|
|
||||||
size="22"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<VListItemTitle>FAQ</VListItemTitle>
|
|
||||||
</VListItem>
|
|
||||||
|
|
||||||
<!-- Divider -->
|
|
||||||
<VDivider class="my-2" />
|
|
||||||
|
|
||||||
<!-- 👉 Logout -->
|
<!-- 👉 Logout -->
|
||||||
<VListItem to="/login">
|
<VListItem @click="logoutAndRedirect">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<VIcon
|
<VIcon
|
||||||
class="me-2"
|
class="me-2"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
interface Store {
|
||||||
|
id_structure: number
|
||||||
|
nom: string
|
||||||
|
ip_master: string
|
||||||
|
telephone: string
|
||||||
|
photoLink: string
|
||||||
|
enseigne: string
|
||||||
|
nbcaisses: number
|
||||||
|
pays: string
|
||||||
|
adresse: string
|
||||||
|
caisses: Caisse[]
|
||||||
|
date_migration: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Caisse {
|
||||||
|
id_caisse: number
|
||||||
|
ip: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface storePos {
|
||||||
|
workstationId: number
|
||||||
|
ip: string
|
||||||
|
version: string
|
||||||
|
businessDate: Date
|
||||||
|
businessDateS: string
|
||||||
|
openingDate: string
|
||||||
|
closingDate: string
|
||||||
|
boTransaction: Transaction
|
||||||
|
replication: Replication
|
||||||
|
saleTransaction: xStoreTransaction
|
||||||
|
primaryRegister: boolean
|
||||||
|
fatalError: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Replication {
|
||||||
|
pendingReplications: number
|
||||||
|
minPendingReplicationDate: string
|
||||||
|
maxPendingReplicationDate: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Transaction {
|
||||||
|
backOfficeTransactions: number
|
||||||
|
minBackOfficeTransactionDate: string
|
||||||
|
maxBackOfficeTransactionDate: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface xStoreTransaction {
|
||||||
|
count: number
|
||||||
|
minDate: string
|
||||||
|
minDateT: string
|
||||||
|
minDateH: string
|
||||||
|
maxDate: string
|
||||||
|
maxDateT: string
|
||||||
|
maxDateH: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StoreData {
|
||||||
|
store: Store
|
||||||
|
pos: storePos[]
|
||||||
|
}
|
||||||
|
|
@ -5,8 +5,38 @@ export default [
|
||||||
icon: { icon: 'tabler-smart-home' },
|
icon: { icon: 'tabler-smart-home' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Second page',
|
title: 'Store',
|
||||||
to: { name: 'second-page' },
|
to: { name: 'store-list' },
|
||||||
icon: { icon: 'tabler-file' },
|
icon: { icon: 'tabler-building-store' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Flow',
|
||||||
|
icon: { icon: 'tabler-topology-bus' },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: 'BL not sent list',
|
||||||
|
to: { name: 'flux-bl-not-sent' },
|
||||||
|
icon: { icon: 'tabler-unlink' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'OBI',
|
||||||
|
icon: { icon: 'tabler-shopping-bag-check' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Dotsoft',
|
||||||
|
icon: { icon: 'tabler-database-star' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'XADMIN',
|
||||||
|
icon: { icon: 'tabler-database-star' },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: 'Logs',
|
||||||
|
to: { name: 'xadmin-log' },
|
||||||
|
icon: { icon: 'tabler-bug' },
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,38 @@ export default [
|
||||||
icon: { icon: 'tabler-smart-home' },
|
icon: { icon: 'tabler-smart-home' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Second page',
|
title: 'Store',
|
||||||
to: { name: 'second-page' },
|
to: { name: 'store-list' },
|
||||||
icon: { icon: 'tabler-file' },
|
icon: { icon: 'tabler-building-store' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Flow',
|
||||||
|
icon: { icon: 'tabler-topology-bus' },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: 'BL not sent list',
|
||||||
|
to: { name: 'flux-bl-not-sent' },
|
||||||
|
icon: { icon: 'tabler-unlink' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'OBI',
|
||||||
|
icon: { icon: 'tabler-shopping-bag-check' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Dotsoft',
|
||||||
|
icon: { icon: 'tabler-database-star' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'XADMIN',
|
||||||
|
icon: { icon: 'tabler-database-star' },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: 'Logs',
|
||||||
|
to: { name: 'xadmin-log' },
|
||||||
|
icon: { icon: 'tabler-bug' },
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,310 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import exportFromJSON from 'export-from-json'
|
||||||
|
import { VDataTable } from 'vuetify/labs/VDataTable'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const headers = computed(() => [
|
||||||
|
{ title: t('Distributor'), key: 'libelleDis' },
|
||||||
|
{ title: t('Name'), key: 'nomStructure' },
|
||||||
|
{ title: t('Chain'), key: 'enseigne' },
|
||||||
|
{ title: t('Brand'), key: 'marque' },
|
||||||
|
{ title: t('Season'), key: 'codeSaison' },
|
||||||
|
{ title: t('R-C-S'), key: 'refRct' },
|
||||||
|
{ title: t('Item'), key: 'nomProduit' },
|
||||||
|
{ title: t('Item ID'), key: 'idProduit' },
|
||||||
|
{ title: t('External Code'), key: 'codeExterne' },
|
||||||
|
{ title: t('BL ID'), key: 'idBonLivraison' },
|
||||||
|
{ title: t('BL Date'), key: 'dateBl' },
|
||||||
|
{ title: t('Expeditor'), key: 'expediteur' },
|
||||||
|
{ title: t('Notes'), key: 'remarques' },
|
||||||
|
])
|
||||||
|
|
||||||
|
const selectedDistributor = ref()
|
||||||
|
const selectedStore = ref()
|
||||||
|
const selectedRefr = ref()
|
||||||
|
|
||||||
|
const searchQuery = ref<any>('')
|
||||||
|
|
||||||
|
// Data table options
|
||||||
|
const { data: dtListData } = await useApi<any>(createUrl('/flux/bl/notsent'))
|
||||||
|
|
||||||
|
const options = ref({ page: 1, itemsPerPage: 10, sortBy: [''], sortDesc: [false] })
|
||||||
|
|
||||||
|
const distributor = computed(() => {
|
||||||
|
const allItems = dtListData.value.map(({ libelleDis }: { libelleDis: any }) => libelleDis)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
const uniqueItems = allItems.filter((distributor: any, index: any, self: string | any[]) => self.indexOf(distributor) === index)
|
||||||
|
const sortedItems = uniqueItems.sort()
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
return sortedItems.map((distributor: any) => ({ title: distributor, value: distributor }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const store = computed(() => {
|
||||||
|
const allItems = dtListData.value.map(({ nomStructure, idStructure }: { nomStructure: any; idStructure: any }) => ({ nomStructure, idStructure }))
|
||||||
|
|
||||||
|
const uniqueItems = allItems.filter((item: any, index: any, self: any[]) =>
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
index === self.findIndex(t => (
|
||||||
|
t.nomStructure === item.nomStructure && t.idStructure === item.idStructure
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
|
const sortedItems = uniqueItems.sort((a: { idStructure: number }, b: { idStructure: number }) => a.idStructure - b.idStructure)
|
||||||
|
|
||||||
|
return sortedItems.map(({ nomStructure }: { nomStructure: any }) => ({ title: nomStructure, value: nomStructure }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const filterRefr = computed(() => {
|
||||||
|
const allItems = dtListData.value.map(({ refR }: { refR: any }) => refR)
|
||||||
|
|
||||||
|
const uniqueItems = allItems.filter((refR: any, index: any, self: string | any[]) => self.indexOf(refR) === index)
|
||||||
|
const sortedItems = uniqueItems.sort()
|
||||||
|
|
||||||
|
return sortedItems.map((refR: any) => ({ title: refR, value: refR }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const filteredData = computed(() => {
|
||||||
|
let filtered = dtListData.value
|
||||||
|
|
||||||
|
// If a distributor is selected, filter the records for this distributor
|
||||||
|
if (selectedDistributor.value)
|
||||||
|
filtered = filtered.filter(({ libelleDis }: { libelleDis: any }) => libelleDis === selectedDistributor.value)
|
||||||
|
|
||||||
|
// If a store is selected, filter the records for this store
|
||||||
|
if (selectedStore.value)
|
||||||
|
filtered = filtered.filter(({ nomStructure }: { nomStructure: any }) => nomStructure === selectedStore.value)
|
||||||
|
|
||||||
|
// If a ref R is selected, filter the records for this ref R
|
||||||
|
if (selectedRefr.value)
|
||||||
|
filtered = filtered.filter(({ refR }: { refR: any }) => refR === selectedRefr.value)
|
||||||
|
|
||||||
|
// If a search query is provided, filter the records for this query
|
||||||
|
if (searchQuery.value) {
|
||||||
|
filtered = filtered.filter((dataFiltered: { [s: string]: unknown } | ArrayLike<unknown>) =>
|
||||||
|
Object.values(dataFiltered).some(value =>
|
||||||
|
String(value).toLowerCase().includes(searchQuery.value.toLowerCase()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
})
|
||||||
|
|
||||||
|
const widgetData = computed(() => [
|
||||||
|
{ title: t('Distributors'), value: distributor.value.length, icon: 'tabler-brand-campaignmonitor' },
|
||||||
|
{ title: t('Stores'), value: store.value.length, icon: 'tabler-building-store' },
|
||||||
|
{ title: t('Items'), value: filterRefr.value.length, icon: 'tabler-shirt-sport' },
|
||||||
|
{ title: t('Errors'), value: dtListData.value.length, icon: 'tabler-exclamation-circle' },
|
||||||
|
])
|
||||||
|
|
||||||
|
const ExcelField = [
|
||||||
|
'idDistrib',
|
||||||
|
'libelleDis',
|
||||||
|
'idStructure',
|
||||||
|
'nomStructure',
|
||||||
|
'enseigne',
|
||||||
|
'marque',
|
||||||
|
'codeSaison',
|
||||||
|
'refR',
|
||||||
|
'refRc',
|
||||||
|
'refRct',
|
||||||
|
'nomProduit',
|
||||||
|
'idProduit',
|
||||||
|
'codeExterne',
|
||||||
|
'idBonLivraison',
|
||||||
|
'dateBl',
|
||||||
|
'idExpediteur',
|
||||||
|
'expediteur',
|
||||||
|
'remarques',
|
||||||
|
]
|
||||||
|
|
||||||
|
const ExcelData = dtListData.value.map((item: { [x: string]: string }) => {
|
||||||
|
const orderedItem: { [key: string]: string } = {}
|
||||||
|
|
||||||
|
ExcelField.forEach(field => {
|
||||||
|
orderedItem[field] = item[field]
|
||||||
|
})
|
||||||
|
|
||||||
|
return orderedItem
|
||||||
|
})
|
||||||
|
|
||||||
|
const exportEXCEL = () => {
|
||||||
|
const date = new Date()
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
|
||||||
|
const fileName = `BLBLOQUES_${year}${month}${day}`
|
||||||
|
|
||||||
|
exportFromJSON({
|
||||||
|
data: ExcelData,
|
||||||
|
fileName,
|
||||||
|
exportType: exportFromJSON.types.xls,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VBreadcrumbs
|
||||||
|
class="px-0 py-2"
|
||||||
|
:items="[
|
||||||
|
{ title: t('Home'), to: { name: 'root' } },
|
||||||
|
{ title: t('BL not sent list') },
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<!-- 👉 Statistics Widgets -->
|
||||||
|
<VCard class="mb-6">
|
||||||
|
<VCardText>
|
||||||
|
<VRow>
|
||||||
|
<template
|
||||||
|
v-for="(data, id) in widgetData"
|
||||||
|
:key="id"
|
||||||
|
>
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="6"
|
||||||
|
md="3"
|
||||||
|
class="px-6"
|
||||||
|
>
|
||||||
|
<div class="d-flex justify-space-between">
|
||||||
|
<div class="d-flex flex-column gap-y-1">
|
||||||
|
<h4
|
||||||
|
class="text-h4 text-high-emphasis"
|
||||||
|
:class="{ 'text-error': id === 3 }"
|
||||||
|
>
|
||||||
|
{{ data.value }}
|
||||||
|
</h4>
|
||||||
|
<div class="text-body-1 text-capitalize">
|
||||||
|
{{ data.title }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<VAvatar
|
||||||
|
color="rgba(var(--v-theme-on-background), var(--v-hover-opacity))"
|
||||||
|
rounded
|
||||||
|
class="text-high-emphasis"
|
||||||
|
size="38"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
:icon="data.icon"
|
||||||
|
size="26"
|
||||||
|
/>
|
||||||
|
</VAvatar>
|
||||||
|
</div>
|
||||||
|
</VCol>
|
||||||
|
<VDivider
|
||||||
|
v-if="$vuetify.display.mdAndUp ? id !== widgetData.length - 1
|
||||||
|
: $vuetify.display.smAndUp ? id % 2 === 0
|
||||||
|
: false"
|
||||||
|
vertical
|
||||||
|
inset
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</VRow>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
|
||||||
|
<VCard
|
||||||
|
:title="$t('Filters')"
|
||||||
|
class="mb-6"
|
||||||
|
>
|
||||||
|
<VCardText>
|
||||||
|
<VRow>
|
||||||
|
<!-- 👉 Select distributor -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="4"
|
||||||
|
>
|
||||||
|
<AppSelect
|
||||||
|
v-model="selectedDistributor"
|
||||||
|
:placeholder="$t('Distributor')"
|
||||||
|
:items="distributor"
|
||||||
|
clearable
|
||||||
|
clear-icon="tabler-x"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<!-- 👉 Select store -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="4"
|
||||||
|
>
|
||||||
|
<AppSelect
|
||||||
|
v-model="selectedStore"
|
||||||
|
:placeholder="$t('Store')"
|
||||||
|
:items="store"
|
||||||
|
clearable
|
||||||
|
clear-icon="tabler-x"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<!-- 👉 Select Reference R -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="4"
|
||||||
|
>
|
||||||
|
<AppSelect
|
||||||
|
v-model="selectedRefr"
|
||||||
|
:placeholder="$t('Item')"
|
||||||
|
:items="filterRefr"
|
||||||
|
clearable
|
||||||
|
clear-icon="tabler-x"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
|
||||||
|
<!-- 👉 BL not sent -->
|
||||||
|
<VCard
|
||||||
|
:title="$t('BL not sent list')"
|
||||||
|
class="mb-6"
|
||||||
|
>
|
||||||
|
<div class="d-flex flex-wrap gap-4 mx-5">
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<!-- 👉 Search -->
|
||||||
|
<AppTextField
|
||||||
|
v-model="searchQuery"
|
||||||
|
:placeholder="$t('Search')"
|
||||||
|
density="compact"
|
||||||
|
style="inline-size: 200px;"
|
||||||
|
class="me-3"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<VSpacer />
|
||||||
|
|
||||||
|
<div class="d-flex gap-4 flex-wrap align-center">
|
||||||
|
<!-- 👉 Export button -->
|
||||||
|
<VBtn
|
||||||
|
variant="tonal"
|
||||||
|
color="primary"
|
||||||
|
prepend-icon="tabler-upload"
|
||||||
|
@click="exportEXCEL"
|
||||||
|
>
|
||||||
|
Export
|
||||||
|
</VBtn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<VDivider class="mt-4" />
|
||||||
|
|
||||||
|
<VCol cols="12">
|
||||||
|
<!-- 👉 Datatable -->
|
||||||
|
<VDataTable
|
||||||
|
class="dt-row-striped"
|
||||||
|
:headers="headers"
|
||||||
|
:items="filteredData"
|
||||||
|
:items-per-page="options.itemsPerPage"
|
||||||
|
:page="options.page"
|
||||||
|
:options="options"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
</Vcard>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -2,24 +2,11 @@
|
||||||
<div>
|
<div>
|
||||||
<VCard
|
<VCard
|
||||||
class="mb-6"
|
class="mb-6"
|
||||||
title="Kick start your project 🚀"
|
title="XSTORE HELPDESK DASHBOARD 🚀"
|
||||||
>
|
>
|
||||||
<VCardText>All the best for your new project.</VCardText>
|
<VCardText>A cet endroit bientôt des statistiques</VCardText>
|
||||||
<VCardText>
|
|
||||||
Please make sure to read our <a
|
|
||||||
href="https://demos.pixinvent.com/vuexy-vuejs-admin-template/documentation/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="text-decoration-none"
|
|
||||||
>
|
|
||||||
Template Documentation
|
|
||||||
</a> to understand where to go from here and how to use our template.
|
|
||||||
</VCardText>
|
|
||||||
</VCard>
|
|
||||||
|
|
||||||
<VCard title="Want to integrate JWT? 🔒">
|
<VCardText>👉 Cliquez maintenant sur Boutique :)</VCardText>
|
||||||
<VCardText>We carefully crafted JWT flow so you can implement JWT with ease and with minimum efforts.</VCardText>
|
|
||||||
<VCardText>Please read our JWT Documentation to get more out of JWT authentication.</VCardText>
|
|
||||||
</VCard>
|
</VCard>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
|
<!-- ❗Errors in the form are set on line 60 -->
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AuthProvider from '@/views/pages/authentication/AuthProvider.vue'
|
import { VForm } from 'vuetify/components/VForm'
|
||||||
import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant'
|
import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant'
|
||||||
import authV2LoginIllustrationBorderedDark from '@images/pages/auth-v2-login-illustration-bordered-dark.png'
|
import authV2LoginIllustrationBorderedDark from '@images/pages/auth-v2-login-illustration-bordered-dark.png'
|
||||||
import authV2LoginIllustrationBorderedLight from '@images/pages/auth-v2-login-illustration-bordered-light.png'
|
import authV2LoginIllustrationBorderedLight from '@images/pages/auth-v2-login-illustration-bordered-light.png'
|
||||||
|
|
@ -8,30 +9,67 @@ import authV2LoginIllustrationLight from '@images/pages/auth-v2-login-illustrati
|
||||||
import authV2MaskDark from '@images/pages/misc-mask-dark.png'
|
import authV2MaskDark from '@images/pages/misc-mask-dark.png'
|
||||||
import authV2MaskLight from '@images/pages/misc-mask-light.png'
|
import authV2MaskLight from '@images/pages/misc-mask-light.png'
|
||||||
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
|
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
|
||||||
|
import { userStore } from '@stores/user.store'
|
||||||
import { themeConfig } from '@themeConfig'
|
import { themeConfig } from '@themeConfig'
|
||||||
|
|
||||||
|
const authThemeImg = useGenerateImageVariant(authV2LoginIllustrationLight, authV2LoginIllustrationDark, authV2LoginIllustrationBorderedLight, authV2LoginIllustrationBorderedDark, true)
|
||||||
|
|
||||||
|
const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
|
||||||
|
|
||||||
definePage({
|
definePage({
|
||||||
meta: {
|
meta: {
|
||||||
layout: 'blank',
|
layout: 'blank',
|
||||||
|
unauthenticatedOnly: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const form = ref({
|
|
||||||
email: '',
|
|
||||||
password: '',
|
|
||||||
remember: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
const isPasswordVisible = ref(false)
|
const isPasswordVisible = ref(false)
|
||||||
|
|
||||||
const authThemeImg = useGenerateImageVariant(
|
const route = useRoute()
|
||||||
authV2LoginIllustrationLight,
|
const router = useRouter()
|
||||||
authV2LoginIllustrationDark,
|
|
||||||
authV2LoginIllustrationBorderedLight,
|
|
||||||
authV2LoginIllustrationBorderedDark,
|
|
||||||
true)
|
|
||||||
|
|
||||||
const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
|
const ability = useAbility()
|
||||||
|
|
||||||
|
const errors = ref<Record<string, string | undefined>>({
|
||||||
|
username: undefined,
|
||||||
|
password: undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
const refVForm = ref<VForm>()
|
||||||
|
|
||||||
|
const credentials = ref({
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const rememberMe = ref(false)
|
||||||
|
const isSnackbarVisibility = ref(false)
|
||||||
|
|
||||||
|
const useUserStore = userStore()
|
||||||
|
|
||||||
|
const login = async () => {
|
||||||
|
try {
|
||||||
|
await useUserStore.login(credentials.value.username, credentials.value.password, ability)
|
||||||
|
|
||||||
|
// Redirect to `to` query if exist or redirect to index route
|
||||||
|
// ❗ nextTick is required to wait for DOM updates and later redirect
|
||||||
|
await nextTick(() => {
|
||||||
|
router.replace(route.query.to ? String(route.query.to) : '/')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
// ❗ show invalid credentials error
|
||||||
|
isSnackbarVisibility.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
refVForm.value?.validate()
|
||||||
|
.then(({ valid: isValid }) => {
|
||||||
|
if (isValid)
|
||||||
|
login()
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -40,8 +78,8 @@ const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
|
||||||
class="auth-wrapper bg-surface"
|
class="auth-wrapper bg-surface"
|
||||||
>
|
>
|
||||||
<VCol
|
<VCol
|
||||||
md="8"
|
lg="8"
|
||||||
class="d-none d-md-flex"
|
class="d-none d-lg-flex"
|
||||||
>
|
>
|
||||||
<div class="position-relative bg-background rounded-lg w-100 ma-8 me-0">
|
<div class="position-relative bg-background rounded-lg w-100 ma-8 me-0">
|
||||||
<div class="d-flex align-center justify-center w-100 h-100">
|
<div class="d-flex align-center justify-center w-100 h-100">
|
||||||
|
|
@ -53,15 +91,15 @@ const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<VImg
|
<VImg
|
||||||
class="auth-footer-mask"
|
|
||||||
:src="authThemeMask"
|
:src="authThemeMask"
|
||||||
|
class="auth-footer-mask"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
|
||||||
<VCol
|
<VCol
|
||||||
cols="12"
|
cols="12"
|
||||||
md="4"
|
lg="4"
|
||||||
class="auth-card-v2 d-flex align-center justify-center"
|
class="auth-card-v2 d-flex align-center justify-center"
|
||||||
>
|
>
|
||||||
<VCard
|
<VCard
|
||||||
|
|
@ -74,49 +112,51 @@ const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
|
||||||
:nodes="themeConfig.app.logo"
|
:nodes="themeConfig.app.logo"
|
||||||
class="mb-6"
|
class="mb-6"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h4 class="text-h4 mb-1">
|
<h4 class="text-h4 mb-1">
|
||||||
Welcome to <span class="text-capitalize">{{ themeConfig.app.title }}</span>! 👋🏻
|
Welcome to <span class="text-capitalize"> {{ themeConfig.app.title }} </span>! 👋🏻
|
||||||
</h4>
|
</h4>
|
||||||
<p class="mb-0">
|
<p class="mb-0">
|
||||||
Please sign-in to your account and start the adventure
|
Please sign-in to your account and start the adventure
|
||||||
</p>
|
</p>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<VForm @submit.prevent="() => { }">
|
<VForm
|
||||||
|
ref="refVForm"
|
||||||
|
@submit.prevent="onSubmit"
|
||||||
|
>
|
||||||
<VRow>
|
<VRow>
|
||||||
<!-- email -->
|
<!-- username -->
|
||||||
<VCol cols="12">
|
<VCol cols="12">
|
||||||
<AppTextField
|
<AppTextField
|
||||||
v-model="form.email"
|
v-model="credentials.username"
|
||||||
|
label="Username"
|
||||||
|
placeholder="First name"
|
||||||
|
type="username"
|
||||||
autofocus
|
autofocus
|
||||||
label="Email"
|
:rules="[requiredValidator]"
|
||||||
type="email"
|
:error-messages="errors.username"
|
||||||
placeholder="johndoe@email.com"
|
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
|
||||||
<!-- password -->
|
<!-- password -->
|
||||||
<VCol cols="12">
|
<VCol cols="12">
|
||||||
<AppTextField
|
<AppTextField
|
||||||
v-model="form.password"
|
v-model="credentials.password"
|
||||||
label="Password"
|
label="Password"
|
||||||
placeholder="············"
|
placeholder="············"
|
||||||
|
:rules="[requiredValidator]"
|
||||||
:type="isPasswordVisible ? 'text' : 'password'"
|
:type="isPasswordVisible ? 'text' : 'password'"
|
||||||
|
:error-messages="errors.password"
|
||||||
:append-inner-icon="isPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
|
:append-inner-icon="isPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
|
||||||
@click:append-inner="isPasswordVisible = !isPasswordVisible"
|
@click:append-inner="isPasswordVisible = !isPasswordVisible"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="d-flex align-center flex-wrap justify-space-between mt-2 mb-4">
|
<div class="d-flex align-center flex-wrap justify-space-between mt-1 mb-4">
|
||||||
<VCheckbox
|
<VCheckbox
|
||||||
v-model="form.remember"
|
v-model="rememberMe"
|
||||||
label="Remember me"
|
label="Remember me"
|
||||||
/>
|
/>
|
||||||
<a
|
|
||||||
class="text-primary ms-2 mb-1"
|
|
||||||
href="#"
|
|
||||||
>
|
|
||||||
Forgot Password?
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<VBtn
|
<VBtn
|
||||||
|
|
@ -126,46 +166,28 @@ const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
|
||||||
Login
|
Login
|
||||||
</VBtn>
|
</VBtn>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
|
||||||
<!-- create account -->
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
class="text-center text-base"
|
|
||||||
>
|
|
||||||
<span>New on our platform?</span>
|
|
||||||
|
|
||||||
<a
|
|
||||||
class="text-primary ms-2"
|
|
||||||
href="#"
|
|
||||||
>
|
|
||||||
Create an account
|
|
||||||
</a>
|
|
||||||
</VCol>
|
|
||||||
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
class="d-flex align-center"
|
|
||||||
>
|
|
||||||
<VDivider />
|
|
||||||
|
|
||||||
<span class="mx-4">or</span>
|
|
||||||
|
|
||||||
<VDivider />
|
|
||||||
</VCol>
|
|
||||||
|
|
||||||
<!-- auth providers -->
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
class="text-center"
|
|
||||||
>
|
|
||||||
<AuthProvider />
|
|
||||||
</VCol>
|
|
||||||
</VRow>
|
</VRow>
|
||||||
</VForm>
|
</VForm>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
|
<!-- Snackbar -->
|
||||||
|
<VSnackbar
|
||||||
|
v-model="isSnackbarVisibility"
|
||||||
|
location="center"
|
||||||
|
>
|
||||||
|
Incorrect username or password ...
|
||||||
|
|
||||||
|
<template #actions>
|
||||||
|
<VBtn
|
||||||
|
color="error"
|
||||||
|
@click="isSnackbarVisibility = false"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</VBtn>
|
||||||
|
</template>
|
||||||
|
</VSnackbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<VCard title="Create Awesome 🙌">
|
|
||||||
<VCardText>This is your second page.</VCardText>
|
|
||||||
<VCardText>
|
|
||||||
Chocolate sesame snaps pie carrot cake pastry pie lollipop muffin.
|
|
||||||
Carrot cake dragée chupa chups jujubes. Macaroon liquorice cookie
|
|
||||||
wafer tart marzipan bonbon. Gingerbread jelly-o dragée
|
|
||||||
chocolate.
|
|
||||||
</VCardText>
|
|
||||||
</VCard>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import StoreHeader from '@/views/pages/store/view/StoreHeader.vue'
|
||||||
|
import StoreTabAdmin from '@/views/pages/store/view/StoreTabAdmin.vue'
|
||||||
|
import StoreTabGeneral from '@/views/pages/store/view/StoreTabGeneral.vue'
|
||||||
|
import StoreTabItem from '@/views/pages/store/view/StoreTabItem.vue'
|
||||||
|
import StoreTabLog from '@/views/pages/store/view/StoreTabLog.vue'
|
||||||
|
import StoreTabRemote from '@/views/pages/store/view/StoreTabRemote.vue'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const route = useRoute('store-details')
|
||||||
|
|
||||||
|
const storeTab = ref(route.query.tab || 'general')
|
||||||
|
|
||||||
|
// tabs
|
||||||
|
const tabs = [
|
||||||
|
{ title: 'General', icon: 'tabler-settings', tab: 'general' },
|
||||||
|
{ title: t('Item'), icon: 'tabler-shirt-sport', tab: 'item' },
|
||||||
|
{ title: t('Remote'), icon: 'tabler-brand-openvpn', tab: 'remote' },
|
||||||
|
{ title: 'Admin', icon: 'tabler-lock', tab: 'admin' },
|
||||||
|
{ title: 'Log', icon: 'tabler-bug', tab: 'log' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const { data: storeData } = await useApi<any>(`/stores/${route.query.storeId}/details?dbHost=${route.query.dbHost}`)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VBreadcrumbs
|
||||||
|
class="px-0 py-2"
|
||||||
|
:items="[
|
||||||
|
{ title: t('Home'), to: { name: 'root' } },
|
||||||
|
{ title: t('List of stores'), to: { name: 'store-list' } },
|
||||||
|
{ title: t('Store visualization') },
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<StoreHeader
|
||||||
|
class="mb-5"
|
||||||
|
:store-data="storeData"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<VTabs
|
||||||
|
v-model="storeTab"
|
||||||
|
class="v-tabs-pill"
|
||||||
|
>
|
||||||
|
<VTab
|
||||||
|
v-for="item in tabs"
|
||||||
|
:key="item.icon"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
size="20"
|
||||||
|
start
|
||||||
|
:icon="item.icon"
|
||||||
|
/>
|
||||||
|
{{ item.title }}
|
||||||
|
</VTab>
|
||||||
|
</VTabs>
|
||||||
|
|
||||||
|
<VWindow
|
||||||
|
v-model="storeTab"
|
||||||
|
class="mt-6 disable-tab-transition"
|
||||||
|
:touch="false"
|
||||||
|
>
|
||||||
|
<!-- 👉 General -->
|
||||||
|
<VWindowItem>
|
||||||
|
<StoreTabGeneral :store-data="storeData" />
|
||||||
|
</VWindowItem>
|
||||||
|
|
||||||
|
<!-- 👉 Item -->
|
||||||
|
<VWindowItem>
|
||||||
|
<StoreTabItem />
|
||||||
|
</VWindowItem>
|
||||||
|
|
||||||
|
<!-- 👉 Remote Access -->
|
||||||
|
<VWindowItem>
|
||||||
|
<StoreTabRemote :store-data="storeData" />
|
||||||
|
</VWindowItem>
|
||||||
|
|
||||||
|
<!-- 👉 Admin -->
|
||||||
|
<VWindowItem>
|
||||||
|
<StoreTabAdmin :store-data="storeData" />
|
||||||
|
</VWindowItem>
|
||||||
|
|
||||||
|
<!-- 👉 Log -->
|
||||||
|
<VWindowItem>
|
||||||
|
<StoreTabLog :store-data="storeData" />
|
||||||
|
</VWindowItem>
|
||||||
|
</VWindow>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,364 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { VDataTable } from 'vuetify/labs/VDataTable'
|
||||||
|
import { useDataTableStore } from '@/stores/datatable.store'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const headers = computed(() => [
|
||||||
|
{ title: 'ID', key: 'id_structure' },
|
||||||
|
{ title: t('Name'), key: 'nom' },
|
||||||
|
{ title: t('Migration'), key: 'date_migration' },
|
||||||
|
{ title: 'Pos', key: 'nbcaisses' },
|
||||||
|
{ title: 'IP', key: 'ip_master', sortable: false },
|
||||||
|
{ title: t('Phone'), key: 'telephone', sortable: false },
|
||||||
|
{ title: t('Brand'), key: 'enseigne' },
|
||||||
|
{ title: t('Country'), key: 'pays' },
|
||||||
|
{ title: '', key: 'actions', sortable: false },
|
||||||
|
])
|
||||||
|
|
||||||
|
const selectedCountry = ref()
|
||||||
|
const selectedBrand = ref()
|
||||||
|
const selectedNbPos = ref()
|
||||||
|
|
||||||
|
// for reload process
|
||||||
|
const storesList = ref<any>('')
|
||||||
|
const isLoading = ref(false)
|
||||||
|
|
||||||
|
const searchQuery = ref<any>('')
|
||||||
|
|
||||||
|
// Data table options
|
||||||
|
const { data: dtListData } = await useApi<any>(createUrl('/stores'))
|
||||||
|
|
||||||
|
storesList.value = dtListData.value
|
||||||
|
|
||||||
|
const country = computed(() => {
|
||||||
|
const allItems = storesList.value.map((store: { pays: any }) => store.pays)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
const uniqueItems = allItems.filter((country: any, index: any, self: string | any[]) => self.indexOf(country) === index)
|
||||||
|
const sortedItems = uniqueItems.sort()
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
return sortedItems.map((country: any) => ({ title: country, value: country }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const brand = computed(() => {
|
||||||
|
const allItems = storesList.value.map((store: { enseigne: any }) => store.enseigne)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
const uniqueItems = allItems.filter((brand: any, index: any, self: string | any[]) => self.indexOf(brand) === index)
|
||||||
|
const sortedItems = uniqueItems.sort()
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
return sortedItems.map((brand: any) => ({ title: brand, value: brand }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const nbPos = computed(() => {
|
||||||
|
const allItems = storesList.value.map((store: { nbcaisses: number }) => store.nbcaisses)
|
||||||
|
const uniqueItems = Array.from(new Set(allItems)) as number[] // Utilisez une assertion de type
|
||||||
|
const sortedItems = uniqueItems.sort((a, b) => a - b) // Triez les nombres en ordre croissant
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
return sortedItems.map((nbPos: number) => ({ title: nbPos, value: nbPos }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const filteredData = computed(() => {
|
||||||
|
let filtered = storesList.value
|
||||||
|
|
||||||
|
// If a brand is selected, filter the records for this brand
|
||||||
|
if (selectedBrand.value)
|
||||||
|
filtered = filtered.filter((store: { enseigne: any }) => store.enseigne === selectedBrand.value)
|
||||||
|
|
||||||
|
// If a country is selected, filter the records for this country
|
||||||
|
if (selectedCountry.value)
|
||||||
|
filtered = filtered.filter((store: { pays: any }) => store.pays === selectedCountry.value)
|
||||||
|
|
||||||
|
// If a number of POS is selected, filter the records for this number
|
||||||
|
if (selectedNbPos.value)
|
||||||
|
filtered = filtered.filter((store: { nbcaisses: any }) => store.nbcaisses === selectedNbPos.value)
|
||||||
|
|
||||||
|
// If a search query is provided, filter the records for this query
|
||||||
|
if (searchQuery.value) {
|
||||||
|
filtered = filtered.filter((store: { [s: string]: unknown } | ArrayLike<unknown>) =>
|
||||||
|
Object.values(store).some(value =>
|
||||||
|
String(value).toLowerCase().includes(searchQuery.value.toLowerCase()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectedStore = ref(null)
|
||||||
|
const isDialogVisible = ref(false)
|
||||||
|
|
||||||
|
const openPosList = (store: any) => {
|
||||||
|
selectedStore.value = store
|
||||||
|
isDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Store {
|
||||||
|
id_structure: number
|
||||||
|
caisses: Caisse[]
|
||||||
|
}
|
||||||
|
interface Caisse {
|
||||||
|
id_caisse: number
|
||||||
|
ip: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const reloadStores = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const { data, error } = await useApi<any>(createUrl('/stores/reload'))
|
||||||
|
|
||||||
|
if (!isEmpty(error.value))
|
||||||
|
console.error('Error loading store data:', error.value)
|
||||||
|
|
||||||
|
// Reload the store data
|
||||||
|
const { data: storeData, error: storeError } = await useApi<any>(createUrl('/stores'))
|
||||||
|
if (!isEmpty(storeError.value))
|
||||||
|
console.error('Error loading store data:', storeError.value)
|
||||||
|
else
|
||||||
|
storesList.value = storeData.value
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// pinia store for datatable filters & options
|
||||||
|
const stStoreList = useDataTableStore()
|
||||||
|
|
||||||
|
const saveDtFilters = () => {
|
||||||
|
stStoreList.setFilters('selectedCountry', selectedCountry.value)
|
||||||
|
stStoreList.setFilters('selectedBrand', selectedBrand.value)
|
||||||
|
stStoreList.setFilters('selectedNbPos', selectedNbPos.value)
|
||||||
|
stStoreList.searchText = searchQuery.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const restoreDtFilters = () => {
|
||||||
|
if (stStoreList.filters.selectedCountry)
|
||||||
|
selectedCountry.value = stStoreList.filters.selectedCountry
|
||||||
|
|
||||||
|
if (stStoreList.filters.selectedBrand)
|
||||||
|
selectedBrand.value = stStoreList.filters.selectedBrand
|
||||||
|
|
||||||
|
if (stStoreList.filters.selectedNbPos)
|
||||||
|
selectedNbPos.value = stStoreList.filters.selectedNbPos
|
||||||
|
|
||||||
|
if (stStoreList.searchText)
|
||||||
|
searchQuery.value = stStoreList.searchText
|
||||||
|
}
|
||||||
|
|
||||||
|
const navigateToStoreDetails = (item: { ip_master: string; id_structure: number }) => {
|
||||||
|
// Save datatable filters state
|
||||||
|
saveDtFilters()
|
||||||
|
|
||||||
|
// go to store details
|
||||||
|
router.push(`/store/details?dbHost=${item.ip_master}&storeId=${item.id_structure}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// Restore datatable filters state on page load
|
||||||
|
restoreDtFilters()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(route, (to, from) => {
|
||||||
|
if (from && from.path !== '/store/details')
|
||||||
|
stStoreList.clearState()
|
||||||
|
}, { immediate: true })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VBreadcrumbs
|
||||||
|
class="px-0 py-2"
|
||||||
|
:items="[
|
||||||
|
{ title: t('Home'), to: { name: 'root' } },
|
||||||
|
{ title: t('List of stores') },
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<!-- 👉 filters -->
|
||||||
|
<VCard
|
||||||
|
:title="$t('Filters')"
|
||||||
|
class="mb-6"
|
||||||
|
>
|
||||||
|
<VCardText>
|
||||||
|
<VRow>
|
||||||
|
<!-- 👉 Select country -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="4"
|
||||||
|
>
|
||||||
|
<AppSelect
|
||||||
|
v-model="selectedCountry"
|
||||||
|
:placeholder="$t('Country')"
|
||||||
|
:items="country"
|
||||||
|
clearable
|
||||||
|
clear-icon="tabler-x"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<!-- 👉 Select Brand -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="4"
|
||||||
|
>
|
||||||
|
<AppSelect
|
||||||
|
v-model="selectedBrand"
|
||||||
|
:placeholder="$t('Brand')"
|
||||||
|
:items="brand"
|
||||||
|
clearable
|
||||||
|
clear-icon="tabler-x"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<!-- 👉 Select Multi POS -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="4"
|
||||||
|
>
|
||||||
|
<AppSelect
|
||||||
|
v-model="selectedNbPos"
|
||||||
|
:placeholder="$t('Multi POS')"
|
||||||
|
:items="nbPos"
|
||||||
|
clearable
|
||||||
|
clear-icon="tabler-x"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
<!-- 👉 datatable, search & export & reload -->
|
||||||
|
<VCard
|
||||||
|
:title="$t('List of stores')"
|
||||||
|
class="mb-6"
|
||||||
|
>
|
||||||
|
<div class="d-flex flex-wrap gap-4 mx-5">
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<!-- 👉 Search -->
|
||||||
|
<AppTextField
|
||||||
|
v-model="searchQuery"
|
||||||
|
:placeholder="$t('Search')"
|
||||||
|
density="compact"
|
||||||
|
style="inline-size: 200px;"
|
||||||
|
type="text"
|
||||||
|
class="me-3"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<VSpacer />
|
||||||
|
<div class="d-flex gap-4 flex-wrap align-center">
|
||||||
|
<VBtn
|
||||||
|
color="primary"
|
||||||
|
prepend-icon="tabler-reload"
|
||||||
|
@click="reloadStores"
|
||||||
|
>
|
||||||
|
{{ t("Reload") }}
|
||||||
|
</VBtn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<VDivider class="mt-4" />
|
||||||
|
|
||||||
|
<VCol cols="12">
|
||||||
|
<div v-if="isLoading">
|
||||||
|
<VProgressCircular
|
||||||
|
indeterminate
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- 👉 Datatable https://vuetifyjs.com/en/api/v-data-table/#props -->
|
||||||
|
<VDataTable
|
||||||
|
v-else
|
||||||
|
:headers="headers"
|
||||||
|
:items="filteredData"
|
||||||
|
:items-per-page="stStoreList.itemsPerPage"
|
||||||
|
:page="stStoreList.currentPage"
|
||||||
|
:sort-by="stStoreList.getSortBy()"
|
||||||
|
@update:sort-by="sortBy => { stStoreList.setSortBy(sortBy[0]?.key) ; stStoreList.setSortOrder(sortBy[0]?.order) }"
|
||||||
|
@update:items-per-page="itemsPerPage => { stStoreList.setItemsPerPage(itemsPerPage) }"
|
||||||
|
@update:page="page => { stStoreList.setCurrentPage(page) }"
|
||||||
|
>
|
||||||
|
<!-- Store details hyperlink -->
|
||||||
|
<template #item.nom="{ item }">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="router-link"
|
||||||
|
@click.prevent="navigateToStoreDetails(item)"
|
||||||
|
>
|
||||||
|
{{ item.nom }}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Pos count -->
|
||||||
|
<template #item.nbcaisses="{ item }">
|
||||||
|
<VIcon v-if="item.nbcaisses > 1 && item.nbcaisses <= 9">
|
||||||
|
{{ `tabler-square-rounded-number-${item.nbcaisses}` }}
|
||||||
|
</VIcon>
|
||||||
|
<span v-else-if="item.nbcaisses > 9">{{ item.nbcaisses }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<template #item.actions="{ item }">
|
||||||
|
<IconBtn>
|
||||||
|
<VIcon icon="tabler-dots-vertical" />
|
||||||
|
<VMenu activator="parent">
|
||||||
|
<VList>
|
||||||
|
<VListItem
|
||||||
|
value="connect"
|
||||||
|
prepend-icon="tabler-numbers"
|
||||||
|
@click="openPosList(item)"
|
||||||
|
>
|
||||||
|
POS
|
||||||
|
</VListItem>
|
||||||
|
</VList>
|
||||||
|
</VMenu>
|
||||||
|
</IconBtn>
|
||||||
|
</template>
|
||||||
|
</VDataTable>
|
||||||
|
<!-- 👉 Dialog page for line actions -->
|
||||||
|
<VDialog
|
||||||
|
v-model="isDialogVisible"
|
||||||
|
max-width="600"
|
||||||
|
>
|
||||||
|
<!-- Dialog close btn -->
|
||||||
|
<DialogCloseBtn @click="isDialogVisible = !isDialogVisible" />
|
||||||
|
|
||||||
|
<!-- Dialog Content -->
|
||||||
|
<VCard :title="t('Connect to invidual POS')">
|
||||||
|
<VCardText>
|
||||||
|
<div v-if="selectedStore && (selectedStore as Store).caisses">
|
||||||
|
<div
|
||||||
|
v-for="(caisse, index) in (selectedStore as Store).caisses"
|
||||||
|
:key="index"
|
||||||
|
class="d-flex align-items-center mb-5"
|
||||||
|
>
|
||||||
|
<VTextField
|
||||||
|
v-model="caisse.ip"
|
||||||
|
label="IP"
|
||||||
|
class="me-3"
|
||||||
|
/>
|
||||||
|
<VBtn
|
||||||
|
:to="`/store/details?dbHost=${caisse.ip}&storeId=${(selectedStore as Store).id_structure}&workstationId=${caisse.id_caisse}`"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
Pos {{ caisse.id_caisse }}
|
||||||
|
</VBtn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</VCardText>
|
||||||
|
<VCardText class="d-flex justify-end flex-wrap gap-3">
|
||||||
|
<VBtn
|
||||||
|
variant="tonal"
|
||||||
|
color="secondary"
|
||||||
|
@click="isDialogVisible = false"
|
||||||
|
>
|
||||||
|
{{ $t("Close") }}
|
||||||
|
</VBtn>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
</VDialog>
|
||||||
|
</VCol>
|
||||||
|
</VCard>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,492 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { endOfDay, format } from 'date-fns'
|
||||||
|
import exportFromJSON from 'export-from-json'
|
||||||
|
import { VDataTable } from 'vuetify/labs/VDataTable'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const headers = computed(() => [
|
||||||
|
{ title: '', key: 'data-table-expand' },
|
||||||
|
{ title: t('Store'), key: 'storeName' },
|
||||||
|
{ title: t('WSK'), key: 'wkstnId' },
|
||||||
|
{ title: t('Level'), key: 'logLevel' },
|
||||||
|
{ title: t('Category'), key: 'loggerCategory' },
|
||||||
|
{ title: t('Thread'), key: 'threadName' },
|
||||||
|
{ title: t('Date'), key: 'createDate' },
|
||||||
|
{ title: t('User'), key: 'createUserId' },
|
||||||
|
])
|
||||||
|
|
||||||
|
const selectedStore = ref(null)
|
||||||
|
const selectedWkStn = ref(null)
|
||||||
|
const selectedLogLevel = ref(null)
|
||||||
|
const selectedCategory = ref(null)
|
||||||
|
const selectedDateRange = ref(null)
|
||||||
|
const searchQuery = ref('')
|
||||||
|
|
||||||
|
const isSnackbarExport = ref(false)
|
||||||
|
const isSnackbarRange = ref(false)
|
||||||
|
|
||||||
|
// Data table options
|
||||||
|
const options = ref({ page: 1, itemsPerPage: 10, sortBy: ['createDate'], sortDesc: [true] })
|
||||||
|
const isLoading = ref(false)
|
||||||
|
interface dtListDataType {
|
||||||
|
businessDate: string
|
||||||
|
createDate: Date
|
||||||
|
createUserId: string
|
||||||
|
rtlLocId: number
|
||||||
|
wkstnId: number
|
||||||
|
logLevel: string
|
||||||
|
threadName: string
|
||||||
|
logMessage: string
|
||||||
|
loggerCategory: string
|
||||||
|
storeName: string
|
||||||
|
}
|
||||||
|
const dtListData = ref<dtListDataType[]>([])
|
||||||
|
const levelCounts = reactive({ INFO: 0, WARN: 0, ERROR: 0, FATAL: 0 })
|
||||||
|
|
||||||
|
const widgetData = computed(() => [
|
||||||
|
{ title: 'INFO', value: levelCounts.INFO, icon: 'tabler-info-circle' },
|
||||||
|
{ title: 'WARN', value: levelCounts.WARN, icon: 'tabler-alert-triangle' },
|
||||||
|
{ title: 'ERRROR', value: levelCounts.ERROR, icon: 'tabler-exclamation-circle' },
|
||||||
|
{ title: 'FATAL', value: levelCounts.FATAL, icon: 'tabler-bug' },
|
||||||
|
])
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
isSnackbarExport.value = false
|
||||||
|
isSnackbarRange.value = false
|
||||||
|
|
||||||
|
const rangeDate = selectedDateRange.value || ''
|
||||||
|
let beginDate
|
||||||
|
let endDate
|
||||||
|
|
||||||
|
if (rangeDate.length === 8) {
|
||||||
|
beginDate = rangeDate.substring(0, 8)
|
||||||
|
endDate = beginDate
|
||||||
|
}
|
||||||
|
else if (rangeDate.length > 0) {
|
||||||
|
beginDate = rangeDate.substring(0, 8)
|
||||||
|
endDate = rangeDate.substring(rangeDate.length - 8)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
beginDate = format(endOfDay(new Date()), 'yyyyMMdd')
|
||||||
|
endDate = beginDate
|
||||||
|
}
|
||||||
|
|
||||||
|
let paramUrl = `?beginDate=${beginDate}&endDate=${endDate}`
|
||||||
|
|
||||||
|
if (selectedStore && selectedStore.value) {
|
||||||
|
const matchedStore: dtListDataType | undefined = dtListData.value.find((item: dtListDataType) => item.storeName === selectedStore.value)
|
||||||
|
|
||||||
|
if (matchedStore && matchedStore.rtlLocId)
|
||||||
|
paramUrl += `&storeId=${matchedStore.rtlLocId}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedWkStn && selectedWkStn.value)
|
||||||
|
paramUrl += `&wkstnId=${selectedWkStn.value}`
|
||||||
|
|
||||||
|
if (searchQuery.value)
|
||||||
|
paramUrl += `&searchText=${searchQuery.value}`
|
||||||
|
|
||||||
|
levelCounts.INFO = 0
|
||||||
|
levelCounts.WARN = 0
|
||||||
|
levelCounts.ERROR = 0
|
||||||
|
levelCounts.FATAL = 0
|
||||||
|
|
||||||
|
if (beginDate !== endDate && (searchQuery.value.length < 5)) {
|
||||||
|
isSnackbarRange.value = true
|
||||||
|
dtListData.value = []
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const response = await useApi<any>(createUrl(`/xadmin/log${paramUrl}`))
|
||||||
|
|
||||||
|
if (response.data.value) {
|
||||||
|
dtListData.value = response.data.value
|
||||||
|
|
||||||
|
// Calculate the number of occurrences of each level
|
||||||
|
dtListData.value.forEach((item: { logLevel: string }) => {
|
||||||
|
const logLevel = item.logLevel as keyof typeof levelCounts
|
||||||
|
|
||||||
|
if (logLevel in levelCounts)
|
||||||
|
levelCounts[logLevel]++
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else { dtListData.value = [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = computed(() => {
|
||||||
|
const allItems = dtListData.value.map(({ storeName, rtlLocId }: { storeName: any; rtlLocId: any }) => ({ storeName, rtlLocId }))
|
||||||
|
|
||||||
|
const uniqueItems = allItems.filter((item: any, index: any, self: any[]) =>
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
index === self.findIndex(t => (
|
||||||
|
t.storeName === item.storeName && t.rtlLocId === item.rtlLocId
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
|
const sortedItems = uniqueItems.sort((a: { rtlLocId: number }, b: { rtlLocId: number }) => a.rtlLocId - b.rtlLocId)
|
||||||
|
|
||||||
|
return sortedItems.map(({ storeName }: { storeName: any }) => ({ title: storeName, value: storeName }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const wkstn = computed(() => {
|
||||||
|
const allItems = dtListData.value.map((list: { wkstnId: any }) => list.wkstnId)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
const uniqueItems = allItems.filter((wkstn: any, index: any, self: string | any[]) => self.indexOf(wkstn) === index)
|
||||||
|
const sortedItems = uniqueItems.sort()
|
||||||
|
|
||||||
|
return sortedItems.map((list: any) => ({ title: list, value: list }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const level = computed(() => {
|
||||||
|
const allItems = dtListData.value.map((list: { logLevel: any }) => list.logLevel)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
const uniqueItems = allItems.filter((level: any, index: any, self: string | any[]) => self.indexOf(level) === index)
|
||||||
|
const sortedItems = uniqueItems.sort()
|
||||||
|
|
||||||
|
return sortedItems.map((list: any) => ({ title: list, value: list }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const category = computed(() => {
|
||||||
|
const allItems = dtListData.value.map((list: { loggerCategory: any }) => list.loggerCategory)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
const uniqueItems = allItems.filter((category: any, index: any, self: string | any[]) => self.indexOf(category) === index)
|
||||||
|
const sortedItems = uniqueItems.sort()
|
||||||
|
|
||||||
|
return sortedItems.map((list: any) => ({ title: list, value: list }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const filteredLogList = computed(() => {
|
||||||
|
let filtered = dtListData.value
|
||||||
|
|
||||||
|
// If a store is selected, filter the records for this store
|
||||||
|
if (selectedStore.value !== undefined && selectedStore.value !== null)
|
||||||
|
filtered = filtered.filter((list: { storeName: any }) => list.storeName === selectedStore.value)
|
||||||
|
|
||||||
|
// If a workstation is selected, filter the records for this number
|
||||||
|
if (selectedWkStn.value !== undefined && selectedWkStn.value !== null)
|
||||||
|
filtered = filtered.filter((list: { wkstnId: any }) => list.wkstnId === selectedWkStn.value)
|
||||||
|
|
||||||
|
// If a loglevel is selected, filter the records for this Loglevel
|
||||||
|
if (selectedLogLevel.value !== undefined && selectedLogLevel.value !== null)
|
||||||
|
filtered = filtered.filter((list: { logLevel: any }) => list.logLevel === selectedLogLevel.value)
|
||||||
|
|
||||||
|
// If a loggerCategory is selected, filter the records for this loggerCategory
|
||||||
|
if (selectedCategory.value !== undefined && selectedCategory.value !== null)
|
||||||
|
filtered = filtered.filter((list: { loggerCategory: any }) => list.loggerCategory === selectedCategory.value)
|
||||||
|
|
||||||
|
// If a search query is provided, filter the records for this query
|
||||||
|
if (searchQuery.value) {
|
||||||
|
filtered = filtered.filter((log: { [s: string]: unknown } | ArrayLike<unknown>) =>
|
||||||
|
Object.values(log).some(value =>
|
||||||
|
String(value).toLowerCase().includes(searchQuery.value.toLowerCase()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
})
|
||||||
|
|
||||||
|
const ExcelField = [
|
||||||
|
'rtlLocId',
|
||||||
|
'storeName',
|
||||||
|
'wkstnId',
|
||||||
|
'logLevel',
|
||||||
|
'threadName',
|
||||||
|
'logMessage',
|
||||||
|
'loggerCategory',
|
||||||
|
'createDate',
|
||||||
|
'createUserId',
|
||||||
|
]
|
||||||
|
|
||||||
|
const ExcelData = computed(() => {
|
||||||
|
return dtListData.value.map((item: dtListDataType) => {
|
||||||
|
const orderedItem: { [key: string]: string } = {}
|
||||||
|
|
||||||
|
ExcelField.forEach(field => {
|
||||||
|
if (field === 'createDate') {
|
||||||
|
const date = new Date(item[field as keyof dtListDataType])
|
||||||
|
|
||||||
|
orderedItem[field] = format(date, 'dd/MM/yyyy HH:mm:ss')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
orderedItem[field] = String(item[field as keyof dtListDataType])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return orderedItem
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const exportEXCEL = () => {
|
||||||
|
const fileName = `LOGS_XADMIN-${format(new Date(), 'yyyyMMdd')}`
|
||||||
|
|
||||||
|
if (ExcelData.value.length > 0) {
|
||||||
|
exportFromJSON({
|
||||||
|
data: ExcelData.value,
|
||||||
|
fileName,
|
||||||
|
exportType: exportFromJSON.types.xls,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
isSnackbarExport.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VBreadcrumbs
|
||||||
|
class="px-0 py-2"
|
||||||
|
:items="[
|
||||||
|
{ title: t('Home'), to: { name: 'root' } },
|
||||||
|
{ title: t('Logs list') },
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<!-- 👉 Statistics Widgets -->
|
||||||
|
<VCard class="mb-6">
|
||||||
|
<VCardText>
|
||||||
|
<VRow>
|
||||||
|
<template
|
||||||
|
v-for="(data, id) in widgetData"
|
||||||
|
:key="id"
|
||||||
|
>
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="6"
|
||||||
|
md="3"
|
||||||
|
class="px-6"
|
||||||
|
>
|
||||||
|
<div class="d-flex justify-space-between">
|
||||||
|
<div class="d-flex flex-column gap-y-1">
|
||||||
|
<h4
|
||||||
|
class="text-h4 text-high-emphasis"
|
||||||
|
:class="{ 'text-error': id === 3 }"
|
||||||
|
>
|
||||||
|
{{ data.value }}
|
||||||
|
</h4>
|
||||||
|
<div class="text-body-1 text-capitalize">
|
||||||
|
{{ data.title }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<VAvatar
|
||||||
|
color="rgba(var(--v-theme-on-background), var(--v-hover-opacity))"
|
||||||
|
rounded
|
||||||
|
class="text-high-emphasis"
|
||||||
|
size="38"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
:icon="data.icon"
|
||||||
|
size="26"
|
||||||
|
/>
|
||||||
|
</VAvatar>
|
||||||
|
</div>
|
||||||
|
</VCol>
|
||||||
|
<VDivider
|
||||||
|
v-if="$vuetify.display.mdAndUp ? id !== widgetData.length - 1
|
||||||
|
: $vuetify.display.smAndUp ? id % 2 === 0
|
||||||
|
: false"
|
||||||
|
vertical
|
||||||
|
inset
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</VRow>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
<!-- 👉 filters -->
|
||||||
|
<VCard
|
||||||
|
:title="$t('Filters')"
|
||||||
|
class="mb-6"
|
||||||
|
>
|
||||||
|
<VCardText>
|
||||||
|
<VRow>
|
||||||
|
<!-- 👉 Select workstation -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="4"
|
||||||
|
>
|
||||||
|
<AppSelect
|
||||||
|
v-model="selectedStore"
|
||||||
|
:placeholder="$t('Store')"
|
||||||
|
:items="store"
|
||||||
|
clearable
|
||||||
|
clear-icon="tabler-x"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<!-- 👉 Select workstation -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="2"
|
||||||
|
>
|
||||||
|
<AppSelect
|
||||||
|
v-model="selectedWkStn"
|
||||||
|
:placeholder="$t('WorkStation')"
|
||||||
|
:items="wkstn"
|
||||||
|
clearable
|
||||||
|
clear-icon="tabler-x"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<!-- 👉 Select log level -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="2"
|
||||||
|
>
|
||||||
|
<AppSelect
|
||||||
|
v-model="selectedLogLevel"
|
||||||
|
:placeholder="$t('Level')"
|
||||||
|
:items="level"
|
||||||
|
clearable
|
||||||
|
clear-icon="tabler-x"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<!-- 👉 Select logger category -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="4"
|
||||||
|
>
|
||||||
|
<AppSelect
|
||||||
|
v-model="selectedCategory"
|
||||||
|
:placeholder="$t('loggerCategory')"
|
||||||
|
:items="category"
|
||||||
|
clearable
|
||||||
|
clear-icon="tabler-x"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
<VRow>
|
||||||
|
<!-- 👉 Date log -->
|
||||||
|
<VCol sm="4">
|
||||||
|
<AppDateTimePicker
|
||||||
|
v-model="selectedDateRange"
|
||||||
|
label=""
|
||||||
|
:placeholder="$t('Select date')"
|
||||||
|
:config="{ mode: 'range', altFormat: 'J M Y', altInput: true, dateFormat: 'Ymd' }"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
<!-- 👉 datatable, search & export & reload -->
|
||||||
|
<VCard
|
||||||
|
:title="$t('Logs list')"
|
||||||
|
class="mb-6"
|
||||||
|
>
|
||||||
|
<div class="d-flex flex-wrap gap-4 mx-5">
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<!-- 👉 Search -->
|
||||||
|
<AppTextField
|
||||||
|
v-model="searchQuery"
|
||||||
|
:placeholder="$t('Search')"
|
||||||
|
density="compact"
|
||||||
|
style="inline-size: 200px;"
|
||||||
|
class="me-3"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<VSpacer />
|
||||||
|
<div class="d-flex gap-1 flex-wrap aalign-center">
|
||||||
|
<!-- 👉 Export button -->
|
||||||
|
<VBtn
|
||||||
|
variant="tonal"
|
||||||
|
color="primary"
|
||||||
|
prepend-icon="tabler-upload"
|
||||||
|
@click="exportEXCEL"
|
||||||
|
>
|
||||||
|
Export
|
||||||
|
</VBtn>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-4 flex-wrap align-center">
|
||||||
|
<VBtn
|
||||||
|
color="primary"
|
||||||
|
prepend-icon="tabler-reload"
|
||||||
|
@click="fetchData"
|
||||||
|
>
|
||||||
|
{{ t("Reload") }}
|
||||||
|
</VBtn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<VDivider class="mt-4" />
|
||||||
|
|
||||||
|
<VCol cols="12">
|
||||||
|
<div v-if="isLoading">
|
||||||
|
<VProgressCircular
|
||||||
|
indeterminate
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- 👉 Datatable -->
|
||||||
|
<VDataTable
|
||||||
|
v-else
|
||||||
|
class="dt-row-striped"
|
||||||
|
:headers="headers"
|
||||||
|
:items="filteredLogList"
|
||||||
|
:items-per-page="options.itemsPerPage"
|
||||||
|
:page="options.page"
|
||||||
|
:options="options"
|
||||||
|
density="compact"
|
||||||
|
expand-on-click
|
||||||
|
>
|
||||||
|
<!-- format date log -->
|
||||||
|
<template #item.createDate="{ item }">
|
||||||
|
{{ format(new Date(item.createDate), 'dd/MM/yyyy HH:mm:ss') }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- format business date -->
|
||||||
|
<template #item.businessDate="{ item }">
|
||||||
|
{{ format(new Date(item.businessDate), 'dd/MM/yyyy') }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Expanded Row Data -->
|
||||||
|
<template #expanded-row="slotProps">
|
||||||
|
<tr class="v-data-table__tr">
|
||||||
|
<td :colspan="headers.length">
|
||||||
|
<p class="my-1">
|
||||||
|
{{ slotProps.item.logMessage }}
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</VDataTable>
|
||||||
|
</VCol>
|
||||||
|
</VCard>
|
||||||
|
</div>
|
||||||
|
<!-- Snackbar Export -->
|
||||||
|
<VSnackbar
|
||||||
|
v-model="isSnackbarExport"
|
||||||
|
location="center"
|
||||||
|
>
|
||||||
|
{{ $t('ExcelData is empty, cannot export') }}
|
||||||
|
|
||||||
|
<template #actions>
|
||||||
|
<VBtn
|
||||||
|
color="error"
|
||||||
|
@click="isSnackbarExport = false"
|
||||||
|
>
|
||||||
|
{{ $t("Close") }}
|
||||||
|
</VBtn>
|
||||||
|
</template>
|
||||||
|
</VSnackbar>
|
||||||
|
<!-- Snackbar Export -->
|
||||||
|
<VSnackbar
|
||||||
|
v-model="isSnackbarRange"
|
||||||
|
location="center"
|
||||||
|
>
|
||||||
|
{{ $t('Dates range error') }}
|
||||||
|
|
||||||
|
<template #actions>
|
||||||
|
<VBtn
|
||||||
|
color="error"
|
||||||
|
@click="isSnackbarRange = false"
|
||||||
|
>
|
||||||
|
{{ $t("Close") }}
|
||||||
|
</VBtn>
|
||||||
|
</template>
|
||||||
|
</VSnackbar>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import type { Router } from 'vue-router'
|
||||||
|
import { canNavigate } from '@layouts/plugins/casl'
|
||||||
|
|
||||||
|
export const setupGuards = (router: Router) => {
|
||||||
|
// 👉 router.beforeEach
|
||||||
|
// Docs: https://router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards
|
||||||
|
router.beforeEach(to => {
|
||||||
|
/*
|
||||||
|
* If it's a public route, continue navigation. This kind of pages are allowed to visited by login & non-login users. Basically, without any restrictions.
|
||||||
|
* Examples of public routes are, 404, under maintenance, etc.
|
||||||
|
*/
|
||||||
|
if (to.meta.public)
|
||||||
|
return
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user is logged in by checking if token & user data exists in local storage
|
||||||
|
* Feel free to update this logic to suit your needs
|
||||||
|
*/
|
||||||
|
const isLoggedIn = !!(useCookie('userData').value && useCookie('accessToken').value)
|
||||||
|
|
||||||
|
/*
|
||||||
|
If user is logged in and is trying to access login like page, redirect to home
|
||||||
|
else allow visiting the page
|
||||||
|
(WARN: Don't allow executing further by return statement because next code will check for permissions)
|
||||||
|
*/
|
||||||
|
if (to.meta.unauthenticatedOnly) {
|
||||||
|
if (isLoggedIn)
|
||||||
|
return '/'
|
||||||
|
else
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!canNavigate(to)) {
|
||||||
|
/* eslint-disable indent */
|
||||||
|
return isLoggedIn
|
||||||
|
? { name: 'not-authorized' }
|
||||||
|
: {
|
||||||
|
name: 'login',
|
||||||
|
query: {
|
||||||
|
...to.query,
|
||||||
|
to: to.fullPath !== '/' ? to.path : undefined,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
/* eslint-enable indent */
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { createMongoAbility } from '@casl/ability'
|
||||||
|
|
||||||
|
export type Actions = 'create' | 'read' | 'update' | 'delete' | 'manage'
|
||||||
|
|
||||||
|
// ex: Post, Comment, User, etc. We haven't used any of these in our demo though.
|
||||||
|
export type Subjects = 'Post' | 'Comment' | 'all'
|
||||||
|
|
||||||
|
export interface Rule { action: Actions; subject: Subjects }
|
||||||
|
|
||||||
|
export const ability = createMongoAbility<[Actions, Subjects]>()
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { useAbility as useCaslAbility } from '@casl/vue'
|
||||||
|
import type { ability } from '../ability'
|
||||||
|
|
||||||
|
export const useAbility = () => useCaslAbility<typeof ability>()
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import type { App } from 'vue'
|
||||||
|
|
||||||
|
import { createMongoAbility } from '@casl/ability'
|
||||||
|
import { abilitiesPlugin } from '@casl/vue'
|
||||||
|
import type { Rule } from './ability'
|
||||||
|
|
||||||
|
export default function (app: App) {
|
||||||
|
const userAbilityRules = useCookie<Rule[]>('userAbilityRules')
|
||||||
|
const initialAbility = createMongoAbility(userAbilityRules.value ?? [])
|
||||||
|
|
||||||
|
app.use(abilitiesPlugin, initialAbility, {
|
||||||
|
useGlobalProperties: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { ability } from './ability';
|
||||||
|
|
||||||
|
declare module 'vue' {
|
||||||
|
interface ComponentCustomProperties {
|
||||||
|
$ability: typeof ability;
|
||||||
|
$can(this: this, ...args: Parameters<this['$ability']['can']>): boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
export const genId = <T extends { id: number | string }>(array: T[]) => {
|
||||||
|
const { length } = array
|
||||||
|
|
||||||
|
let lastIndex = 0
|
||||||
|
|
||||||
|
if (length)
|
||||||
|
lastIndex = Number(array[length - 1]?.id) + 1
|
||||||
|
|
||||||
|
return lastIndex || (length + 1)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export const paginateArray = (array: unknown[], perPage: number, page: number) => array.slice((page - 1) * perPage, page * perPage)
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export const paginationMeta = <T extends { page: number; itemsPerPage: number }>(options: T, total: number) => {
|
||||||
|
const start = (options.page - 1) * options.itemsPerPage + 1
|
||||||
|
const end = Math.min(options.page * options.itemsPerPage, total)
|
||||||
|
|
||||||
|
return `Showing ${total === 0 ? 0 : start} to ${end} of ${total} entries`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import type { App } from 'vue'
|
||||||
|
import { createI18n } from 'vue-i18n'
|
||||||
|
import { cookieRef } from '@layouts/stores/config'
|
||||||
|
import { themeConfig } from '@themeConfig'
|
||||||
|
|
||||||
|
const messages = Object.fromEntries(
|
||||||
|
Object.entries(
|
||||||
|
import.meta.glob<{ default: any }>('./locales/*.json', { eager: true }))
|
||||||
|
.map(([key, value]) => [key.slice(10, -5), value.default]),
|
||||||
|
)
|
||||||
|
|
||||||
|
let _i18n: any = null
|
||||||
|
|
||||||
|
export const getI18n = () => {
|
||||||
|
if (_i18n === null) {
|
||||||
|
_i18n = createI18n({
|
||||||
|
legacy: false,
|
||||||
|
locale: cookieRef('language', themeConfig.app.i18n.defaultLocale).value,
|
||||||
|
fallbackLocale: 'en',
|
||||||
|
messages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return _i18n
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function (app: App) {
|
||||||
|
app.use(getI18n())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,282 @@
|
||||||
|
{
|
||||||
|
"Home": "الصفحة الرئيسية",
|
||||||
|
"Store": "متجر",
|
||||||
|
"Stores": "المتاجر",
|
||||||
|
"Flow": "التدفق",
|
||||||
|
"OBI": "OBI",
|
||||||
|
"Dotsoft": "Dotsoft",
|
||||||
|
"List of stores": "قائمة المتاجر",
|
||||||
|
"Store visualization": "عرض المتاجر",
|
||||||
|
"BL not sent list": "قائمة البيانات الخارجية غير المرسلة",
|
||||||
|
"BL not sent": "بيانات البيانات الخارجية غير المرسلة",
|
||||||
|
"ST ID": "ST ID",
|
||||||
|
"Exp ID": "Exp ID",
|
||||||
|
"BL ID": "BL ID",
|
||||||
|
"BL Date": "تاريخ BL",
|
||||||
|
"External Code": "رمز خارجي",
|
||||||
|
"Notes": "ملاحظات",
|
||||||
|
"Expeditor": "المندوب",
|
||||||
|
"Distributor": "البائع",
|
||||||
|
"Distributors": "البائعين",
|
||||||
|
"Season": "الموسم",
|
||||||
|
"Country": "الدولة",
|
||||||
|
"Brand": "العلامة التجارية",
|
||||||
|
"R-C-S": "R-C-S",
|
||||||
|
"Item ID": "معرف العنصر",
|
||||||
|
"Chain": "المجموعة",
|
||||||
|
"Multi POS": "Multi POS",
|
||||||
|
"Search": "بحث",
|
||||||
|
"Name": "الاسم",
|
||||||
|
"Phone": "هاتف",
|
||||||
|
"Mode": "الوضع",
|
||||||
|
"Value": "القيمة",
|
||||||
|
"Item": "العنصر",
|
||||||
|
"Items": "العناصر",
|
||||||
|
"Remote": "متصل",
|
||||||
|
"Option": "خيار",
|
||||||
|
"Stock": "المخزون",
|
||||||
|
"Price": "السعر",
|
||||||
|
"ITEM_ID": "ID العنصر",
|
||||||
|
"PRICE": "السعر",
|
||||||
|
"TYPE": "TYPE",
|
||||||
|
"PARENT": "PARENT",
|
||||||
|
"LEVEL": "LEVEL",
|
||||||
|
"Migration": "MIGRATION",
|
||||||
|
"EFFECTIVE DATE": "EFFECTIVE DATE",
|
||||||
|
"EXPIRATION DATE": "EXPIRATION DATE",
|
||||||
|
"DATE_CREATE": "CREATE DATE",
|
||||||
|
"USER_CREATE": "CREATE USER",
|
||||||
|
"DATE_UPDATE": "UPDATE DATE",
|
||||||
|
"USER_UPDATE": "UPDATE USER",
|
||||||
|
"Reload": "تحديث",
|
||||||
|
"WorkStation": "القطعة العملية",
|
||||||
|
"SignatureString": "Signature string",
|
||||||
|
"SignatureSource": "Signature source",
|
||||||
|
"Submit": "Submit",
|
||||||
|
"You can search for a reference, reference-color or reference-color-size.": "You can search for a reference, reference-color or reference-color-size.",
|
||||||
|
"Minimum 5 characters long": "Minimum 5 characters long",
|
||||||
|
"Filters": "Filters",
|
||||||
|
"XADMIN": "XADMIN",
|
||||||
|
"Logs": "Logs",
|
||||||
|
"Level": "level",
|
||||||
|
"loggerCategory": "loggerCategory",
|
||||||
|
"Thread": "Thread",
|
||||||
|
"Date": "Date",
|
||||||
|
"WSK": "WSK",
|
||||||
|
"Select date": "Select date",
|
||||||
|
"ExcelData is empty, cannot export": "ExcelData is empty, cannot export",
|
||||||
|
"Logs list": "Logs list",
|
||||||
|
"Business Date": "Business Date",
|
||||||
|
"Dates range error": "With a date range, a text of at least 5 characters must be specified",
|
||||||
|
"Close": "Close",
|
||||||
|
"Connect to invidual POS": "Connect to invidual POS",
|
||||||
|
"---------------------------": "---------------------------",
|
||||||
|
"UI Elements": "عناصر واجهة المستخدم",
|
||||||
|
"Forms & Tables": "النماذج والجداول",
|
||||||
|
"Pages": "الصفحات",
|
||||||
|
"Charts & Maps": "الرسوم البيانية والخرائط",
|
||||||
|
"Others": "آحرون",
|
||||||
|
"Typography": "الطباعة",
|
||||||
|
"Cards": "البطاقات",
|
||||||
|
"Basic": "أساسي",
|
||||||
|
"Advance": "يتقدم",
|
||||||
|
"Widgets": "الحاجيات",
|
||||||
|
"Actions": "أجراءات",
|
||||||
|
"Components": "عناصر",
|
||||||
|
"Alert": "انذار",
|
||||||
|
"Close Alert": "أغلق التنبيه",
|
||||||
|
"Avatar": "الصورة الرمزية",
|
||||||
|
"Badge": "شارة",
|
||||||
|
"Button": "زر",
|
||||||
|
"Calendar": "تقويم",
|
||||||
|
"Image": "صورة",
|
||||||
|
"Pagination": "ترقيم الصفحات",
|
||||||
|
"Progress Circular": "تقدم التعميم",
|
||||||
|
"Progress Linear": "تقدم خطي",
|
||||||
|
"Autocomplete": "الإكمال التلقائي",
|
||||||
|
"Tooltip": "تلميح",
|
||||||
|
"Slider": "المنزلق",
|
||||||
|
"Date Time Picker": "منتقي التاريخ والوقت",
|
||||||
|
"Select": "يختار",
|
||||||
|
"Switch": "يُحوّل",
|
||||||
|
"Checkbox": "خانة اختيار",
|
||||||
|
"Radio": "مذياع",
|
||||||
|
"Textarea": "تيكستاريا",
|
||||||
|
"Rating": "تقييم",
|
||||||
|
"File Input": "إدخال الملف",
|
||||||
|
"Otp Input": "إدخال أوتب",
|
||||||
|
"Form Layout": "تخطيط النموذج",
|
||||||
|
"Form Validation": "التحقق من صحة النموذج",
|
||||||
|
"Charts": "الرسوم البيانية",
|
||||||
|
"Apex Chart": "مخطط أبيكس",
|
||||||
|
"Chartjs": "تشارتجس",
|
||||||
|
"Account Settings": "إعدادت الحساب",
|
||||||
|
"User Profile": "ملف تعريفي للمستخدم",
|
||||||
|
"FAQ": "التعليمات",
|
||||||
|
"Dialog Examples": "أمثلة على الحوار",
|
||||||
|
"Pricing": "التسعير",
|
||||||
|
"List": "قائمة",
|
||||||
|
"Edit": "يحرر",
|
||||||
|
"Nav Levels": "مستويات التنقل",
|
||||||
|
"Level 2.1": "المستوى 2.1",
|
||||||
|
"Level 2.2": "مستوى 2.2",
|
||||||
|
"Level 3.1": "المستوى 3.1",
|
||||||
|
"Level 3.2": "المستوى 3.2",
|
||||||
|
"Raise Support": "رفع الدعم",
|
||||||
|
"Documentation": "توثيق",
|
||||||
|
"Dashboards": "لوحات القيادة",
|
||||||
|
"Apps & Pages": "التطبيقات والصفحات",
|
||||||
|
"Email": "البريد الإلكتروني",
|
||||||
|
"Chat": "دردشة",
|
||||||
|
"Invoice": "فاتورة",
|
||||||
|
"Preview": "معاينة",
|
||||||
|
"Add": "يضيف",
|
||||||
|
"User": "المستعمل",
|
||||||
|
"View": "رأي",
|
||||||
|
"Login v1": "تسجيل الدخول v1",
|
||||||
|
"Login v2": "تسجيل الدخول v2",
|
||||||
|
"Login": "تسجيل الدخول",
|
||||||
|
"Register v1": "تسجيل v1",
|
||||||
|
"Register v2": "تسجيل v2",
|
||||||
|
"Register": "تسجيل",
|
||||||
|
"Forget Password v1": "نسيت كلمة المرور v1",
|
||||||
|
"Forget Password v2": "نسيت كلمة المرور v2",
|
||||||
|
"Forgot Password v1": "نسيت كلمة المرور v1",
|
||||||
|
"Forgot Password v2": "نسيت كلمة المرور v2",
|
||||||
|
"Forgot Password": "نسيت كلمة المرور",
|
||||||
|
"Reset Password v1": "إعادة تعيين كلمة المرور v1",
|
||||||
|
"Reset Password v2": "إعادة تعيين كلمة المرور v2",
|
||||||
|
"Reset Password": "إعادة تعيين كلمة المرور",
|
||||||
|
"Miscellaneous": "متفرقات",
|
||||||
|
"Coming Soon": "قريبا",
|
||||||
|
"Not Authorized": "غير مخول",
|
||||||
|
"Under Maintenance": "تحت الصيانة",
|
||||||
|
"Error": "خطأ",
|
||||||
|
"Errors": "خطوات",
|
||||||
|
"Statistics": "إحصائيات",
|
||||||
|
"Analytics": "تحليلات",
|
||||||
|
"Access Control": "صلاحية التحكم صلاحية الدخول",
|
||||||
|
"User Interface": "واجهة المستخدم",
|
||||||
|
"CRM": "سي آر إم",
|
||||||
|
"Icons": "أيقونات",
|
||||||
|
"Chip": "رقاقة",
|
||||||
|
"Dialog": "حوار",
|
||||||
|
"Expansion Panel": "لوحة التوسع",
|
||||||
|
"Combobox": "صندوق التحرير",
|
||||||
|
"Textfield": "مجال التحرير مكان كتابة النص",
|
||||||
|
"Range Slider": "نطاق المنزلق",
|
||||||
|
"Menu": "قائمة الطعام",
|
||||||
|
"Snackbar": "مطعم الوجبات الخفيفة",
|
||||||
|
"Tabs": "نوافذ التبويب",
|
||||||
|
"Form Elements": "عناصر النموذج",
|
||||||
|
"Form Layouts": "تخطيطات النموذج",
|
||||||
|
"Authentication": "المصادقة",
|
||||||
|
"Page Not Found - 404": "الصفحة غير موجودة - 404",
|
||||||
|
"Not Authorized - 401": "غير مصرح - 401",
|
||||||
|
"Server Error - 500": "خطأ في الخادم - 500",
|
||||||
|
"2": "2",
|
||||||
|
"Forms": "نماذج",
|
||||||
|
"Timeline": "الجدول الزمني",
|
||||||
|
"Disabled Menu": "قائمة المعوقين",
|
||||||
|
"Help Center": "مركز المساعدة",
|
||||||
|
"Verify Email": "التحقق من البريد الإلكتروني",
|
||||||
|
"Verify Email v1": "تحقق من البريد الإلكتروني v1",
|
||||||
|
"Verify Email v2": "تحقق من البريد الإلكتروني v2",
|
||||||
|
"Two Steps": "خطوتين",
|
||||||
|
"Two Steps v1": "خطوتين v1.0",
|
||||||
|
"Two Steps v2": "خطوتين v2.0",
|
||||||
|
"Custom Input": "إدخال مخصص",
|
||||||
|
"Extensions": "ملحقات",
|
||||||
|
"Tour": "رحلة",
|
||||||
|
"Register Multi-Steps": "تسجيل خطوات متعددة",
|
||||||
|
"Wizard Examples": "أمثلة المعالج",
|
||||||
|
"Checkout": "الدفع",
|
||||||
|
"Create Deal": "إنشاء صفقة",
|
||||||
|
"Property Listing": "قائمة الممتلكات ",
|
||||||
|
"Roles & Permissions": "الأدوار والأذونات",
|
||||||
|
"Roles": "الأدوار",
|
||||||
|
"Permissions": "الأذونات",
|
||||||
|
"Simple Table": "جدول بسيط",
|
||||||
|
"Tables": "الجداول",
|
||||||
|
"DataTable": "جدول البيانات",
|
||||||
|
"Data Table": "جدول البيانات",
|
||||||
|
"Apps": "التطبيقات",
|
||||||
|
"Misc": "متفرقات",
|
||||||
|
"Wizard Pages": "صفحات المعالج",
|
||||||
|
"eCommerce": "التجارة الإلكترونية",
|
||||||
|
"Form Wizard": "معالج النموذج",
|
||||||
|
"Numbered": "مرقم",
|
||||||
|
"ecommerce": "التجارة الإلكترونية",
|
||||||
|
"Ecommerce": "التجارة الإلكترونية",
|
||||||
|
"Product": "المنتج",
|
||||||
|
"Category": "الفئة",
|
||||||
|
"Order": "طلب",
|
||||||
|
"Details": "تفاصيل",
|
||||||
|
"Customer": "الزبون",
|
||||||
|
"Manage Review": "إدارة المراجعة",
|
||||||
|
"Referrals": "الإحالات",
|
||||||
|
"Settings": "الإعدادات",
|
||||||
|
"Course Details": "تفاصيل الدورة التدريبية",
|
||||||
|
"My Course": "دورتي",
|
||||||
|
"Overview": "نظرة عامة",
|
||||||
|
"Academy": "أكاديمية",
|
||||||
|
"Logistics": "الخدمات اللوجستية",
|
||||||
|
"Dashboard": "لوحة القيادة",
|
||||||
|
"Fleet": "الأسطول",
|
||||||
|
"Editors": "المحررين",
|
||||||
|
"Front Pages": "الصفحات الأمامية",
|
||||||
|
"Landing": "المقصودة",
|
||||||
|
"checkout": "الدفع",
|
||||||
|
"Payment": "دفع",
|
||||||
|
"Swiper": "المنزلق",
|
||||||
|
"3": "3",
|
||||||
|
"5": "5",
|
||||||
|
"10": "10",
|
||||||
|
"20": "20",
|
||||||
|
"25": "25",
|
||||||
|
"50": "50",
|
||||||
|
"100": "100",
|
||||||
|
"$vuetify": {
|
||||||
|
"badge": "شارة",
|
||||||
|
"noDataText": "لا تتوافر بيانات",
|
||||||
|
"close": "قريب",
|
||||||
|
"open": "افتح",
|
||||||
|
"carousel": {
|
||||||
|
"ariaLabel": {
|
||||||
|
"delimiter": "تحديد"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dataFooter": {
|
||||||
|
"itemsPerPageText": "مواد لكل صفحة:",
|
||||||
|
"itemsPerPageAll": "الجميع",
|
||||||
|
"pageText": "{0} - {1} من {2}",
|
||||||
|
"firstPage": "الصفحة الأولى",
|
||||||
|
"prevPage": "الصفحة السابقة",
|
||||||
|
"nextPage": "الصفحة التالية",
|
||||||
|
"lastPage": "آخر صفحة"
|
||||||
|
},
|
||||||
|
"pagination": {
|
||||||
|
"ariaLabel": {
|
||||||
|
"root": "جذر",
|
||||||
|
"previous": "السابق",
|
||||||
|
"next": "التالي",
|
||||||
|
"currentPage": "الصفحه الحاليه",
|
||||||
|
"page": "صفحة"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"input": {
|
||||||
|
"clear": "صافي",
|
||||||
|
"appendAction": "إلحاق الإجراء",
|
||||||
|
"prependAction": "قبل العمل",
|
||||||
|
"otp": "أوتب"
|
||||||
|
},
|
||||||
|
"fileInput": {
|
||||||
|
"counterSize": "حجم العداد"
|
||||||
|
},
|
||||||
|
"rating": {
|
||||||
|
"ariaLabel": {
|
||||||
|
"item": "العنصر"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,282 @@
|
||||||
|
{
|
||||||
|
"Home": "Home",
|
||||||
|
"Store": "Store",
|
||||||
|
"Stores": "Stores",
|
||||||
|
"Flow": "Flow",
|
||||||
|
"OBI": "OBI",
|
||||||
|
"Dotsoft": "Dotsoft",
|
||||||
|
"List of stores": "List of stores",
|
||||||
|
"Store visualization": "Store visualization",
|
||||||
|
"BL not sent list": "BL not sent list",
|
||||||
|
"BL not sent": "BL not sent",
|
||||||
|
"ST ID": "ST ID",
|
||||||
|
"Exp ID": "Exp ID",
|
||||||
|
"BL ID": "BL ID",
|
||||||
|
"BL Date": "BL Date",
|
||||||
|
"External Code": "External Code",
|
||||||
|
"Notes": "Notes",
|
||||||
|
"Expeditor": "Expeditor",
|
||||||
|
"Distributor": "Distributor",
|
||||||
|
"Distributors": "Distributors",
|
||||||
|
"Season": "Season",
|
||||||
|
"Country": "Country",
|
||||||
|
"Brand": "Brand",
|
||||||
|
"R-C-S": "R-C-S",
|
||||||
|
"Item ID": "Item ID",
|
||||||
|
"Chain": "Chain",
|
||||||
|
"Multi POS": "Multi POS",
|
||||||
|
"Search": "Search",
|
||||||
|
"Name": "Name",
|
||||||
|
"Phone": "Phone",
|
||||||
|
"Mode": "Mode",
|
||||||
|
"Value": "Value",
|
||||||
|
"Item": "Item",
|
||||||
|
"Items": "Items",
|
||||||
|
"Remote": "Remote",
|
||||||
|
"Option": "Option",
|
||||||
|
"Stock": "Stock",
|
||||||
|
"Price": "Price",
|
||||||
|
"ITEM_ID": "ITEM ID",
|
||||||
|
"PRICE": "PRICE",
|
||||||
|
"TYPE": "TYPE",
|
||||||
|
"PARENT": "PARENT",
|
||||||
|
"LEVEL": "LEVEL",
|
||||||
|
"Migration": "MIGRATION",
|
||||||
|
"EFFECTIVE DATE": "EFFECTIVE DATE",
|
||||||
|
"EXPIRATION DATE": "EXPIRATION DATE",
|
||||||
|
"DATE_CREATE": "CREATE DATE",
|
||||||
|
"USER_CREATE": "CREATE USER",
|
||||||
|
"DATE_UPDATE": "UPDATE DATE",
|
||||||
|
"USER_UPDATE": "UPDATE USER",
|
||||||
|
"Reload": "Reload",
|
||||||
|
"WorkStation": "Workstation",
|
||||||
|
"SignatureString": "Signature string",
|
||||||
|
"SignatureSource": "Signature source",
|
||||||
|
"Submit": "Submit",
|
||||||
|
"You can search for a reference, reference-color or reference-color-size.": "You can search for a reference, reference-color or reference-color-size.",
|
||||||
|
"Minimum 5 characters long": "Minimum 5 characters long",
|
||||||
|
"Filters": "Filters",
|
||||||
|
"XADMIN": "XADMIN",
|
||||||
|
"Logs": "Logs",
|
||||||
|
"Level": "level",
|
||||||
|
"loggerCategory": "loggerCategory",
|
||||||
|
"Thread": "Thread",
|
||||||
|
"Date": "Date",
|
||||||
|
"WSK": "WSK",
|
||||||
|
"Select date": "Select date",
|
||||||
|
"ExcelData is empty, cannot export": "No data, cannot export",
|
||||||
|
"Logs list": "Logs list",
|
||||||
|
"Business Date": "Business Date",
|
||||||
|
"Dates range error": "With a date range, a text of at least 5 characters must be specified",
|
||||||
|
"Close": "Close",
|
||||||
|
"Connect to invidual POS": "Connect to invidual POS",
|
||||||
|
"---------------------------": "---------------------------",
|
||||||
|
"UI Elements": "UI Elements",
|
||||||
|
"Forms & Tables": "Forms & Tables",
|
||||||
|
"Pages": "Pages",
|
||||||
|
"Charts & Maps": "Charts & Maps",
|
||||||
|
"Others": "Others",
|
||||||
|
"Typography": "Typography",
|
||||||
|
"Cards": "Cards",
|
||||||
|
"Basic": "Basic",
|
||||||
|
"Advance": "Advance",
|
||||||
|
"Widgets": "Widgets",
|
||||||
|
"Components": "Components",
|
||||||
|
"Alert": "Alert",
|
||||||
|
"Close Alert": "Close Alert",
|
||||||
|
"Avatar": "Avatar",
|
||||||
|
"Badge": "Badge",
|
||||||
|
"Button": "Button",
|
||||||
|
"Calendar": "Calendar",
|
||||||
|
"Image": "Image",
|
||||||
|
"Pagination": "Pagination",
|
||||||
|
"Progress Circular": "Progress Circular",
|
||||||
|
"Progress Linear": "Progress Linear",
|
||||||
|
"Autocomplete": "Autocomplete",
|
||||||
|
"Tooltip": "Tooltip",
|
||||||
|
"Slider": "Slider",
|
||||||
|
"Date Time Picker": "Date Time Picker",
|
||||||
|
"Select": "Select",
|
||||||
|
"Switch": "Switch",
|
||||||
|
"Checkbox": "Checkbox",
|
||||||
|
"Radio": "Radio",
|
||||||
|
"Textarea": "Textarea",
|
||||||
|
"Rating": "Rating",
|
||||||
|
"File Input": "File Input",
|
||||||
|
"Otp Input": "Otp Input",
|
||||||
|
"Form Layout": "Form Layout",
|
||||||
|
"Form Validation": "Form Validation",
|
||||||
|
"Charts": "Charts",
|
||||||
|
"Apex Chart": "Apex Chart",
|
||||||
|
"Chartjs": "Chartjs",
|
||||||
|
"Account Settings": "Account Settings",
|
||||||
|
"User Profile": "User Profile",
|
||||||
|
"FAQ": "FAQ",
|
||||||
|
"Dialog Examples": "Dialog Examples",
|
||||||
|
"Pricing": "Pricing",
|
||||||
|
"List": "List",
|
||||||
|
"Edit": "Edit",
|
||||||
|
"Nav Levels": "Nav Levels",
|
||||||
|
"Level 2.1": "Level 2.1",
|
||||||
|
"Level 2.2": "Level 2.2",
|
||||||
|
"Level 3.1": "Level 3.1",
|
||||||
|
"Level 3.2": "Level 3.2",
|
||||||
|
"Raise Support": "Raise Support",
|
||||||
|
"Documentation": "Documentation",
|
||||||
|
"Dashboards": "Dashboards",
|
||||||
|
"Analytics": "Analytics",
|
||||||
|
"Apps & Pages": "Apps & Pages",
|
||||||
|
"Email": "Email",
|
||||||
|
"Chat": "Chat",
|
||||||
|
"Invoice": "Invoice",
|
||||||
|
"Preview": "Preview",
|
||||||
|
"Add": "Add",
|
||||||
|
"User": "User",
|
||||||
|
"View": "View",
|
||||||
|
"Login v1": "Login v1",
|
||||||
|
"Login v2": "Login v2",
|
||||||
|
"Login": "Login",
|
||||||
|
"Register v1": "Register v1",
|
||||||
|
"Register v2": "Register v2",
|
||||||
|
"Register": "Register",
|
||||||
|
"Forget Password v1": "Forget Password v1",
|
||||||
|
"Forget Password v2": "Forget Password v2",
|
||||||
|
"Forgot Password v1": "Forgot Password v1",
|
||||||
|
"Forgot Password v2": "Forgot Password v2",
|
||||||
|
"Forgot Password": "Forgot Password",
|
||||||
|
"Reset Password v1": "Reset Password v1",
|
||||||
|
"Reset Password v2": "Reset Password v2",
|
||||||
|
"Reset Password": "Reset Password",
|
||||||
|
"Miscellaneous": "Miscellaneous",
|
||||||
|
"Coming Soon": "Coming Soon",
|
||||||
|
"Not Authorized": "Not Authorized",
|
||||||
|
"Under Maintenance": "Under Maintenance",
|
||||||
|
"Error": "Error",
|
||||||
|
"Errors": "Errors",
|
||||||
|
"Statistics": "Statistics",
|
||||||
|
"Actions": "Actions",
|
||||||
|
"Access Control": "Access Control",
|
||||||
|
"User Interface": "User Interface",
|
||||||
|
"CRM": "CRM",
|
||||||
|
"eCommerce": "eCommerce",
|
||||||
|
"Icons": "Icons",
|
||||||
|
"Chip": "Chip",
|
||||||
|
"Dialog": "Dialog",
|
||||||
|
"Expansion Panel": "Expansion Panel",
|
||||||
|
"Combobox": "Combobox",
|
||||||
|
"Textfield": "Textfield",
|
||||||
|
"Range Slider": "Range Slider",
|
||||||
|
"Menu": "Menu",
|
||||||
|
"Snackbar": "Snackbar",
|
||||||
|
"Tabs": "Tabs",
|
||||||
|
"Form Elements": "Form Elements",
|
||||||
|
"Form Layouts": "Form Layouts",
|
||||||
|
"Authentication": "Authentication",
|
||||||
|
"Page Not Found - 404": "Page Not Found - 404",
|
||||||
|
"Not Authorized - 401": "Not Authorized - 401",
|
||||||
|
"Server Error - 500": "Server Error - 500",
|
||||||
|
"2": "2",
|
||||||
|
"Forms": "Forms",
|
||||||
|
"Timeline": "Timeline",
|
||||||
|
"Disabled Menu": "Disabled Menu",
|
||||||
|
"Help Center": "Help Center",
|
||||||
|
"Verify Email": "Verify Email",
|
||||||
|
"Verify Email v1": "Verify Email v1",
|
||||||
|
"Verify Email v2": "Verify Email v2",
|
||||||
|
"Two Steps": "Two Steps",
|
||||||
|
"Two Steps v1": "Two Steps v1",
|
||||||
|
"Two Steps v2": "Two Steps v2",
|
||||||
|
"Custom Input": "Custom Input",
|
||||||
|
"Extensions": "Extensions",
|
||||||
|
"Tour": "Tour",
|
||||||
|
"Register Multi-Steps": "Register Multi-Steps",
|
||||||
|
"Wizard Examples": "Wizard Examples",
|
||||||
|
"Checkout": "Checkout",
|
||||||
|
"Create Deal": "Create Deal",
|
||||||
|
"Property Listing": "Property Listing",
|
||||||
|
"Roles & Permissions": "Roles & Permissions",
|
||||||
|
"Roles": "Roles",
|
||||||
|
"Simple Table": "Simple Table",
|
||||||
|
"Tables": "Tables",
|
||||||
|
"Data Table": "Data Table",
|
||||||
|
"Permissions": "Permissions",
|
||||||
|
"Apps": "Apps",
|
||||||
|
"Misc": "Misc",
|
||||||
|
"Wizard Pages": "Wizard Pages",
|
||||||
|
"Form Wizard": "Form Wizard",
|
||||||
|
"Numbered": "Numbered",
|
||||||
|
"3": "3",
|
||||||
|
"ecommerce": "ecommerce",
|
||||||
|
"Ecommerce": "Ecommerce",
|
||||||
|
"Editors": "Editors",
|
||||||
|
"Front Pages": "Front Pages",
|
||||||
|
"Landing": "Landing",
|
||||||
|
"checkout": "checkout",
|
||||||
|
"Payment": "Payment",
|
||||||
|
"Swiper": "Swiper",
|
||||||
|
"Product": "Product",
|
||||||
|
"Category": "Category",
|
||||||
|
"Order": "Order",
|
||||||
|
"Details": "Details",
|
||||||
|
"Customer": "Customer",
|
||||||
|
"Manage Review": "Manage Review",
|
||||||
|
"Referrals": "Referrals",
|
||||||
|
"Settings": "Settings",
|
||||||
|
"Overview": "Overview",
|
||||||
|
"My Course": "My Course",
|
||||||
|
"Course Details": "Course Details",
|
||||||
|
"Academy": "Academy",
|
||||||
|
"Logistics": "Logistics",
|
||||||
|
"Dashboard": "Dashboard",
|
||||||
|
"Fleet": "Fleet",
|
||||||
|
"5": "5",
|
||||||
|
"10": "10",
|
||||||
|
"20": "20",
|
||||||
|
"25": "25",
|
||||||
|
"50": "50",
|
||||||
|
"100": "100",
|
||||||
|
"$vuetify": {
|
||||||
|
"badge": "Badge",
|
||||||
|
"noDataText": "No data available",
|
||||||
|
"close": "Close",
|
||||||
|
"open": "open",
|
||||||
|
"carousel": {
|
||||||
|
"ariaLabel": {
|
||||||
|
"delimiter": "delimiter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dataFooter": {
|
||||||
|
"itemsPerPageText": "Items per page:",
|
||||||
|
"itemsPerPageAll": "All",
|
||||||
|
"pageText": "{0}-{1} of {2}",
|
||||||
|
"firstPage": "First Page",
|
||||||
|
"prevPage": "Previous Page",
|
||||||
|
"nextPage": "Next Page",
|
||||||
|
"lastPage": "Last Page"
|
||||||
|
},
|
||||||
|
"pagination": {
|
||||||
|
"ariaLabel": {
|
||||||
|
"root": "root",
|
||||||
|
"previous": "previous",
|
||||||
|
"next": "next",
|
||||||
|
"currentPage": "currentPage",
|
||||||
|
"page": "page"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"input": {
|
||||||
|
"clear": "clear",
|
||||||
|
"appendAction": "appendAction",
|
||||||
|
"prependAction": "prependAction",
|
||||||
|
"counterSize": "counterSize",
|
||||||
|
"otp": "otp"
|
||||||
|
},
|
||||||
|
"fileInput": {
|
||||||
|
"counterSize": "counterSize"
|
||||||
|
},
|
||||||
|
"rating": {
|
||||||
|
"ariaLabel": {
|
||||||
|
"item": "item"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,283 @@
|
||||||
|
{
|
||||||
|
"Home": "Accueil",
|
||||||
|
"Store": "Boutique",
|
||||||
|
"Stores": "Boutiques",
|
||||||
|
"Flow": "Flux",
|
||||||
|
"OBI": "OBI",
|
||||||
|
"Dotsoft": "Dotsoft",
|
||||||
|
"List of stores": "Liste des boutiques",
|
||||||
|
"Store visualization": "Visualisation boutique",
|
||||||
|
"BL not sent list": "Liste BL non envoyés",
|
||||||
|
"BL not sent": "BL non envoyés",
|
||||||
|
"ST ID": "ID ST",
|
||||||
|
"Exp ID": "ID Exp",
|
||||||
|
"BL ID": "ID BL",
|
||||||
|
"BL Date": "Date BL",
|
||||||
|
"External Code": "Code externe",
|
||||||
|
"Notes": "Notes",
|
||||||
|
"Expeditor": "Expéditeur",
|
||||||
|
"Distributor": "Distributeur",
|
||||||
|
"Distributors": "Distributeurs",
|
||||||
|
"Season": "Saison",
|
||||||
|
"Country": "Pays",
|
||||||
|
"Brand": "Marque",
|
||||||
|
"R-C-S": "R-C-T",
|
||||||
|
"Item ID": "ID produit",
|
||||||
|
"Chain": "Enseigne",
|
||||||
|
"Multi POS": "Multi POS",
|
||||||
|
"Search": "Chercher",
|
||||||
|
"Name": "Nom",
|
||||||
|
"Phone": "Téléphone",
|
||||||
|
"Mode": "Mode",
|
||||||
|
"Value": "Valeur",
|
||||||
|
"Item": "Article",
|
||||||
|
"Items": "Articles",
|
||||||
|
"Remote": "Accès distant",
|
||||||
|
"Option": "Option",
|
||||||
|
"Stock": "Stock",
|
||||||
|
"Price": "Prix",
|
||||||
|
"ITEM_ID": "ITEM ID",
|
||||||
|
"PRICE": "PRIX",
|
||||||
|
"TYPE": "TYPE",
|
||||||
|
"PARENT": "PARENT",
|
||||||
|
"LEVEL": "NIVEAU",
|
||||||
|
"Migration": "MIGRATION",
|
||||||
|
"EFFECTIVE DATE": "DATE EFFECTIVE",
|
||||||
|
"EXPIRATION DATE": "DATE EXPIRATION",
|
||||||
|
"DATE_CREATE": "DATE CREATION",
|
||||||
|
"USER_CREATE": "UTIL. CREATION",
|
||||||
|
"DATE_UPDATE": "DATE MAJ",
|
||||||
|
"USER_UPDATE": "UTIL. MAJ",
|
||||||
|
"Reload": "Rafraichir",
|
||||||
|
"WorkStation": "Caisse",
|
||||||
|
"SignatureString": "Chaine de Signature",
|
||||||
|
"SignatureSource": "Signature source",
|
||||||
|
"Submit": "Valider",
|
||||||
|
"You can search for a reference, reference-color or reference-color-size.": "Vous pouvez chercher une reference, reference-couleur ou reference-couleur-taille.",
|
||||||
|
"Minimum 5 characters long": "Minimum 5 caractères",
|
||||||
|
"Filters": "Filtres",
|
||||||
|
"XADMIN": "XADMIN",
|
||||||
|
"Logs": "Logs",
|
||||||
|
"Level": "Niveau",
|
||||||
|
"loggerCategory": "Categorie log",
|
||||||
|
"Thread": "Thread",
|
||||||
|
"Date": "Date",
|
||||||
|
"WSK": "Caisse",
|
||||||
|
"Select date": "Selectionner une date",
|
||||||
|
"ExcelData is empty, cannot export": "Aucune donnée, impossible d'exporter",
|
||||||
|
"Logs list": "Liste des logs",
|
||||||
|
"Business Date": "Date ouverture",
|
||||||
|
"Dates range error": "Dans le cas d'une plage de dates, un texte d'au moins 5 caractères doit être spécifié.",
|
||||||
|
"Close": "Fermer",
|
||||||
|
"Connect to invidual POS": "Connexion à une caisse individuelle",
|
||||||
|
"---------------------------": "---------------------------",
|
||||||
|
"UI Elements": "ÉLÉMENTS DE L'UI",
|
||||||
|
"Forms & Tables": "Formulaires et tableaux",
|
||||||
|
"Pages": "Des pages",
|
||||||
|
"Charts & Maps": "Graphiques et cartes",
|
||||||
|
"Others": "Autres",
|
||||||
|
"Typography": "Typographie",
|
||||||
|
"Cards": "Cartes",
|
||||||
|
"Basic": "De base",
|
||||||
|
"Advance": "Avance",
|
||||||
|
"Widgets": "Widget",
|
||||||
|
"Card Action": "Action de la carte",
|
||||||
|
"Components": "Composants",
|
||||||
|
"Alert": "Alerte",
|
||||||
|
"Close Alert": "Fermer l'alerte",
|
||||||
|
"Avatar": "Avatar",
|
||||||
|
"Badge": "Badge",
|
||||||
|
"Button": "Bouton",
|
||||||
|
"Calendar": "Calendrier",
|
||||||
|
"Image": "Image",
|
||||||
|
"Pagination": "Pagination",
|
||||||
|
"Progress Circular": "Progrès circulaire",
|
||||||
|
"Progress Linear": "Progrès Linéaire",
|
||||||
|
"Autocomplete": "Saisie automatique",
|
||||||
|
"Tooltip": "Info-bulle",
|
||||||
|
"Slider": "Glissière",
|
||||||
|
"Date Time Picker": "Sélecteur de date et d'heure",
|
||||||
|
"Select": "Sélectionner",
|
||||||
|
"Switch": "Commutateur",
|
||||||
|
"Checkbox": "Case à cocher",
|
||||||
|
"Radio": "Radio",
|
||||||
|
"Textarea": "Textarea",
|
||||||
|
"Rating": "Évaluation",
|
||||||
|
"File Input": "Entrée de fichier",
|
||||||
|
"Otp Input": "Entrée Otp",
|
||||||
|
"Form Layout": "Disposition du formulaire",
|
||||||
|
"Form Validation": "Validation de formulaire",
|
||||||
|
"Charts": "Graphiques",
|
||||||
|
"Apex Chart": "Graphique Apex",
|
||||||
|
"Chartjs": "Chartjs",
|
||||||
|
"Account Settings": "Paramètres du compte",
|
||||||
|
"User Profile": "Profil de l'utilisateur",
|
||||||
|
"FAQ": "FAQ",
|
||||||
|
"Dialog Examples": "Exemples de dialogue",
|
||||||
|
"Pricing": "Tarification",
|
||||||
|
"List": "liste",
|
||||||
|
"Edit": "Éditer",
|
||||||
|
"Nav Levels": "Niveaux de navigation",
|
||||||
|
"Level 2.1": "Niveau 2.1",
|
||||||
|
"Level 2.2": "Niveau 2.2",
|
||||||
|
"Level 3.1": "Niveau 3.1",
|
||||||
|
"Level 3.2": "Niveau 3.2",
|
||||||
|
"Raise Support": "Augmenter le soutien",
|
||||||
|
"Documentation": "Documentation",
|
||||||
|
"Dashboards": "Tableaux de bord",
|
||||||
|
"Analytics": "Analytique",
|
||||||
|
"Apps & Pages": "Applications et pages",
|
||||||
|
"Email": "Email",
|
||||||
|
"Chat": "Bavarder",
|
||||||
|
"Invoice": "Facture d'achat",
|
||||||
|
"Preview": "Aperçu",
|
||||||
|
"Add": "Ajouter",
|
||||||
|
"User": "Utilisateur",
|
||||||
|
"View": "Vue",
|
||||||
|
"Login v1": "Connexion v1",
|
||||||
|
"Login v2": "Connexion v2",
|
||||||
|
"Login": "Connexion",
|
||||||
|
"Register v1": "S'inscrire v1",
|
||||||
|
"Register v2": "S'inscrire v2",
|
||||||
|
"Register": "S'inscrire",
|
||||||
|
"Forget Password v1": "Oubliez le mot de passe v1",
|
||||||
|
"Forget Password v2": "Oubliez le mot de passe v2",
|
||||||
|
"Forgot Password v1": "Oubliez le mot de passe v1",
|
||||||
|
"Forgot Password v2": "Oubliez le mot de passe v2",
|
||||||
|
"Forgot Password": "Oubliez le mot de passe",
|
||||||
|
"Reset Password v1": "Réinitialiser le mot de passe v1",
|
||||||
|
"Reset Password v2": "Réinitialiser le mot de passe v2",
|
||||||
|
"Reset Password": "Réinitialiser le mot de passe",
|
||||||
|
"Miscellaneous": "Divers",
|
||||||
|
"Coming Soon": "Bientôt disponible",
|
||||||
|
"Not Authorized": "Pas autorisé",
|
||||||
|
"Under Maintenance": "En maintenance",
|
||||||
|
"Error": "Erreur",
|
||||||
|
"Errors": "Erreurs",
|
||||||
|
"Statistics": "Statistiques",
|
||||||
|
"Card Actions": "Actions de la carte",
|
||||||
|
"Actions": "Actions",
|
||||||
|
"Access Control": "Contrôle d'accès",
|
||||||
|
"User Interface": "Interface utilisateur",
|
||||||
|
"CRM": "CRM",
|
||||||
|
"eCommerce": "commerce électronique",
|
||||||
|
"Icons": "Icône",
|
||||||
|
"Chip": "Ébrécher",
|
||||||
|
"Dialog": "Dialogue",
|
||||||
|
"Expansion Panel": "Panneau d'extension",
|
||||||
|
"Combobox": "Boîte combo",
|
||||||
|
"Textfield": "Champ de texte",
|
||||||
|
"Range Slider": "Curseur Gamme",
|
||||||
|
"Menu": "Menu",
|
||||||
|
"Snackbar": "Casse-croûte",
|
||||||
|
"Tabs": "Onglets",
|
||||||
|
"Form Elements": "Éléments de formulaire",
|
||||||
|
"Form Layouts": "Dispositions de formulaire",
|
||||||
|
"Authentication": "Authentification",
|
||||||
|
"Page Not Found - 404": "Page introuvable - 404",
|
||||||
|
"Not Authorized - 401": "Non autorisé - 401",
|
||||||
|
"Server Error - 500": "Erreur de serveur - 500",
|
||||||
|
"2": "2",
|
||||||
|
"Forms": "Formes",
|
||||||
|
"Timeline": "Chronologie",
|
||||||
|
"Disabled Menu": "Menu désactivé",
|
||||||
|
"Help Center": "Centre d'aide",
|
||||||
|
"Verify Email": "Vérifier les courriels",
|
||||||
|
"Verify Email v1": "Vérifier l'e-mail v1",
|
||||||
|
"Verify Email v2": "Vérifier l'e-mail v2",
|
||||||
|
"Two Steps": "Deux étapes",
|
||||||
|
"Two Steps v1": "Deux étapes v1",
|
||||||
|
"Two Steps v2": "Deux étapes v2",
|
||||||
|
"Custom Input": "Entrée personnalisée",
|
||||||
|
"Extensions": "Rallonges",
|
||||||
|
"Tour": "Tour",
|
||||||
|
"Register Multi-Steps": "Enregistrer plusieurs étapes",
|
||||||
|
"Wizard Examples": "Exemples de guide",
|
||||||
|
"Checkout": "Check-out",
|
||||||
|
"Create Deal": "Créer une offre",
|
||||||
|
"Property Listing": "Liste des propriétés",
|
||||||
|
"Roles & Permissions": "Rôles et autorisations",
|
||||||
|
"Roles": "Rôles",
|
||||||
|
"Permissions": "Autorisations",
|
||||||
|
"Simple Table": "Table simple",
|
||||||
|
"Tables": "Tables",
|
||||||
|
"Data Table": "Table de données",
|
||||||
|
"Apps": "Applications",
|
||||||
|
"Misc": "Divers",
|
||||||
|
"Wizard Pages": "Pages de l'assistant",
|
||||||
|
"Form Wizard": "Assistant de formulaire",
|
||||||
|
"Numbered": "Numéroté",
|
||||||
|
"3": "3",
|
||||||
|
"ecommerce": "commerce électronique",
|
||||||
|
"Ecommerce": "Commerce électronique",
|
||||||
|
"Product": "Produit",
|
||||||
|
"Category": "Catégorie",
|
||||||
|
"Order": "Ordre",
|
||||||
|
"Details": "Détails",
|
||||||
|
"Customer": "Client",
|
||||||
|
"Manage Review": "Gérer la revue",
|
||||||
|
"Referrals": "Références",
|
||||||
|
"Settings": "Paramètres",
|
||||||
|
"Course Details": "Détails du cours",
|
||||||
|
"My Course": "Mon cours",
|
||||||
|
"Overview": "Aperçu",
|
||||||
|
"Academy": "Académie",
|
||||||
|
"Logistics": "Logistique",
|
||||||
|
"Dashboard": "Tableau de bord",
|
||||||
|
"Fleet": "Flotte",
|
||||||
|
"Editors": "Éditeurs",
|
||||||
|
"Front Pages": "Pages frontales",
|
||||||
|
"Landing": "d'atterrissage",
|
||||||
|
"checkout": "Check-out",
|
||||||
|
"Payment": "Paiement",
|
||||||
|
"Swiper": "Swiper",
|
||||||
|
"5": "5",
|
||||||
|
"10": "10",
|
||||||
|
"20": "20",
|
||||||
|
"25": "25",
|
||||||
|
"50": "50",
|
||||||
|
"100": "100",
|
||||||
|
"$vuetify": {
|
||||||
|
"badge": "Badge",
|
||||||
|
"noDataText": "Pas de données disponibles",
|
||||||
|
"close": "Fermer",
|
||||||
|
"open": "Ouvert",
|
||||||
|
"carousel": {
|
||||||
|
"ariaLabel": {
|
||||||
|
"delimiter": "délimiteur"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dataFooter": {
|
||||||
|
"itemsPerPageText": "Objets par page:",
|
||||||
|
"itemsPerPageAll": "Tout",
|
||||||
|
"pageText": "{0}-{1} of {2}",
|
||||||
|
"firstPage": "Première page",
|
||||||
|
"prevPage": "Page précédente",
|
||||||
|
"nextPage": "Page suivante",
|
||||||
|
"lastPage": "Dernière page"
|
||||||
|
},
|
||||||
|
"pagination": {
|
||||||
|
"ariaLabel": {
|
||||||
|
"root": "racine",
|
||||||
|
"previous": "précédente",
|
||||||
|
"next": "suivante",
|
||||||
|
"currentPage": "page actuelle",
|
||||||
|
"page": "page"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"input": {
|
||||||
|
"clear": "dégager",
|
||||||
|
"appendAction": "ajouter une action",
|
||||||
|
"prependAction": "préfixer l'action",
|
||||||
|
"otp": "otp"
|
||||||
|
},
|
||||||
|
"fileInput": {
|
||||||
|
"counterSize": "Taille du compteur"
|
||||||
|
},
|
||||||
|
"rating": {
|
||||||
|
"ariaLabel": {
|
||||||
|
"item": "Objet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
/**
|
||||||
|
* global type definitions
|
||||||
|
* using the typescript interface, you can define the i18n resources that is type-safed!
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* you need to import the some interfaces
|
||||||
|
*/
|
||||||
|
import en from '@/plugins/i18n/locales/en.json';
|
||||||
|
import 'vue-i18n';
|
||||||
|
|
||||||
|
type LocaleMessage = typeof en
|
||||||
|
|
||||||
|
declare module 'vue-i18n' {
|
||||||
|
export interface DefineLocaleMessage extends LocaleMessage {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import type { SortItem } from '@/@core/types'
|
||||||
|
|
||||||
|
export const useDataTableStore = defineStore({
|
||||||
|
id: 'dataTable',
|
||||||
|
state: () => ({
|
||||||
|
filters: {} as Record<string, string>,
|
||||||
|
currentPage: 1,
|
||||||
|
itemsPerPage: 10,
|
||||||
|
searchText: '',
|
||||||
|
sortBy: '',
|
||||||
|
sortOrder: false,
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
clearState() {
|
||||||
|
this.filters = {} as Record<string, string>
|
||||||
|
this.currentPage = 1
|
||||||
|
this.itemsPerPage = 10
|
||||||
|
this.searchText = ''
|
||||||
|
this.sortBy = ''
|
||||||
|
this.sortOrder = false
|
||||||
|
},
|
||||||
|
setFilters(filterName: string, value: any) {
|
||||||
|
this.filters[filterName] = value
|
||||||
|
},
|
||||||
|
setCurrentPage(page: number) {
|
||||||
|
this.currentPage = page
|
||||||
|
},
|
||||||
|
setItemsPerPage(value: number) {
|
||||||
|
this.itemsPerPage = value
|
||||||
|
},
|
||||||
|
setSearchText(value: string) {
|
||||||
|
this.searchText = value
|
||||||
|
},
|
||||||
|
setSortBy(value: string) {
|
||||||
|
this.sortBy = value
|
||||||
|
},
|
||||||
|
setSortOrder(value: string) {
|
||||||
|
if (value === 'asc')
|
||||||
|
this.sortOrder = true
|
||||||
|
else
|
||||||
|
this.sortOrder = false
|
||||||
|
},
|
||||||
|
getSortBy(): SortItem[] {
|
||||||
|
return [{
|
||||||
|
key: this.sortBy,
|
||||||
|
order: this.sortOrder ? 'asc' : 'desc',
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
interface UserState {
|
||||||
|
username: string | null
|
||||||
|
role: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const userStore = defineStore('user', {
|
||||||
|
state: (): UserState => ({
|
||||||
|
username: null,
|
||||||
|
role: null,
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
async login(username: string, password: string, ability: any) {
|
||||||
|
try {
|
||||||
|
const res = await $api(`${import.meta.env.VITE_API_BASE_URL}/auth/login`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { accessToken, userData, userAbilityRules: { rules } } = res
|
||||||
|
|
||||||
|
this.username = username
|
||||||
|
this.role = password === 'admin123' ? 'Admin' : 'Support'
|
||||||
|
|
||||||
|
useCookie('accessToken').value = accessToken
|
||||||
|
useCookie('userData').value = userData
|
||||||
|
useCookie('userAbilityRules').value = rules
|
||||||
|
ability.update(rules)
|
||||||
|
}
|
||||||
|
catch (err: any) {
|
||||||
|
console.log(err.message)
|
||||||
|
throw err // Rethrow the error so it can be caught in the component
|
||||||
|
}
|
||||||
|
},
|
||||||
|
logout() {
|
||||||
|
this.username = null
|
||||||
|
this.role = null
|
||||||
|
|
||||||
|
// Effacer les cookies
|
||||||
|
useCookie('accessToken').value = null
|
||||||
|
useCookie('userData').value = null
|
||||||
|
useCookie('userAbilityRules').value = null
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
import { ofetch } from 'ofetch'
|
import { ofetch } from 'ofetch'
|
||||||
|
|
||||||
export const $api = ofetch.create({
|
export const $api = ofetch.create({
|
||||||
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
|
baseURL: import.meta.env.VITE_API_BASE_URL,
|
||||||
async onRequest({ options }) {
|
async onRequest({ options }) {
|
||||||
const accessToken = useCookie('accessToken').value
|
const accessToken = useCookie('accessToken').value
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
options.headers = {
|
options.headers = {
|
||||||
...options.headers,
|
...options.headers,
|
||||||
Authorization: `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
|
||||||
|
// 'Cache-Control': 'no-cache',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { useTheme } from 'vuetify'
|
|
||||||
|
|
||||||
const { global } = useTheme()
|
|
||||||
|
|
||||||
const authProviders = [
|
|
||||||
{
|
|
||||||
icon: 'fa-facebook',
|
|
||||||
color: '#4267b2',
|
|
||||||
colorInDark: '#4267b2',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'fa-google',
|
|
||||||
color: '#dd4b39',
|
|
||||||
colorInDark: '#db4437',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'fa-twitter',
|
|
||||||
color: '#1da1f2',
|
|
||||||
colorInDark: '#1da1f2',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="d-flex justify-center flex-wrap gap-3">
|
|
||||||
<VBtn
|
|
||||||
v-for="link in authProviders"
|
|
||||||
:key="link.icon"
|
|
||||||
icon
|
|
||||||
variant="tonal"
|
|
||||||
size="38"
|
|
||||||
:color="global.name.value === 'dark' ? link.colorInDark : link.color"
|
|
||||||
class="rounded"
|
|
||||||
>
|
|
||||||
<VIcon
|
|
||||||
size="18"
|
|
||||||
:icon="link.icon"
|
|
||||||
/>
|
|
||||||
</VBtn>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { StoreData } from '@/models/storeData'
|
||||||
|
import UserProfileHeaderBg from '@images/pages/user-profile-header-bg.png'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
storeData: StoreData
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VCard v-if="props">
|
||||||
|
<VImg
|
||||||
|
:src="UserProfileHeaderBg"
|
||||||
|
min-height="80"
|
||||||
|
max-height="120"
|
||||||
|
cover
|
||||||
|
/>
|
||||||
|
<VCardText class="d-flex align-bottom flex-sm-row flex-column justify-center gap-x-5">
|
||||||
|
<div class="d-flex h-0">
|
||||||
|
<VAvatar
|
||||||
|
rounded
|
||||||
|
size="120"
|
||||||
|
:image="props.storeData.store.photoLink"
|
||||||
|
class="user-profile-avatar mx-auto"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="user-profile-info w-100 mt-16 pt-6 pt-sm-0 mt-sm-0">
|
||||||
|
<div class="d-flex align-center justify-center justify-sm-space-between flex-wrap gap-4 mb-3">
|
||||||
|
<div class="d-flex flex-wrap justify-center justify-sm-start flex-grow-1 gap-4">
|
||||||
|
<span class="d-flex">
|
||||||
|
<span class="text-h5">
|
||||||
|
{{ props.storeData.store.id_structure }} - {{ props.storeData.store.nom }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-center gap-4">
|
||||||
|
<div class="d-flex flex-column">
|
||||||
|
<span class="text-h6 font-weight-medium">{{ props.storeData.store.date_migration }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex align-center justify-center justify-sm-space-between flex-wrap gap-4">
|
||||||
|
<div class="d-flex flex-wrap justify-center justify-sm-start flex-grow-1 gap-4">
|
||||||
|
<span class="d-flex">
|
||||||
|
<VIcon
|
||||||
|
size="20"
|
||||||
|
icon="tabler-color-swatch"
|
||||||
|
class="me-1"
|
||||||
|
/>
|
||||||
|
<span class="text-body-1">
|
||||||
|
{{ props.storeData.store.enseigne }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="d-flex">
|
||||||
|
<VIcon
|
||||||
|
size="20"
|
||||||
|
icon="tabler-phone-call"
|
||||||
|
class="me-1"
|
||||||
|
/>
|
||||||
|
<span class="text-body-1">
|
||||||
|
{{ props.storeData.store.telephone }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="d-flex">
|
||||||
|
<VIcon
|
||||||
|
size="20"
|
||||||
|
icon="tabler-map-pin"
|
||||||
|
class="me-1"
|
||||||
|
/>
|
||||||
|
<span class="text-body-1">
|
||||||
|
{{ props.storeData.store.adresse }} - ({{ props.storeData.store.pays }})
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex align-center gap-4">
|
||||||
|
<VAvatar
|
||||||
|
color="primary"
|
||||||
|
variant="tonal"
|
||||||
|
size="42"
|
||||||
|
>
|
||||||
|
<VIcon icon="tabler-http-post" />
|
||||||
|
</VAvatar>
|
||||||
|
|
||||||
|
<div class="d-flex flex-column">
|
||||||
|
<span class="text-h5 font-weight-medium">{{ props.storeData.store.nbcaisses }}</span>
|
||||||
|
<span class="text-sm">
|
||||||
|
Caisses
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.user-profile-avatar {
|
||||||
|
border: 5px solid rgb(var(--v-theme-surface));
|
||||||
|
background-color: rgb(var(--v-theme-surface)) !important;
|
||||||
|
inset-block-start: -3rem;
|
||||||
|
|
||||||
|
.v-img__img {
|
||||||
|
border-radius: 0.125rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { StoreData } from '@/models/storeData'
|
||||||
|
import StoreTabAdminH from '@/views/pages/store/view/StoreTabAdminH.vue'
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
storeData: StoreData
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<VRow>
|
||||||
|
<VCol cols="12">
|
||||||
|
<StoreTabAdminH :store-data="storeData" />
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { StoreData } from '@/models/storeData'
|
||||||
|
import StoreTabAdminHsequence from '@/views/pages/store/view/StoreTabAdminHsequence.vue'
|
||||||
|
import StoreTabAdminHsignature from '@/views/pages/store/view/StoreTabAdminHsignature.vue'
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
storeData: StoreData
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTab = ref('tab-1')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VCard>
|
||||||
|
<VTabs
|
||||||
|
v-model="currentTab"
|
||||||
|
grow
|
||||||
|
stacked
|
||||||
|
>
|
||||||
|
<VTab>
|
||||||
|
<VIcon
|
||||||
|
start
|
||||||
|
icon="tabler-123"
|
||||||
|
/>
|
||||||
|
Sequence
|
||||||
|
</VTab>
|
||||||
|
|
||||||
|
<VTab>
|
||||||
|
<VIcon
|
||||||
|
start
|
||||||
|
icon="tabler-certificate"
|
||||||
|
/>
|
||||||
|
Signature
|
||||||
|
</VTab>
|
||||||
|
</VTabs>
|
||||||
|
|
||||||
|
<VCardText style="padding: 5px !important;">
|
||||||
|
<VWindow
|
||||||
|
v-model="currentTab"
|
||||||
|
class="ms-3"
|
||||||
|
>
|
||||||
|
<VWindowItem value="tab-1">
|
||||||
|
<StoreTabAdminHsequence :store-data="storeData" />
|
||||||
|
</VWindowItem>
|
||||||
|
<VWindowItem value="tab-2">
|
||||||
|
<StoreTabAdminHsignature :store-data="storeData" />
|
||||||
|
</VWindowItem>
|
||||||
|
</VWindow>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { VDataTable } from 'vuetify/labs/VDataTable'
|
||||||
|
import type { StoreData } from '@/models/storeData'
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const route = useRoute('store-details')
|
||||||
|
|
||||||
|
const headers = computed(() => [
|
||||||
|
{ title: 'WSK', key: 'wkstnId' },
|
||||||
|
{ title: t('Name'), key: 'sequenceId' },
|
||||||
|
{ title: t('Mode'), key: 'sequenceMode' },
|
||||||
|
{ title: t('Value'), key: 'sequenceNbr' },
|
||||||
|
{ title: t('DATE_CREATE'), key: 'createDate' },
|
||||||
|
{ title: t('USER_CREATE'), key: 'createUserId' },
|
||||||
|
{ title: t('DATE_UPDATE'), key: 'updateDate' },
|
||||||
|
{ title: t('USER_UPDATE'), key: 'updateUserId' },
|
||||||
|
])
|
||||||
|
|
||||||
|
const selectedWkStn = ref()
|
||||||
|
const selectedSequenceMode = ref()
|
||||||
|
const searchQuery = ref('')
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
storeData: StoreData
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data table options
|
||||||
|
const options = ref({ page: 1, itemsPerPage: 10, sortBy: [''], sortDesc: [false] })
|
||||||
|
const isLoading = ref(false)
|
||||||
|
|
||||||
|
const data = ref([]) // Initialisez data comme un tableau vide
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
const response = await useApi<any>(createUrl(`/stores/${props.storeData.store.id_structure}/sequence?dbHost=${route.query.dbHost}`))
|
||||||
|
|
||||||
|
data.value = response.data.value
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const wkstn = computed(() => {
|
||||||
|
const allWkstns = data.value.map((store: { wkstnId: any }) => store.wkstnId)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
const uniqueWkstns = allWkstns.filter((wkstn: any, index: any, self: string | any[]) => self.indexOf(wkstn) === index)
|
||||||
|
const sortedWkstns = uniqueWkstns.sort()
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
return sortedWkstns.map((wkstn: any) => ({ title: wkstn, value: wkstn }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const seqmode = computed(() => {
|
||||||
|
const allSeqModes = data.value.map((store: { sequenceMode: any }) => store.sequenceMode)
|
||||||
|
|
||||||
|
const uniqueSeqModes = allSeqModes.filter((seqmod: any, index: any, self: string | any[]) => self.indexOf(seqmod) === index)
|
||||||
|
const sortedSeqModes = uniqueSeqModes.sort()
|
||||||
|
|
||||||
|
return sortedSeqModes.map((seqmod: any) => ({ title: seqmod, value: seqmod }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const filteredSequenceList = computed(() => {
|
||||||
|
let filtered = data.value
|
||||||
|
|
||||||
|
// If a workstation is selected, filter the records for this number
|
||||||
|
if (selectedWkStn.value !== undefined && selectedWkStn.value !== null)
|
||||||
|
filtered = filtered.filter((store: { wkstnId: any }) => store.wkstnId === selectedWkStn.value)
|
||||||
|
|
||||||
|
// If a sequence mode is selected, filter the records for this mode
|
||||||
|
if (selectedSequenceMode.value !== undefined && selectedSequenceMode.value !== null)
|
||||||
|
filtered = filtered.filter((store: { sequenceMode: any }) => store.sequenceMode === selectedSequenceMode.value)
|
||||||
|
|
||||||
|
// If a search query is provided, filter the records for this query
|
||||||
|
if (searchQuery.value) {
|
||||||
|
filtered = filtered.filter((store: { [s: string]: unknown } | ArrayLike<unknown>) =>
|
||||||
|
Object.values(store).some(value =>
|
||||||
|
String(value).toLowerCase().includes(searchQuery.value.toLowerCase()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<!-- 👉 stores -->
|
||||||
|
<VCard class="mb-6">
|
||||||
|
<VCardText>
|
||||||
|
<VRow>
|
||||||
|
<!-- 👉 Select country -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="4"
|
||||||
|
>
|
||||||
|
<AppSelect
|
||||||
|
v-model="selectedWkStn"
|
||||||
|
:placeholder="$t('WorkStation')"
|
||||||
|
:items="wkstn"
|
||||||
|
clearable
|
||||||
|
clear-icon="tabler-x"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<!-- 👉 Select Brand -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="4"
|
||||||
|
>
|
||||||
|
<AppSelect
|
||||||
|
v-model="selectedSequenceMode"
|
||||||
|
:placeholder="$t('Mode')"
|
||||||
|
:items="seqmode"
|
||||||
|
clearable
|
||||||
|
clear-icon="tabler-x"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
</VCardText>
|
||||||
|
|
||||||
|
<VDivider class="my-4" />
|
||||||
|
|
||||||
|
<div class="d-flex flex-wrap gap-4 mx-5">
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<!-- 👉 Search -->
|
||||||
|
<AppTextField
|
||||||
|
v-model="searchQuery"
|
||||||
|
:placeholder="$t('Search')"
|
||||||
|
density="compact"
|
||||||
|
style="inline-size: 200px;"
|
||||||
|
class="me-3"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<VSpacer />
|
||||||
|
<div class="d-flex gap-4 flex-wrap align-center">
|
||||||
|
<VBtn
|
||||||
|
color="primary"
|
||||||
|
prepend-icon="tabler-reload"
|
||||||
|
@click="fetchData"
|
||||||
|
>
|
||||||
|
{{ $t('Reload') }}
|
||||||
|
</VBtn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<VDivider class="mt-4" />
|
||||||
|
|
||||||
|
<VCol cols="12">
|
||||||
|
<div v-if="isLoading">
|
||||||
|
<VProgressCircular
|
||||||
|
indeterminate
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- 👉 Datatable -->
|
||||||
|
<VDataTable
|
||||||
|
v-else
|
||||||
|
:headers="headers"
|
||||||
|
:items="filteredSequenceList"
|
||||||
|
:items-per-page="options.itemsPerPage"
|
||||||
|
:page="options.page"
|
||||||
|
:options="options"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
</VCard>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { VDataTable } from 'vuetify/labs/VDataTable'
|
||||||
|
import type { StoreData } from '@/models/storeData'
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const route = useRoute('store-details')
|
||||||
|
|
||||||
|
const headers = computed(() => [
|
||||||
|
{ title: 'WSK', key: 'wkstnId' },
|
||||||
|
{ title: t('Name'), key: 'signatureId' },
|
||||||
|
{ title: t('Mode'), key: 'signatureMode' },
|
||||||
|
{ title: t('SignatureString'), key: 'signatureString' },
|
||||||
|
{ title: t('SignatureSource'), key: 'signatureSource' },
|
||||||
|
{ title: t('DATE_CREATE'), key: 'createDate' },
|
||||||
|
{ title: t('USER_CREATE'), key: 'createUserId' },
|
||||||
|
{ title: t('DATE_UPDATE'), key: 'updateDate' },
|
||||||
|
{ title: t('USER_UPDATE'), key: 'updateUserId' },
|
||||||
|
])
|
||||||
|
|
||||||
|
const selectedWkStn = ref()
|
||||||
|
const selectedSignatureMode = ref()
|
||||||
|
const searchQuery = ref('')
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
storeData: StoreData
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data table options
|
||||||
|
const options = ref({ page: 1, itemsPerPage: 10, sortBy: [''], sortDesc: [false] })
|
||||||
|
const isLoading = ref(false)
|
||||||
|
|
||||||
|
const data = ref([]) // Initialisez data comme un tableau vide
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
const response = await useApi<any>(createUrl(`/stores/${props.storeData.store.id_structure}/signature?dbHost=${route.query.dbHost}`))
|
||||||
|
|
||||||
|
data.value = response.data.value
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const wkstn = computed(() => {
|
||||||
|
const allWkstns = data.value.map((store: { wkstnId: any }) => store.wkstnId)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
const uniqueWkstns = allWkstns.filter((wkstn: any, index: any, self: string | any[]) => self.indexOf(wkstn) === index)
|
||||||
|
const sortedWkstns = uniqueWkstns.sort()
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
return sortedWkstns.map((wkstn: any) => ({ title: wkstn, value: wkstn }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const signmode = computed(() => {
|
||||||
|
const allSignModes = data.value.map((store: { signMode: any }) => store.signMode)
|
||||||
|
|
||||||
|
const uniqueSignMoeds = allSignModes.filter((signmod: any, index: any, self: string | any[]) => self.indexOf(signmod) === index)
|
||||||
|
const sortedSignMoeds = uniqueSignMoeds.sort()
|
||||||
|
|
||||||
|
return sortedSignMoeds.map((signmod: any) => ({ title: signmod, value: signmod }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const filteredSigantureList = computed(() => {
|
||||||
|
let filtered = data.value
|
||||||
|
|
||||||
|
// If a workstation is selected, filter the records for this number
|
||||||
|
if (selectedWkStn.value !== undefined && selectedWkStn.value !== null)
|
||||||
|
filtered = filtered.filter((store: { wkstnId: any }) => store.wkstnId === selectedWkStn.value)
|
||||||
|
|
||||||
|
// If a signature mode is selected, filter the records for this mode
|
||||||
|
if (selectedSignatureMode.value !== undefined && selectedSignatureMode.value !== null)
|
||||||
|
filtered = filtered.filter((store: { signMode: any }) => store.signMode === selectedSignatureMode.value)
|
||||||
|
|
||||||
|
// If a search query is provided, filter the records for this query
|
||||||
|
if (searchQuery.value) {
|
||||||
|
filtered = filtered.filter((store: { [s: string]: unknown } | ArrayLike<unknown>) =>
|
||||||
|
Object.values(store).some(value =>
|
||||||
|
String(value).toLowerCase().includes(searchQuery.value.toLowerCase()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<!-- 👉 stores -->
|
||||||
|
<VCard class="mb-6">
|
||||||
|
<VCardText>
|
||||||
|
<VRow>
|
||||||
|
<!-- 👉 Select country -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="4"
|
||||||
|
>
|
||||||
|
<AppSelect
|
||||||
|
v-model="selectedWkStn"
|
||||||
|
:placeholder="$t('WorkStation')"
|
||||||
|
:items="wkstn"
|
||||||
|
clearable
|
||||||
|
clear-icon="tabler-x"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<!-- 👉 Select Brand -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="4"
|
||||||
|
>
|
||||||
|
<AppSelect
|
||||||
|
v-model="selectedSignatureMode"
|
||||||
|
:placeholder="$t('Mode')"
|
||||||
|
:items="signmode"
|
||||||
|
clearable
|
||||||
|
clear-icon="tabler-x"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
</VCardText>
|
||||||
|
|
||||||
|
<VDivider class="my-4" />
|
||||||
|
|
||||||
|
<div class="d-flex flex-wrap gap-4 mx-5">
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<!-- 👉 Search -->
|
||||||
|
<AppTextField
|
||||||
|
v-model="searchQuery"
|
||||||
|
:placeholder="$t('Search')"
|
||||||
|
density="compact"
|
||||||
|
style="inline-size: 200px;"
|
||||||
|
class="me-3"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<VSpacer />
|
||||||
|
<div class="d-flex gap-4 flex-wrap align-center">
|
||||||
|
<VBtn
|
||||||
|
color="primary"
|
||||||
|
prepend-icon="tabler-reload"
|
||||||
|
@click="fetchData"
|
||||||
|
>
|
||||||
|
{{ $t('Reload') }}
|
||||||
|
</VBtn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<VDivider class="mt-4" />
|
||||||
|
|
||||||
|
<VCol cols="12">
|
||||||
|
<div v-if="isLoading">
|
||||||
|
<VProgressCircular
|
||||||
|
indeterminate
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- 👉 Datatable -->
|
||||||
|
<VDataTable
|
||||||
|
v-else
|
||||||
|
:headers="headers"
|
||||||
|
:items="filteredSigantureList"
|
||||||
|
:items-per-page="options.itemsPerPage"
|
||||||
|
:page="options.page"
|
||||||
|
:options="options"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
</VCard>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,231 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { StoreData } from '@/models/storeData'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
storeData: StoreData
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-for="(pos, index) in storeData.pos"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<VChip
|
||||||
|
label
|
||||||
|
size="small"
|
||||||
|
class="text-capitalize me-1"
|
||||||
|
>
|
||||||
|
Pos {{ pos.workstationId }}
|
||||||
|
</VChip>
|
||||||
|
<VChip
|
||||||
|
label
|
||||||
|
size="small"
|
||||||
|
class="text-capitalize"
|
||||||
|
>
|
||||||
|
{{ pos.ip }}
|
||||||
|
</VChip>
|
||||||
|
/
|
||||||
|
<VChip
|
||||||
|
label
|
||||||
|
size="small"
|
||||||
|
class="text-capitalize me-3"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
{{ pos.primaryRegister ? 'Primary' : 'Slave' }}
|
||||||
|
</VChip>
|
||||||
|
<VChip
|
||||||
|
v-if="pos.fatalError"
|
||||||
|
label
|
||||||
|
size="small"
|
||||||
|
class="text-capitalize"
|
||||||
|
color="error"
|
||||||
|
>
|
||||||
|
Fatal
|
||||||
|
</VChip>
|
||||||
|
<VRow class="py-3 mb-1">
|
||||||
|
<!-- 👉 Business date & opening hours -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
md="3"
|
||||||
|
:class="$vuetify.display.mdAndUp ? 'border-e' : 'border-b'"
|
||||||
|
>
|
||||||
|
<div class="ape-3">
|
||||||
|
<div class="d-flex justify-space-between flex-wrap gap-4 flex-column flex-xs-row">
|
||||||
|
<div>
|
||||||
|
<div class="d-flex mb-4">
|
||||||
|
<VAvatar
|
||||||
|
variant="tonal"
|
||||||
|
color="primary"
|
||||||
|
rounded
|
||||||
|
size="54"
|
||||||
|
class="text-primary me-4"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
icon="tabler-calendar-event"
|
||||||
|
size="38"
|
||||||
|
/>
|
||||||
|
</VAvatar>
|
||||||
|
<div>
|
||||||
|
<span class="text-base">Business date</span>
|
||||||
|
<h4 class="text-h4 font-weight-medium text-primary">
|
||||||
|
{{ pos.businessDateS }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex">
|
||||||
|
<VAvatar
|
||||||
|
variant="tonal"
|
||||||
|
color="info"
|
||||||
|
rounded
|
||||||
|
size="54"
|
||||||
|
class="text-primary me-4"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
icon="tabler-clock-hour-10"
|
||||||
|
size="38"
|
||||||
|
/>
|
||||||
|
</VAvatar>
|
||||||
|
<div>
|
||||||
|
<span class="text-base">Opening hours</span>
|
||||||
|
<h4 class="text-h6 font-weight-medium text-info">
|
||||||
|
{{ pos.openingDate }}{{ pos.closingDate ? ` ${pos.closingDate}` : '' }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<!-- 👉 Pending Replication -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
md="3"
|
||||||
|
:class="$vuetify.display.mdAndUp ? 'border-e' : 'border-b'"
|
||||||
|
>
|
||||||
|
<div class="d-flex justify-space-between align-center">
|
||||||
|
<div class="d-flex flex-column ps-3">
|
||||||
|
<h5 class="text-h5 text-high-emphasis mb-0 text-no-wrap">
|
||||||
|
Pending replication
|
||||||
|
</h5>
|
||||||
|
<div class="text-h3 mb-2">
|
||||||
|
{{ pos.replication.pendingReplications }}
|
||||||
|
</div>
|
||||||
|
<span class="mb-1">
|
||||||
|
<div>
|
||||||
|
<VChip
|
||||||
|
variant="tonal"
|
||||||
|
color="secondary"
|
||||||
|
style="inline-size:45px"
|
||||||
|
>
|
||||||
|
Min
|
||||||
|
</VChip>
|
||||||
|
{{ pos.replication.minPendingReplicationDate }}
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span class="mb-1">
|
||||||
|
<div>
|
||||||
|
<VChip
|
||||||
|
variant="tonal"
|
||||||
|
color="secondary"
|
||||||
|
style="inline-size:45px"
|
||||||
|
>
|
||||||
|
Max
|
||||||
|
</VChip>
|
||||||
|
{{ pos.replication.maxPendingReplicationDate }}
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<!-- 👉 XSTORE Ticket -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
md="3"
|
||||||
|
:class="$vuetify.display.mdAndUp ? 'border-e' : 'border-b'"
|
||||||
|
>
|
||||||
|
<div class="d-flex justify-space-between align-center">
|
||||||
|
<div class="d-flex flex-column ps-3">
|
||||||
|
<h5 class="text-h5 text-high-emphasis mb-0 text-no-wrap">
|
||||||
|
XSTORE Tickets
|
||||||
|
</h5>
|
||||||
|
<div class="text-h3 mb-2">
|
||||||
|
{{ pos.saleTransaction.count }}
|
||||||
|
</div>
|
||||||
|
<span class="mb-1">
|
||||||
|
<div>
|
||||||
|
<VChip
|
||||||
|
variant="tonal"
|
||||||
|
color="secondary"
|
||||||
|
style="inline-size:45px"
|
||||||
|
>
|
||||||
|
Min
|
||||||
|
</VChip>
|
||||||
|
{{ pos.saleTransaction.minDate }}
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span class="mb-1">
|
||||||
|
<div>
|
||||||
|
<VChip
|
||||||
|
variant="tonal"
|
||||||
|
color="secondary"
|
||||||
|
style="inline-size:45px"
|
||||||
|
>
|
||||||
|
Max
|
||||||
|
</VChip>
|
||||||
|
{{ pos.saleTransaction.maxDate }}
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<!-- 👉 DOTSOFT Tickets -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
md="3"
|
||||||
|
>
|
||||||
|
<div class="d-flex justify-space-between align-center">
|
||||||
|
<div class="d-flex flex-column ps-3">
|
||||||
|
<h5 class="text-h5 text-high-emphasis mb-0 text-no-wrap">
|
||||||
|
DOTSOFT Tickets
|
||||||
|
</h5>
|
||||||
|
<div class="text-h3 mb-2">
|
||||||
|
{{ pos.boTransaction.backOfficeTransactions }}
|
||||||
|
</div>
|
||||||
|
<span class="mb-1">
|
||||||
|
<div>
|
||||||
|
<VChip
|
||||||
|
variant="tonal"
|
||||||
|
color="secondary"
|
||||||
|
style="inline-size:45px"
|
||||||
|
>
|
||||||
|
Min
|
||||||
|
</VChip>
|
||||||
|
{{ pos.boTransaction.minBackOfficeTransactionDate }}
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span class="mb-1">
|
||||||
|
<div>
|
||||||
|
<VChip
|
||||||
|
variant="tonal"
|
||||||
|
color="secondary"
|
||||||
|
style="inline-size:45px"
|
||||||
|
>
|
||||||
|
Max
|
||||||
|
</VChip>
|
||||||
|
{{ pos.boTransaction.maxBackOfficeTransactionDate }}
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { provide, ref } from 'vue'
|
||||||
|
import { VForm } from 'vuetify/components/VForm'
|
||||||
|
import StoreTabItemH from '@/views/pages/store/view/StoreTabItemH.vue'
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const itemForm = ref('')
|
||||||
|
const injItemForm = ref('')
|
||||||
|
const refItemForm = ref<VForm>()
|
||||||
|
|
||||||
|
provide('item', injItemForm)
|
||||||
|
|
||||||
|
const validateItemForm = () => {
|
||||||
|
refItemForm.value?.validate().then(valid => {
|
||||||
|
if (valid.valid) {
|
||||||
|
console.log(itemForm.value)
|
||||||
|
injItemForm.value = itemForm.value
|
||||||
|
}
|
||||||
|
else { console.log(`KO:${itemForm.value}`) }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const lengthMinValidator = (value: unknown, length: number) => {
|
||||||
|
if (isEmpty(value))
|
||||||
|
return true
|
||||||
|
|
||||||
|
return String(value).length >= length || `The Min Character field must be at least ${length} characters`
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<VRow>
|
||||||
|
<VCol cols="12">
|
||||||
|
<!-- 👉 Item search -->
|
||||||
|
<VCard title="">
|
||||||
|
<VCardText>
|
||||||
|
<VAlert
|
||||||
|
variant="tonal"
|
||||||
|
color="warning"
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
|
<VAlertTitle class="mb-2">
|
||||||
|
{{ $t('You can search for a reference, reference-color or reference-color-size.') }}
|
||||||
|
</VAlertTitle>
|
||||||
|
<span>{{ $t('Minimum 5 characters long') }}</span>
|
||||||
|
</VAlert>
|
||||||
|
|
||||||
|
<VForm
|
||||||
|
ref="refItemForm"
|
||||||
|
@submit.prevent="validateItemForm"
|
||||||
|
>
|
||||||
|
<VRow>
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
>
|
||||||
|
<AppTextField
|
||||||
|
v-model="itemForm"
|
||||||
|
label="Reference"
|
||||||
|
persistent-placeholder
|
||||||
|
placeholder="QY10010"
|
||||||
|
:rules="[requiredValidator, lengthMinValidator(itemForm, 5)]"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
<VCol cols="12">
|
||||||
|
<VBtn type="submit">
|
||||||
|
{{ $t('Submit') }}
|
||||||
|
</VBtn>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
</VForm>
|
||||||
|
</VCardText>
|
||||||
|
|
||||||
|
<VCol cols="12">
|
||||||
|
<StoreTabItemH />
|
||||||
|
</VCol>
|
||||||
|
</VCard>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { inject } from 'vue'
|
||||||
|
import StoreTabItemHitem from '@/views/pages/store/view/StoreTabItemHitem.vue'
|
||||||
|
import StoreTabItemHoption from '@/views/pages/store/view/StoreTabItemHoption.vue'
|
||||||
|
import StoreTabItemHprice from '@/views/pages/store/view/StoreTabItemHprice.vue'
|
||||||
|
import StoreTabItemHstock from '@/views/pages/store/view/StoreTabItemHstock.vue'
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const item = inject<string>('item')
|
||||||
|
|
||||||
|
const currentTab = ref('tab-1')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VCard>
|
||||||
|
<VTabs
|
||||||
|
v-model="currentTab"
|
||||||
|
grow
|
||||||
|
stacked
|
||||||
|
>
|
||||||
|
<VTab>
|
||||||
|
<VIcon
|
||||||
|
start
|
||||||
|
icon="tabler-shirt-sport"
|
||||||
|
/>
|
||||||
|
{{ $t('Item') }}
|
||||||
|
</VTab>
|
||||||
|
|
||||||
|
<VTab>
|
||||||
|
<VIcon
|
||||||
|
start
|
||||||
|
icon="tabler-report-money"
|
||||||
|
/>
|
||||||
|
{{ $t('Option') }}
|
||||||
|
</VTab>
|
||||||
|
|
||||||
|
<VTab>
|
||||||
|
<VIcon
|
||||||
|
start
|
||||||
|
icon="tabler-currency-euro"
|
||||||
|
/>
|
||||||
|
{{ $t('Price') }}
|
||||||
|
</VTab>
|
||||||
|
|
||||||
|
<VTab>
|
||||||
|
<VIcon
|
||||||
|
start
|
||||||
|
icon="tabler-building-warehouse"
|
||||||
|
/>
|
||||||
|
{{ $t('Stock') }}
|
||||||
|
</VTab>
|
||||||
|
</VTabs>
|
||||||
|
|
||||||
|
<VCardText style="padding: 5px !important;">
|
||||||
|
<VWindow
|
||||||
|
v-model="currentTab"
|
||||||
|
class="ms-3"
|
||||||
|
>
|
||||||
|
<VWindowItem value="tab-1">
|
||||||
|
<StoreTabItemHitem :item="item" />
|
||||||
|
</VWindowItem>
|
||||||
|
<VWindowItem value="tab-2">
|
||||||
|
<StoreTabItemHoption :item="item" />
|
||||||
|
</VWindowItem>
|
||||||
|
<VWindowItem value="tab-3">
|
||||||
|
<StoreTabItemHprice :item="item" />
|
||||||
|
</VWindowItem>
|
||||||
|
<VWindowItem value="tab-4">
|
||||||
|
<StoreTabItemHstock :item="item" />
|
||||||
|
</VWindowItem>
|
||||||
|
</VWindow>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { watchEffect } from 'vue'
|
||||||
|
import { VDataTable } from 'vuetify/labs/VDataTable'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
item: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const route = useRoute('store-details')
|
||||||
|
|
||||||
|
// Data table options
|
||||||
|
const options = ref({ page: 1, itemsPerPage: 10, sortBy: [''], sortDesc: [false] })
|
||||||
|
|
||||||
|
const fetchedData = ref<Array<Record<string, unknown>>>([])
|
||||||
|
const isLoading = ref(false)
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
const { data } = await useApi<any>(createUrl(`/items/${props.item}?dbHost=${route.query.dbHost}`))
|
||||||
|
|
||||||
|
fetchedData.value = data.value
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
// eslint-disable-next-line sonarjs/no-collapsible-if
|
||||||
|
if (props.item)
|
||||||
|
// eslint-disable-next-line curly
|
||||||
|
if (!(props.item.trim() === ''))
|
||||||
|
fetchData()
|
||||||
|
})
|
||||||
|
|
||||||
|
// const { t } = useI18n()
|
||||||
|
|
||||||
|
const headers = computed(() => [
|
||||||
|
{ title: t('ITEM_ID'), key: 'itemId' },
|
||||||
|
{ title: t('LEVEL'), key: 'itemLevelCode' },
|
||||||
|
{ title: t('PARENT'), key: 'parentItemId' },
|
||||||
|
{ title: t('TYPE'), key: 'itemTypeCode' },
|
||||||
|
{ title: t('DATE_CREATE'), key: 'createDate' },
|
||||||
|
{ title: t('USER_CREATE'), key: 'createUserId' },
|
||||||
|
{ title: t('DATE_UPDATE'), key: 'updateDate' },
|
||||||
|
{ title: t('USER_UPDATE'), key: 'updateUserId' },
|
||||||
|
])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<VCol>
|
||||||
|
<div v-if="isLoading">
|
||||||
|
<VProgressCircular
|
||||||
|
indeterminate
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<VDataTable
|
||||||
|
v-else
|
||||||
|
:headers="headers"
|
||||||
|
:items="fetchedData"
|
||||||
|
:items-per-page="options.itemsPerPage"
|
||||||
|
:page="options.page"
|
||||||
|
:options="options"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { watchEffect } from 'vue'
|
||||||
|
import { VDataTable } from 'vuetify/labs/VDataTable'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
item: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const route = useRoute('store-details')
|
||||||
|
|
||||||
|
// Data table options
|
||||||
|
const options = ref({ page: 1, itemsPerPage: 10, sortBy: [''], sortDesc: [false] })
|
||||||
|
const isLoading = ref(false)
|
||||||
|
|
||||||
|
const fetchedData = ref<Array<Record<string, unknown>>>([])
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
const { data } = await useApi<any>(createUrl(`/items/${props.item}/options?dbHost=${route.query.dbHost}`))
|
||||||
|
|
||||||
|
fetchedData.value = data.value.map((item: any) => ({
|
||||||
|
...item,
|
||||||
|
level: `${item.levelCode}:${item.levelValue}`,
|
||||||
|
}))
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
// eslint-disable-next-line sonarjs/no-collapsible-if
|
||||||
|
if (props.item)
|
||||||
|
// eslint-disable-next-line curly
|
||||||
|
if (!(props.item.trim() === ''))
|
||||||
|
fetchData()
|
||||||
|
})
|
||||||
|
|
||||||
|
// const { t } = useI18n()
|
||||||
|
|
||||||
|
const headers = computed(() => [
|
||||||
|
{ title: t('ITEM_ID'), key: 'itemId' },
|
||||||
|
{ title: 'LEVEL', key: 'level' },
|
||||||
|
{ title: 'VENDABLE', key: 'itemAvailabilityCode' },
|
||||||
|
{ title: 'TAXE', key: 'taxGroupId' },
|
||||||
|
{ title: 'VENDOR', key: 'vendor' },
|
||||||
|
{ title: 'SEASON', key: 'seasonCode' },
|
||||||
|
{ title: t('DATE_CREATE'), key: 'createDate' },
|
||||||
|
{ title: t('USER_CREATE'), key: 'createUserId' },
|
||||||
|
{ title: t('DATE_UPDATE'), key: 'updateDate' },
|
||||||
|
{ title: t('USER_UPDATE'), key: 'updateUserId' },
|
||||||
|
])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<VCol>
|
||||||
|
<div v-if="isLoading">
|
||||||
|
<VProgressCircular
|
||||||
|
indeterminate
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- 👉 Datatable -->
|
||||||
|
<VDataTable
|
||||||
|
v-else
|
||||||
|
:headers="headers"
|
||||||
|
:items="fetchedData"
|
||||||
|
:items-per-page="options.itemsPerPage"
|
||||||
|
:page="options.page"
|
||||||
|
:options="options"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { watchEffect } from 'vue'
|
||||||
|
import { VDataTable } from 'vuetify/labs/VDataTable'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
item: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const route = useRoute('store-details')
|
||||||
|
const isLoading = ref(false)
|
||||||
|
|
||||||
|
// Data table options
|
||||||
|
const options = ref({ page: 1, itemsPerPage: 10, sortBy: [''], sortDesc: [false] })
|
||||||
|
|
||||||
|
const fetchedData = ref<Array<Record<string, unknown>>>([])
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
const { data } = await useApi<any>(createUrl(`/items/${props.item}/price?dbHost=${route.query.dbHost}`))
|
||||||
|
|
||||||
|
fetchedData.value = data.value.map((item: any) => ({
|
||||||
|
...item,
|
||||||
|
level: `${item.levelCode}:${item.levelValue}`,
|
||||||
|
}))
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
// eslint-disable-next-line sonarjs/no-collapsible-if
|
||||||
|
if (props.item)
|
||||||
|
// eslint-disable-next-line curly
|
||||||
|
if (!(props.item.trim() === ''))
|
||||||
|
fetchData()
|
||||||
|
})
|
||||||
|
|
||||||
|
// const { t } = useI18n()
|
||||||
|
|
||||||
|
const headers = computed(() => [
|
||||||
|
{ title: t('ITEM_ID'), key: 'itemId' },
|
||||||
|
{ title: t('LEVEL'), key: 'level' },
|
||||||
|
{ title: t('TYPE'), key: 'itmPricePropertyCode' },
|
||||||
|
{ title: t('EFFECTIVE DATE'), key: 'effectiveDate' },
|
||||||
|
{ title: t('EXPIRATION DATE'), key: 'expirationDate' },
|
||||||
|
{ title: t('PRICE'), key: 'price' },
|
||||||
|
{ title: 'EXTERNAL ID', key: 'externalId' },
|
||||||
|
{ title: t('DATE_CREATE'), key: 'createDate' },
|
||||||
|
{ title: t('USER_CREATE'), key: 'createUserId' },
|
||||||
|
{ title: t('DATE_UPDATE'), key: 'updateDate' },
|
||||||
|
{ title: t('USER_UPDATE'), key: 'updateUserId' },
|
||||||
|
])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<VCol>
|
||||||
|
<div v-if="isLoading">
|
||||||
|
<VProgressCircular
|
||||||
|
indeterminate
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<VDataTable
|
||||||
|
v-else
|
||||||
|
:headers="headers"
|
||||||
|
:items="fetchedData"
|
||||||
|
:items-per-page="options.itemsPerPage"
|
||||||
|
:page="options.page"
|
||||||
|
:options="options"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { watchEffect } from 'vue'
|
||||||
|
import { VDataTable } from 'vuetify/labs/VDataTable'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
item: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const route = useRoute('store-details')
|
||||||
|
|
||||||
|
// Data table options
|
||||||
|
const options = ref({ page: 1, itemsPerPage: 10, sortBy: [''], sortDesc: [false] })
|
||||||
|
|
||||||
|
const fetchedData = ref<Array<Record<string, unknown>>>([])
|
||||||
|
const isLoading = ref(false)
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
const { data } = await useApi<any>(createUrl(`/items/${props.item}/stock?dbHost=${route.query.dbHost}`))
|
||||||
|
|
||||||
|
fetchedData.value = data.value
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
// eslint-disable-next-line sonarjs/no-collapsible-if
|
||||||
|
if (props.item)
|
||||||
|
// eslint-disable-next-line curly
|
||||||
|
if (!(props.item.trim() === ''))
|
||||||
|
fetchData()
|
||||||
|
})
|
||||||
|
|
||||||
|
// const { t } = useI18n()
|
||||||
|
|
||||||
|
const headers = computed(() => [
|
||||||
|
{ title: t('ITEM_ID'), key: 'itemId' },
|
||||||
|
{ title: 'LOCATION', key: 'invLocationId' },
|
||||||
|
{ title: 'BUCKET', key: 'bucketId' },
|
||||||
|
{ title: 'STOCK', key: 'unitCount' },
|
||||||
|
{ title: t('DATE_CREATE'), key: 'createDate' },
|
||||||
|
{ title: t('USER_CREATE'), key: 'createUserId' },
|
||||||
|
{ title: t('DATE_UPDATE'), key: 'updateDate' },
|
||||||
|
{ title: t('USER_UPDATE'), key: 'updateUserId' },
|
||||||
|
])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<VCol>
|
||||||
|
<div v-if="isLoading">
|
||||||
|
<VProgressCircular
|
||||||
|
indeterminate
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<VDataTable
|
||||||
|
v-else
|
||||||
|
:headers="headers"
|
||||||
|
:items="fetchedData"
|
||||||
|
:items-per-page="options.itemsPerPage"
|
||||||
|
:page="options.page"
|
||||||
|
:options="options"
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,325 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { endOfDay, format } from 'date-fns'
|
||||||
|
import exportFromJSON from 'export-from-json'
|
||||||
|
import { VDataTable } from 'vuetify/labs/VDataTable'
|
||||||
|
import type { StoreData } from '@/models/storeData'
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const route = useRoute('store-details')
|
||||||
|
|
||||||
|
const headers = computed(() => [
|
||||||
|
{ title: '', key: 'data-table-expand' },
|
||||||
|
{ title: t('WSK'), key: 'wkstnId' },
|
||||||
|
{ title: t('Level'), key: 'logLevel' },
|
||||||
|
{ title: t('Category'), key: 'loggerCategory' },
|
||||||
|
{ title: t('Thread'), key: 'threadName' },
|
||||||
|
{ title: t('Date'), key: 'createDate' },
|
||||||
|
{ title: t('Business Date'), key: 'businessDate' },
|
||||||
|
{ title: t('User'), key: 'createUserId' },
|
||||||
|
])
|
||||||
|
|
||||||
|
const selectedWkStn = ref(null)
|
||||||
|
const selectedLogLevel = ref(null)
|
||||||
|
const selectedCategory = ref(null)
|
||||||
|
const selectedDate = ref(null)
|
||||||
|
const searchQuery = ref('')
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
storeData: StoreData
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSnackbarExport = ref(false)
|
||||||
|
|
||||||
|
// Data table options
|
||||||
|
// TODO SortBy ne marche pas
|
||||||
|
const options = ref({ page: 1, itemsPerPage: 10, sortBy: ['createDate'], sortDesc: [true] })
|
||||||
|
const isLoading = ref(false)
|
||||||
|
|
||||||
|
const dtListData = ref([])
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
isSnackbarExport.value = false
|
||||||
|
|
||||||
|
let logDate
|
||||||
|
if (selectedDate.value)
|
||||||
|
logDate = format(new Date(selectedDate.value), 'yyyyMMdd')
|
||||||
|
else
|
||||||
|
logDate = format(endOfDay(new Date()), 'yyyyMMdd')
|
||||||
|
|
||||||
|
const response = await useApi<any>(createUrl(`/stores/${props.storeData.store.id_structure}/log?dbHost=${route.query.dbHost}&logDate=${logDate}`))
|
||||||
|
|
||||||
|
if (response.data.value)
|
||||||
|
dtListData.value = response.data.value
|
||||||
|
else
|
||||||
|
dtListData.value = []
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const wkstn = computed(() => {
|
||||||
|
const allWkstns = dtListData.value.map((list: { wkstnId: any }) => list.wkstnId)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
const uniqueWkstns = allWkstns.filter((wkstn: any, index: any, self: string | any[]) => self.indexOf(wkstn) === index)
|
||||||
|
const sortedWkstns = uniqueWkstns.sort()
|
||||||
|
|
||||||
|
return sortedWkstns.map((list: any) => ({ title: list, value: list }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const level = computed(() => {
|
||||||
|
const allLogLevels = dtListData.value.map((list: { logLevel: any }) => list.logLevel)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
const uniqueLogLevels = allLogLevels.filter((level: any, index: any, self: string | any[]) => self.indexOf(level) === index)
|
||||||
|
const sortedLogLevels = uniqueLogLevels.sort()
|
||||||
|
|
||||||
|
return sortedLogLevels.map((list: any) => ({ title: list, value: list }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const category = computed(() => {
|
||||||
|
const allCategories = dtListData.value.map((list: { loggerCategory: any }) => list.loggerCategory)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
const uniqueCategories = allCategories.filter((category: any, index: any, self: string | any[]) => self.indexOf(category) === index)
|
||||||
|
const sortedCategories = uniqueCategories.sort()
|
||||||
|
|
||||||
|
return sortedCategories.map((list: any) => ({ title: list, value: list }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const filteredLogList = computed(() => {
|
||||||
|
let filtered = dtListData.value
|
||||||
|
|
||||||
|
// If a workstation is selected, filter the records for this number
|
||||||
|
if (selectedWkStn.value !== undefined && selectedWkStn.value !== null)
|
||||||
|
filtered = filtered.filter((list: { wkstnId: any }) => list.wkstnId === selectedWkStn.value)
|
||||||
|
|
||||||
|
// If a loglevel is selected, filter the records for this Loglevel
|
||||||
|
if (selectedLogLevel.value !== undefined && selectedLogLevel.value !== null)
|
||||||
|
filtered = filtered.filter((list: { logLevel: any }) => list.logLevel === selectedLogLevel.value)
|
||||||
|
|
||||||
|
// If a loggerCategory is selected, filter the records for this loggerCategory
|
||||||
|
if (selectedCategory.value !== undefined && selectedCategory.value !== null)
|
||||||
|
filtered = filtered.filter((list: { loggerCategory: any }) => list.loggerCategory === selectedCategory.value)
|
||||||
|
|
||||||
|
// If a search query is provided, filter the records for this query
|
||||||
|
if (searchQuery.value) {
|
||||||
|
filtered = filtered.filter((log: { [s: string]: unknown } | ArrayLike<unknown>) =>
|
||||||
|
Object.values(log).some(value =>
|
||||||
|
String(value).toLowerCase().includes(searchQuery.value.toLowerCase()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
})
|
||||||
|
|
||||||
|
const ExcelField = [
|
||||||
|
'wkstnId',
|
||||||
|
'logLevel',
|
||||||
|
'threadName',
|
||||||
|
'logMessage',
|
||||||
|
'loggerCategory',
|
||||||
|
'createDate',
|
||||||
|
'createUserId',
|
||||||
|
]
|
||||||
|
|
||||||
|
const ExcelData = computed(() => {
|
||||||
|
return dtListData.value.map((item: { [x: string]: string }) => {
|
||||||
|
const orderedItem: { [key: string]: string } = {}
|
||||||
|
|
||||||
|
ExcelField.forEach(field => {
|
||||||
|
if (field === 'createDate') {
|
||||||
|
const date = new Date(item[field])
|
||||||
|
|
||||||
|
orderedItem[field] = format(date, 'dd/MM/yyyy HH:mm:ss')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
orderedItem[field] = item[field]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return orderedItem
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const exportEXCEL = () => {
|
||||||
|
const fileName = `LOGS_${props.storeData.store.id_structure}-${format(new Date(), 'yyyyMMdd')}`
|
||||||
|
|
||||||
|
if (ExcelData.value.length > 0) {
|
||||||
|
exportFromJSON({
|
||||||
|
data: ExcelData.value,
|
||||||
|
fileName,
|
||||||
|
exportType: exportFromJSON.types.xls,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
isSnackbarExport.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<!-- 👉 filters -->
|
||||||
|
<VCard
|
||||||
|
:title="$t('Filters')"
|
||||||
|
class="mb-6"
|
||||||
|
>
|
||||||
|
<VCardText>
|
||||||
|
<VRow>
|
||||||
|
<!-- 👉 Select workstation -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="4"
|
||||||
|
>
|
||||||
|
<AppSelect
|
||||||
|
v-model="selectedWkStn"
|
||||||
|
:placeholder="$t('WorkStation')"
|
||||||
|
:items="wkstn"
|
||||||
|
clearable
|
||||||
|
clear-icon="tabler-x"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<!-- 👉 Select log level -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="4"
|
||||||
|
>
|
||||||
|
<AppSelect
|
||||||
|
v-model="selectedLogLevel"
|
||||||
|
:placeholder="$t('Level')"
|
||||||
|
:items="level"
|
||||||
|
clearable
|
||||||
|
clear-icon="tabler-x"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<!-- 👉 Select logger category -->
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="4"
|
||||||
|
>
|
||||||
|
<AppSelect
|
||||||
|
v-model="selectedCategory"
|
||||||
|
:placeholder="$t('loggerCategory')"
|
||||||
|
:items="category"
|
||||||
|
clearable
|
||||||
|
clear-icon="tabler-x"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
<VRow>
|
||||||
|
<!-- 👉 Date log -->
|
||||||
|
<VCol sm="4">
|
||||||
|
<AppDateTimePicker
|
||||||
|
v-model="selectedDate"
|
||||||
|
label=""
|
||||||
|
:placeholder="$t('Select date')"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
<!-- 👉 datatable, search & export & reload -->
|
||||||
|
<VCard
|
||||||
|
:title="$t('Logs list')"
|
||||||
|
class="mb-6"
|
||||||
|
>
|
||||||
|
<div class="d-flex flex-wrap gap-4 mx-5">
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<!-- 👉 Search -->
|
||||||
|
<AppTextField
|
||||||
|
v-model="searchQuery"
|
||||||
|
:placeholder="$t('Search')"
|
||||||
|
density="compact"
|
||||||
|
style="inline-size: 200px;"
|
||||||
|
class="me-3"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<VSpacer />
|
||||||
|
<div class="d-flex gap-1 flex-wrap aalign-center">
|
||||||
|
<!-- 👉 Export button -->
|
||||||
|
<VBtn
|
||||||
|
variant="tonal"
|
||||||
|
color="primary"
|
||||||
|
prepend-icon="tabler-upload"
|
||||||
|
@click="exportEXCEL"
|
||||||
|
>
|
||||||
|
Export
|
||||||
|
</VBtn>
|
||||||
|
</div>
|
||||||
|
<!-- 👉 Reload -->
|
||||||
|
<div class="d-flex gap-4 flex-wrap align-center">
|
||||||
|
<VBtn
|
||||||
|
color="primary"
|
||||||
|
prepend-icon="tabler-reload"
|
||||||
|
@click="fetchData"
|
||||||
|
>
|
||||||
|
{{ $t('Reload') }}
|
||||||
|
</VBtn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<VDivider class="mt-4" />
|
||||||
|
|
||||||
|
<VCol cols="12">
|
||||||
|
<div v-if="isLoading">
|
||||||
|
<VProgressCircular
|
||||||
|
indeterminate
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- 👉 Datatable -->
|
||||||
|
<VDataTable
|
||||||
|
v-else
|
||||||
|
class="dt-row-striped"
|
||||||
|
:headers="headers"
|
||||||
|
:items="filteredLogList"
|
||||||
|
:items-per-page="options.itemsPerPage"
|
||||||
|
:page="options.page"
|
||||||
|
:options="options"
|
||||||
|
density="compact"
|
||||||
|
expand-on-click
|
||||||
|
>
|
||||||
|
<!-- format date log -->
|
||||||
|
<template #item.createDate="{ item }">
|
||||||
|
{{ format(new Date(item.createDate), 'dd/MM/yyyy HH:mm:ss') }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- format business date -->
|
||||||
|
<template #item.businessDate="{ item }">
|
||||||
|
{{ format(new Date(item.businessDate), 'dd/MM/yyyy') }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Expanded Row Data -->
|
||||||
|
<template #expanded-row="slotProps">
|
||||||
|
<tr class="v-data-table__tr">
|
||||||
|
<td :colspan="headers.length">
|
||||||
|
<p class="my-1">
|
||||||
|
{{ slotProps.item.logMessage }}
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</VDataTable>
|
||||||
|
</VCol>
|
||||||
|
</VCard>
|
||||||
|
</div>
|
||||||
|
<!-- Snackbar Export -->
|
||||||
|
<VSnackbar
|
||||||
|
v-model="isSnackbarExport"
|
||||||
|
location="center"
|
||||||
|
>
|
||||||
|
{{ $t('ExcelData is empty, cannot export') }}
|
||||||
|
|
||||||
|
<template #actions>
|
||||||
|
<VBtn
|
||||||
|
color="error"
|
||||||
|
@click="isSnackbarExport = false"
|
||||||
|
>
|
||||||
|
{{ $t("Close") }}
|
||||||
|
</VBtn>
|
||||||
|
</template>
|
||||||
|
</VSnackbar>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import type { StoreData } from '@/models/storeData'
|
||||||
|
import logoVNC from '@images/misc/remote_128.png'
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const drive = ['M:', 'N:', 'O:', 'P:', 'Q:', 'R:', 'S:', 'T:']
|
||||||
|
|
||||||
|
const selectedProtocol = ref('hdpos')
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
storeData: StoreData
|
||||||
|
}
|
||||||
|
|
||||||
|
const localStoreData = ref()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// use copy a store data to prevent changes of ip field
|
||||||
|
localStoreData.value = JSON.parse(JSON.stringify(props.storeData))
|
||||||
|
localStoreData.value.store.caisses = localStoreData.value.store.caisses.map((caisse: any) => ({
|
||||||
|
...caisse,
|
||||||
|
drive: null, // replace null with the initial value you want to use
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
const openVnc = (ip: string) => {
|
||||||
|
let url = ''
|
||||||
|
|
||||||
|
if (selectedProtocol.value === 'hdpos')
|
||||||
|
url = `hdpos://tightvnc?ip=${ip}`
|
||||||
|
else
|
||||||
|
url = `${selectedProtocol.value}://${ip}`
|
||||||
|
|
||||||
|
window.open(url, '_blank')
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
const mountDrive = (ip: string, drive: string) => {
|
||||||
|
const url = `hdpos://monter?lettre=${drive}&ip=${ip}`
|
||||||
|
|
||||||
|
window.open(url, '_blank')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VRow>
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
>
|
||||||
|
<VCard
|
||||||
|
flat
|
||||||
|
border
|
||||||
|
>
|
||||||
|
<VCardText>
|
||||||
|
<VCardText class="text-center d-flex align-center justify-left">
|
||||||
|
<img
|
||||||
|
:src="logoVNC"
|
||||||
|
size="128"
|
||||||
|
>
|
||||||
|
<AppSelect
|
||||||
|
v-model="selectedProtocol"
|
||||||
|
:items="['hdpos', 'vnc']"
|
||||||
|
class="me-3"
|
||||||
|
style="max-inline-size: 130px"
|
||||||
|
/>
|
||||||
|
</VCardText>
|
||||||
|
<div v-if="localStoreData && localStoreData.store.caisses">
|
||||||
|
<div
|
||||||
|
v-for="(caisse, index) in localStoreData.store.caisses"
|
||||||
|
:key="index"
|
||||||
|
class="d-flex align-items-center mb-5"
|
||||||
|
>
|
||||||
|
<VRow>
|
||||||
|
<VCol
|
||||||
|
cols="6"
|
||||||
|
md="6"
|
||||||
|
class="d-flex align-items-center"
|
||||||
|
>
|
||||||
|
<VTextField
|
||||||
|
v-model.lazy="caisse.ip"
|
||||||
|
label="IP"
|
||||||
|
class="me-3"
|
||||||
|
/>
|
||||||
|
<VBtn
|
||||||
|
v-if="caisse.ip"
|
||||||
|
color="primary"
|
||||||
|
@click="openVnc(caisse.ip)"
|
||||||
|
>
|
||||||
|
Pos {{ caisse.id_caisse }}
|
||||||
|
</VBtn>
|
||||||
|
<VBtn
|
||||||
|
v-else
|
||||||
|
disabled
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
Pos {{ caisse.id_caisse }}
|
||||||
|
</VBtn>
|
||||||
|
</VCol>
|
||||||
|
|
||||||
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
class="d-flex align-items-center"
|
||||||
|
>
|
||||||
|
<AppSelect
|
||||||
|
v-model="caisse.drive"
|
||||||
|
:items="drive"
|
||||||
|
label=""
|
||||||
|
density="compact"
|
||||||
|
placeholder="Select"
|
||||||
|
class="me-3"
|
||||||
|
/>
|
||||||
|
<VBtn
|
||||||
|
v-if="caisse.drive"
|
||||||
|
color="primary"
|
||||||
|
@click="mountDrive(caisse.ip, caisse.drive)"
|
||||||
|
>
|
||||||
|
Mount drive {{ caisse.drive }}
|
||||||
|
</VBtn>
|
||||||
|
<VBtn
|
||||||
|
v-else
|
||||||
|
disabled
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
Mount drive
|
||||||
|
</VBtn>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
</template>
|
||||||
|
|
@ -10,14 +10,14 @@ import { AppContentLayoutNav, ContentWidth, FooterType, NavbarType } from '@layo
|
||||||
|
|
||||||
export const { themeConfig, layoutConfig } = defineThemeConfig({
|
export const { themeConfig, layoutConfig } = defineThemeConfig({
|
||||||
app: {
|
app: {
|
||||||
title: 'vuexy',
|
title: 'hdpos',
|
||||||
logo: h('div', { innerHTML: logo, style: 'line-height:0; color: rgb(var(--v-global-theme-primary))' }),
|
logo: h('div', { innerHTML: logo, style: 'line-height:0; color: rgb(var(--v-global-theme-primary))' }),
|
||||||
contentWidth: ContentWidth.Boxed,
|
contentWidth: ContentWidth.Boxed,
|
||||||
contentLayoutNav: AppContentLayoutNav.Vertical,
|
contentLayoutNav: AppContentLayoutNav.Horizontal,
|
||||||
overlayNavFromBreakpoint: breakpointsVuetify.md + 16, // 16 for scrollbar. Docs: https://next.vuetifyjs.com/en/features/display-and-platform/
|
overlayNavFromBreakpoint: breakpointsVuetify.md + 16, // 16 for scrollbar. Docs: https://next.vuetifyjs.com/en/features/display-and-platform/
|
||||||
i18n: {
|
i18n: {
|
||||||
enable: false,
|
enable: true,
|
||||||
defaultLocale: 'en',
|
defaultLocale: 'fr',
|
||||||
langConfig: [
|
langConfig: [
|
||||||
{
|
{
|
||||||
label: 'English',
|
label: 'English',
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,9 @@
|
||||||
],
|
],
|
||||||
"@api-utils/*": [
|
"@api-utils/*": [
|
||||||
"./src/plugins/fake-api/utils/*"
|
"./src/plugins/fake-api/utils/*"
|
||||||
|
],
|
||||||
|
"@stores/*": [
|
||||||
|
"./src/stores/*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lib": [
|
"lib": [
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,11 @@ declare module 'vue-router/auto/routes' {
|
||||||
export interface RouteNamedMap {
|
export interface RouteNamedMap {
|
||||||
'root': RouteRecordInfo<'root', '/', Record<never, never>, Record<never, never>>,
|
'root': RouteRecordInfo<'root', '/', Record<never, never>, Record<never, never>>,
|
||||||
'$error': RouteRecordInfo<'$error', '/:error(.*)', { error: ParamValue<true> }, { error: ParamValue<false> }>,
|
'$error': RouteRecordInfo<'$error', '/:error(.*)', { error: ParamValue<true> }, { error: ParamValue<false> }>,
|
||||||
|
'flux-bl-not-sent': RouteRecordInfo<'flux-bl-not-sent', '/flux/bl/not_sent', Record<never, never>, Record<never, never>>,
|
||||||
'login': RouteRecordInfo<'login', '/login', Record<never, never>, Record<never, never>>,
|
'login': RouteRecordInfo<'login', '/login', Record<never, never>, Record<never, never>>,
|
||||||
'second-page': RouteRecordInfo<'second-page', '/second-page', Record<never, never>, Record<never, never>>,
|
'store-details': RouteRecordInfo<'store-details', '/store/details', Record<never, never>, Record<never, never>>,
|
||||||
|
'store-list': RouteRecordInfo<'store-list', '/store/list', Record<never, never>, Record<never, never>>,
|
||||||
|
'xadmin-log': RouteRecordInfo<'xadmin-log', '/xadmin/log', Record<never, never>, Record<never, never>>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ export default defineConfig({
|
||||||
'apexcharts': fileURLToPath(new URL('node_modules/apexcharts-clevision', import.meta.url)),
|
'apexcharts': fileURLToPath(new URL('node_modules/apexcharts-clevision', import.meta.url)),
|
||||||
'@db': fileURLToPath(new URL('./src/plugins/fake-api/handlers/', import.meta.url)),
|
'@db': fileURLToPath(new URL('./src/plugins/fake-api/handlers/', import.meta.url)),
|
||||||
'@api-utils': fileURLToPath(new URL('./src/plugins/fake-api/utils/', import.meta.url)),
|
'@api-utils': fileURLToPath(new URL('./src/plugins/fake-api/utils/', import.meta.url)),
|
||||||
|
'@stores': fileURLToPath(new URL('./src/stores/', import.meta.url)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
|
|
||||||