Merge pull request 'refactor/issue-1/first-setup' (#9) from refactor/issue-1/first-setup into dev
Reviewed-on: #9pull/10/head
|
|
@ -1 +0,0 @@
|
|||
VITE_API_BASE_URL=
|
||||
|
|
@ -509,6 +509,7 @@ declare module 'vue' {
|
|||
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
|
||||
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
|
||||
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 useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
|
||||
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 until: UnwrapRef<typeof import('@vueuse/core')['until']>
|
||||
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 useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
|
||||
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<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" />
|
||||
</head>
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@
|
|||
"apexcharts-clevision": "^3.28.5",
|
||||
"chart.js": "^4.4.0",
|
||||
"cookie-es": "^1.0.0",
|
||||
"date-fns": "^3.0.6",
|
||||
"export-from-json": "^1.7.4",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"mapbox-gl": "2.15.0",
|
||||
"ofetch": "^1.3.3",
|
||||
|
|
@ -133,4 +135,4 @@
|
|||
"msw": {
|
||||
"workerDirectory": "public"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,12 @@ dependencies:
|
|||
cookie-es:
|
||||
specifier: ^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:
|
||||
specifier: ^3.1.2
|
||||
version: 3.1.2
|
||||
|
|
@ -3198,6 +3204,10 @@ packages:
|
|||
/dash-get@1.0.2:
|
||||
resolution: {integrity: sha512-4FbVrHDwfOASx7uQVxeiCTo7ggSdYZbqs8lH+WU6ViypPlDbe9y6IP5VVUDQBv9DcnyaiPT5XT0UWHgJ64zLeQ==}
|
||||
|
||||
/date-fns@3.0.6:
|
||||
resolution: {integrity: sha512-W+G99rycpKMMF2/YD064b2lE7jJGUe+EjOES7Q8BIGY8sbNdbgcs9XFTZwvzc9Jx1f3k7LB7gZaZa7f8Agzljg==}
|
||||
dev: false
|
||||
|
||||
/de-indent@1.0.2:
|
||||
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
|
||||
dev: true
|
||||
|
|
@ -4109,6 +4119,10 @@ packages:
|
|||
strip-final-newline: 3.0.0
|
||||
dev: true
|
||||
|
||||
/export-from-json@1.7.4:
|
||||
resolution: {integrity: sha512-FjmpluvZS2PTYyhkoMfQoyEJMfe2bfAyNpa5Apa6C9n7SWUWyJkG/VFnzERuj3q9Jjo3iwBjwVsDQ7Z7sczthA==}
|
||||
dev: false
|
||||
|
||||
/external-editor@3.1.0:
|
||||
resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
|
||||
engines: {node: '>=4'}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { useStorage } from '@vueuse/core'
|
||||
import flatpickr from 'flatpickr'
|
||||
import { French } from 'flatpickr/dist/l10n/fr'
|
||||
import { useTheme } from 'vuetify'
|
||||
import { useConfigStore } from '@core/stores/config'
|
||||
import { cookieRef, namespaceConfig } from '@layouts/stores/config'
|
||||
import { themeConfig } from '@themeConfig'
|
||||
import { cookieRef, namespaceConfig } from '@layouts/stores/config'
|
||||
import { useConfigStore } from '@core/stores/config'
|
||||
|
||||
const _syncAppRtl = () => {
|
||||
const configStore = useConfigStore()
|
||||
|
|
@ -14,6 +16,15 @@ const _syncAppRtl = () => {
|
|||
if (locale.value !== storedLang.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(
|
||||
locale,
|
||||
|
|
@ -25,6 +36,15 @@ const _syncAppRtl = () => {
|
|||
// Store selected language in cookie
|
||||
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
|
||||
if (themeConfig.app.i18n.langConfig && themeConfig.app.i18n.langConfig.length) {
|
||||
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
|
||||
.dt-row-striped tr:nth-of-type(even) {
|
||||
background-color: #f6f2f28d;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { createFetch } from '@vueuse/core'
|
|||
import { destr } from 'destr'
|
||||
|
||||
export const useApi = createFetch({
|
||||
baseUrl: import.meta.env.VITE_API_BASE_URL || '/api',
|
||||
baseUrl: import.meta.env.VITE_API_BASE_URL,
|
||||
fetchOptions: {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
|
|
|
|||
|
|
@ -12,26 +12,11 @@
|
|||
class="mx-1"
|
||||
/>
|
||||
By <a
|
||||
href="https://pixinvent.com"
|
||||
href="https://inetum.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-primary ms-1"
|
||||
>Pixinvent</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>
|
||||
>Inetum</a>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,24 @@
|
|||
<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>
|
||||
|
||||
<template>
|
||||
|
|
@ -13,10 +32,18 @@ import avatar1 from '@images/avatars/avatar-1.png'
|
|||
>
|
||||
<VAvatar
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
:color="avatarColor"
|
||||
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 -->
|
||||
<VMenu
|
||||
|
|
@ -38,80 +65,33 @@ import avatar1 from '@images/avatars/avatar-1.png'
|
|||
color="success"
|
||||
>
|
||||
<VAvatar
|
||||
color="primary"
|
||||
:color="avatarColor"
|
||||
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>
|
||||
</VBadge>
|
||||
</VListItemAction>
|
||||
</template>
|
||||
|
||||
<VListItemTitle class="font-weight-semibold">
|
||||
John Doe
|
||||
{{ useUserStore.username }}
|
||||
</VListItemTitle>
|
||||
<VListItemSubtitle>Admin</VListItemSubtitle>
|
||||
<VListItemSubtitle>{{ useUserStore.role }}</VListItemSubtitle>
|
||||
</VListItem>
|
||||
|
||||
<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 -->
|
||||
<VListItem to="/login">
|
||||
<VListItem @click="logoutAndRedirect">
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
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' },
|
||||
},
|
||||
{
|
||||
title: 'Second page',
|
||||
to: { name: 'second-page' },
|
||||
icon: { icon: 'tabler-file' },
|
||||
title: 'Store',
|
||||
to: { name: 'store-list' },
|
||||
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' },
|
||||
},
|
||||
{
|
||||
title: 'Second page',
|
||||
to: { name: 'second-page' },
|
||||
icon: { icon: 'tabler-file' },
|
||||
title: 'Store',
|
||||
to: { name: 'store-list' },
|
||||
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>
|
||||
<VCard
|
||||
class="mb-6"
|
||||
title="Kick start your project 🚀"
|
||||
title="XSTORE HELPDESK DASHBOARD 🚀"
|
||||
>
|
||||
<VCardText>All the best for your new project.</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>
|
||||
<VCardText>A cet endroit bientôt des statistiques</VCardText>
|
||||
|
||||
<VCard title="Want to integrate JWT? 🔒">
|
||||
<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>
|
||||
<VCardText>👉 Cliquez maintenant sur Boutique :)</VCardText>
|
||||
</VCard>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<!-- ❗Errors in the form are set on line 60 -->
|
||||
<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 authV2LoginIllustrationBorderedDark from '@images/pages/auth-v2-login-illustration-bordered-dark.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 authV2MaskLight from '@images/pages/misc-mask-light.png'
|
||||
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
|
||||
import { userStore } from '@stores/user.store'
|
||||
import { themeConfig } from '@themeConfig'
|
||||
|
||||
const authThemeImg = useGenerateImageVariant(authV2LoginIllustrationLight, authV2LoginIllustrationDark, authV2LoginIllustrationBorderedLight, authV2LoginIllustrationBorderedDark, true)
|
||||
|
||||
const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
|
||||
|
||||
definePage({
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
unauthenticatedOnly: true,
|
||||
},
|
||||
})
|
||||
|
||||
const form = ref({
|
||||
email: '',
|
||||
password: '',
|
||||
remember: false,
|
||||
})
|
||||
|
||||
const isPasswordVisible = ref(false)
|
||||
|
||||
const authThemeImg = useGenerateImageVariant(
|
||||
authV2LoginIllustrationLight,
|
||||
authV2LoginIllustrationDark,
|
||||
authV2LoginIllustrationBorderedLight,
|
||||
authV2LoginIllustrationBorderedDark,
|
||||
true)
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
|
|
@ -40,8 +78,8 @@ const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
|
|||
class="auth-wrapper bg-surface"
|
||||
>
|
||||
<VCol
|
||||
md="8"
|
||||
class="d-none d-md-flex"
|
||||
lg="8"
|
||||
class="d-none d-lg-flex"
|
||||
>
|
||||
<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">
|
||||
|
|
@ -53,15 +91,15 @@ const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
|
|||
</div>
|
||||
|
||||
<VImg
|
||||
class="auth-footer-mask"
|
||||
:src="authThemeMask"
|
||||
class="auth-footer-mask"
|
||||
/>
|
||||
</div>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
lg="4"
|
||||
class="auth-card-v2 d-flex align-center justify-center"
|
||||
>
|
||||
<VCard
|
||||
|
|
@ -74,49 +112,51 @@ const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
|
|||
:nodes="themeConfig.app.logo"
|
||||
class="mb-6"
|
||||
/>
|
||||
|
||||
<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>
|
||||
<p class="mb-0">
|
||||
Please sign-in to your account and start the adventure
|
||||
</p>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => { }">
|
||||
<VForm
|
||||
ref="refVForm"
|
||||
@submit.prevent="onSubmit"
|
||||
>
|
||||
<VRow>
|
||||
<!-- email -->
|
||||
<!-- username -->
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.email"
|
||||
v-model="credentials.username"
|
||||
label="Username"
|
||||
placeholder="First name"
|
||||
type="username"
|
||||
autofocus
|
||||
label="Email"
|
||||
type="email"
|
||||
placeholder="johndoe@email.com"
|
||||
:rules="[requiredValidator]"
|
||||
:error-messages="errors.username"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- password -->
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.password"
|
||||
v-model="credentials.password"
|
||||
label="Password"
|
||||
placeholder="············"
|
||||
:rules="[requiredValidator]"
|
||||
:type="isPasswordVisible ? 'text' : 'password'"
|
||||
:error-messages="errors.password"
|
||||
:append-inner-icon="isPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
|
||||
@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
|
||||
v-model="form.remember"
|
||||
v-model="rememberMe"
|
||||
label="Remember me"
|
||||
/>
|
||||
<a
|
||||
class="text-primary ms-2 mb-1"
|
||||
href="#"
|
||||
>
|
||||
Forgot Password?
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<VBtn
|
||||
|
|
@ -126,46 +166,28 @@ const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
|
|||
Login
|
||||
</VBtn>
|
||||
</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>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</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>
|
||||
|
||||
<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'
|
||||
|
||||
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 }) {
|
||||
const accessToken = useCookie('accessToken').value
|
||||
if (accessToken) {
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
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({
|
||||
app: {
|
||||
title: 'vuexy',
|
||||
title: 'hdpos',
|
||||
logo: h('div', { innerHTML: logo, style: 'line-height:0; color: rgb(var(--v-global-theme-primary))' }),
|
||||
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/
|
||||
i18n: {
|
||||
enable: false,
|
||||
defaultLocale: 'en',
|
||||
enable: true,
|
||||
defaultLocale: 'fr',
|
||||
langConfig: [
|
||||
{
|
||||
label: 'English',
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@
|
|||
],
|
||||
"@api-utils/*": [
|
||||
"./src/plugins/fake-api/utils/*"
|
||||
],
|
||||
"@stores/*": [
|
||||
"./src/stores/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
|
|
|
|||
|
|
@ -41,8 +41,11 @@ declare module 'vue-router/auto/routes' {
|
|||
export interface RouteNamedMap {
|
||||
'root': RouteRecordInfo<'root', '/', Record<never, never>, Record<never, never>>,
|
||||
'$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>>,
|
||||
'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)),
|
||||
'@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)),
|
||||
'@stores': fileURLToPath(new URL('./src/stores/', import.meta.url)),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
|
|
|
|||