feat: store details translation

refactor/issue-1/first-setup
Frédérik Benoist 2023-12-17 23:41:57 +01:00
parent 60f141deba
commit f6195e51a0
30 changed files with 1432 additions and 96 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

51
src/models/storeData.ts Normal file
View File

@ -0,0 +1,51 @@
interface Store {
id_structure: number
nom: string
ip_master: string
telephone: string
photoLink: string
enseigne: string
nb_caisses: number
adresse: string
caisses: Caisse[]
}
interface Caisse {
id_caisse: number
ip: string
}
interface Replication {
pendingReplications: number
minPendingReplicationDate: string
maxPendingReplicationDate: string
}
interface Transaction {
backOfficeTransactions: number
minBackOfficeTransactionDate: string
maxBackOfficeTransactionDate: string
backOfficeBusinessDate: string
}
interface XstoreTransaction {
count: number
minDate: string
minDateT: string
minDateH: string
maxDate: string
maxDateT: string
maxDateH: string
}
export interface StoreData {
store: Store
replication: Replication
transaction: Transaction
openingTransaction: XstoreTransaction
closingTransaction: XstoreTransaction
saleTransaction: XstoreTransaction
xstoreVersion: string
xstoreVersionDate: string
xstoreVersionCustomer: string
}

View File

@ -9,4 +9,12 @@ export default [
to: { name: 'store-list' }, to: { name: 'store-list' },
icon: { icon: 'tabler-file' }, icon: { icon: 'tabler-file' },
}, },
{
title: 'Obi',
icon: { icon: 'tabler-file' },
},
{
title: 'Dotsoft',
icon: { icon: 'tabler-file' },
},
] ]

View File

@ -9,4 +9,12 @@ export default [
to: { name: 'store-list' }, to: { name: 'store-list' },
icon: { icon: 'tabler-file' }, icon: { icon: 'tabler-file' },
}, },
{
title: 'Obi',
icon: { icon: 'tabler-file' },
},
{
title: 'Dotsoft',
icon: { icon: 'tabler-file' },
},
] ]

View File

@ -2,24 +2,11 @@
<div> <div>
<VCard <VCard
class="mb-6" class="mb-6"
title="Kick start your project 🚀" title="XSTORE HELPDESK DASHBOARD 🚀"
> >
<VCardText>All the best for your new project.</VCardText> <VCardText>A cet endroit bientôt des statistiques</VCardText>
<VCardText>
Please make sure to read our <a
href="https://demos.pixinvent.com/vuexy-vuejs-admin-template/documentation/"
target="_blank"
rel="noopener noreferrer"
class="text-decoration-none"
>
Template Documentation
</a> to understand where to go from here and how to use our template.
</VCardText>
</VCard>
<VCard title="Want to integrate JWT? 🔒"> <VCardText>👉 Cliquez maintenant sur Boutique :)</VCardText>
<VCardText>We carefully crafted JWT flow so you can implement JWT with ease and with minimum efforts.</VCardText>
<VCardText>Please read our JWT Documentation to get more out of JWT authentication.</VCardText>
</VCard> </VCard>
</div> </div>
</template> </template>

View File

@ -3,6 +3,9 @@ import StoreHeader from '@/views/pages/store/view/StoreHeader.vue'
import StoreTabAdmin from '@/views/pages/store/view/StoreTabAdmin.vue' import StoreTabAdmin from '@/views/pages/store/view/StoreTabAdmin.vue'
import StoreTabGeneral from '@/views/pages/store/view/StoreTabGeneral.vue' import StoreTabGeneral from '@/views/pages/store/view/StoreTabGeneral.vue'
import StoreTabItem from '@/views/pages/store/view/StoreTabItem.vue' import StoreTabItem from '@/views/pages/store/view/StoreTabItem.vue'
import StoreTabRemote from '@/views/pages/store/view/StoreTabRemote.vue'
const { t } = useI18n()
const route = useRoute('store-details') const route = useRoute('store-details')
@ -12,9 +15,10 @@ console.log(route.query.dbHost)
// tabs // tabs
const tabs = [ const tabs = [
{ title: 'General', icon: 'tabler-users', tab: 'general' }, { title: 'General', icon: 'tabler-settings', tab: 'general' },
{ title: 'Item', icon: 'tabler-lock', tab: 'item' }, { title: t('Item'), icon: 'tabler-shirt-sport', tab: 'item' },
{ title: 'Admin', icon: 'tabler-file-text', tab: 'admin' }, { title: t('Remote'), icon: 'tabler-brand-openvpn', tab: 'remote' },
{ title: 'Admin', icon: 'tabler-lock', tab: 'admin' },
] ]
const { data: storeData } = await useApi<any>(`/stores/${route.query.storeId}/details?dbHost=${route.query.dbHost}`) const { data: storeData } = await useApi<any>(`/stores/${route.query.storeId}/details?dbHost=${route.query.dbHost}`)
@ -49,19 +53,24 @@ const { data: storeData } = await useApi<any>(`/stores/${route.query.storeId}/de
class="mt-6 disable-tab-transition" class="mt-6 disable-tab-transition"
:touch="false" :touch="false"
> >
<!-- General --> <!-- 👉 General -->
<VWindowItem> <VWindowItem>
<StoreTabGeneral :store-data="storeData" /> <StoreTabGeneral :store-data="storeData" />
</VWindowItem> </VWindowItem>
<!-- item --> <!-- 👉 Item -->
<VWindowItem> <VWindowItem>
<StoreTabItem /> <StoreTabItem />
</VWindowItem> </VWindowItem>
<!-- admin --> <!-- 👉 Remote Access -->
<VWindowItem> <VWindowItem>
<StoreTabAdmin /> <StoreTabRemote :store-data="storeData" />
</VWindowItem>
<!-- 👉 Admin -->
<VWindowItem>
<StoreTabAdmin :store-data="storeData" />
</VWindowItem> </VWindowItem>
</VWindow> </VWindow>
</div> </div>

View File

@ -11,6 +11,7 @@ const headers = computed(() => [
{ title: t('Phone'), key: 'telephone', sortable: false }, { title: t('Phone'), key: 'telephone', sortable: false },
{ title: t('Brand'), key: 'enseigne' }, { title: t('Brand'), key: 'enseigne' },
{ title: t('Country'), key: 'pays' }, { title: t('Country'), key: 'pays' },
{ title: '', key: 'actions', sortable: false },
]) ])
const selectedCountry = ref() const selectedCountry = ref()
@ -75,6 +76,23 @@ const filteredStoresList = computed(() => {
return filtered 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
}
</script> </script>
<template> <template>
@ -166,18 +184,81 @@ const filteredStoresList = computed(() => {
:page="options.page" :page="options.page"
:options="options" :options="options"
> >
<!-- Store details hyperlink -->
<template #item.nom="{ item }"> <template #item.nom="{ item }">
<RouterLink :to="`/store/details?dbHost=${item.ip_master}&storeId=${item.id_structure}`"> <RouterLink :to="`/store/details?dbHost=${item.ip_master}&storeId=${item.id_structure}`">
{{ item.nom }} {{ item.nom }}
</RouterLink> </RouterLink>
</template> </template>
<!-- Pos count -->
<template #item.nbcaisses="{ item }"> <template #item.nbcaisses="{ item }">
<VIcon v-if="item.nbcaisses > 1 && item.nbcaisses <= 9"> <VIcon v-if="item.nbcaisses > 1 && item.nbcaisses <= 9">
{{ `tabler-square-rounded-number-${item.nbcaisses}` }} {{ `tabler-square-rounded-number-${item.nbcaisses}` }}
</VIcon> </VIcon>
<span v-else-if="item.nbcaisses > 9">{{ item.nbcaisses }}</span> <span v-else-if="item.nbcaisses > 9">{{ item.nbcaisses }}</span>
</template> </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> </VDataTable>
<VDialog
v-model="isDialogVisible"
max-width="600"
>
<!-- Dialog close btn -->
<DialogCloseBtn @click="isDialogVisible = !isDialogVisible" />
<!-- Dialog Content -->
<VCard title="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"
>
Close
</VBtn>
</VCardText>
</VCard>
</VDialog>
</VCol> </VCol>
</vcard> </vcard>
</div> </div>

View File

@ -8,6 +8,31 @@
"Search": "بحث", "Search": "بحث",
"Name": "الاسم", "Name": "الاسم",
"Phone": "هاتف", "Phone": "هاتف",
"Mode": "الوضع",
"Value": "القيمة",
"Item": "العنصر",
"Remote": "متصل",
"Option": "خيار",
"Stock": "المخزون",
"Price": "السعر",
"ITEM_ID": "ID العنصر",
"PRICE": "السعر",
"TYPE": "TYPE",
"PARENT": "PARENT",
"LEVEL": "LEVEL",
"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",
"---------------------------": "---------------------------", "---------------------------": "---------------------------",
"UI Elements": "عناصر واجهة المستخدم", "UI Elements": "عناصر واجهة المستخدم",
"Forms & Tables": "النماذج والجداول", "Forms & Tables": "النماذج والجداول",

View File

@ -8,6 +8,31 @@
"Search": "Search", "Search": "Search",
"Name": "Name", "Name": "Name",
"Phone": "Phone", "Phone": "Phone",
"Mode": "Mode",
"Value": "Value",
"Item": "Item",
"Remote": "Remote",
"Option": "Option",
"Stock": "Stock",
"Price": "Price",
"ITEM_ID": "ITEM ID",
"PRICE": "PRICE",
"TYPE": "TYPE",
"PARENT": "PARENT",
"LEVEL": "LEVEL",
"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",
"---------------------------": "---------------------------", "---------------------------": "---------------------------",
"UI Elements": "UI Elements", "UI Elements": "UI Elements",
"Forms & Tables": "Forms & Tables", "Forms & Tables": "Forms & Tables",

View File

@ -1,5 +1,5 @@
{ {
"Home": "Acceuil", "Home": "Accueil",
"Store": "Boutique", "Store": "Boutique",
"List of stores": "Liste des boutiques", "List of stores": "Liste des boutiques",
"Country": "Pays", "Country": "Pays",
@ -8,6 +8,31 @@
"Search": "Chercher", "Search": "Chercher",
"Name": "Nom", "Name": "Nom",
"Phone": "Téléphone", "Phone": "Téléphone",
"Mode": "Mode",
"Value": "Valeur",
"Item": "Article",
"Remote": "Accès distant",
"Option": "Option",
"Stock": "Stock",
"Price": "Prix",
"ITEM_ID": "ITEM ID",
"PRICE": "PRIX",
"TYPE": "TYPE",
"PARENT": "PARENT",
"LEVEL": "NIVEAU",
"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",
"---------------------------": "---------------------------", "---------------------------": "---------------------------",
"UI Elements": "ÉLÉMENTS DE L'UI", "UI Elements": "ÉLÉMENTS DE L'UI",
"Forms & Tables": "Formulaires et tableaux", "Forms & Tables": "Formulaires et tableaux",

View File

@ -1,22 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import type { StoreData } from '@/models/storeData'
import UserProfileHeaderBg from '@images/pages/user-profile-header-bg.png' import UserProfileHeaderBg from '@images/pages/user-profile-header-bg.png'
interface Store {
id_structure: number
nom: string
ip: string
telephone: string
photoLink: string
enseigne: string
adresse: string
}
interface StoreHeaderData {
store: Store
}
interface Props { interface Props {
storeData: StoreHeaderData storeData: StoreData
} }
const props = defineProps<Props>() const props = defineProps<Props>()
@ -42,7 +29,7 @@ const props = defineProps<Props>()
<div class="user-profile-info w-100 mt-16 pt-6 pt-sm-0 mt-sm-0"> <div class="user-profile-info w-100 mt-16 pt-6 pt-sm-0 mt-sm-0">
<h5 class="text-h5 text-center text-sm-start font-weight-medium mb-3"> <h5 class="text-h5 text-center text-sm-start font-weight-medium mb-3">
{{ props?.storeData.store.id_structure }} - {{ props?.storeData.store.nom }} {{ props.storeData.store.id_structure }} - {{ props.storeData.store.nom }}
</h5> </h5>
<div class="d-flex align-center justify-center justify-sm-space-between flex-wrap gap-4"> <div class="d-flex align-center justify-center justify-sm-space-between flex-wrap gap-4">
@ -54,7 +41,7 @@ const props = defineProps<Props>()
class="me-1" class="me-1"
/> />
<span class="text-body-1"> <span class="text-body-1">
{{ props?.storeData.store.enseigne }} {{ props.storeData.store.enseigne }}
</span> </span>
</span> </span>
@ -65,7 +52,7 @@ const props = defineProps<Props>()
class="me-1" class="me-1"
/> />
<span class="text-body-1"> <span class="text-body-1">
{{ props?.storeData.store.telephone }} {{ props.storeData.store.telephone }}
</span> </span>
</span> </span>
@ -76,17 +63,27 @@ const props = defineProps<Props>()
class="me-1" class="me-1"
/> />
<span class="text-body-1"> <span class="text-body-1">
{{ props?.storeData.store.adresse }} {{ props.storeData.store.adresse }}
</span> </span>
</span> </span>
</div> </div>
<VBtn <div class="d-flex align-center gap-4">
prepend-icon="tabler-check" <VAvatar
color="success" color="primary"
> variant="tonal"
Connected size="42"
</VBtn> >
<VIcon icon="tabler-git-merge" />
</VAvatar>
<div class="d-flex flex-column">
<span class="text-h5 font-weight-medium">{{ props.storeData.xstoreVersion }} ({{ props.storeData.xstoreVersionCustomer }})</span>
<span class="text-sm">
{{ props.storeData.xstoreVersionDate }}
</span>
</div>
</div>
</div> </div>
</div> </div>
</VCardText> </VCardText>

View File

@ -1,8 +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> <template>
<div> <div>
<VCard title="STORE TAB ADMIN"> <VRow>
<VCardText>We carefully crafted JWT flow so you can implement JWT with ease and with minimum efforts.</VCardText> <VCol cols="12">
<VCardText>Please read our JWT Documentation to get more out of JWT authentication.</VCardText> <StoreTabAdminH :store-data="storeData" />
</VCard> </VCol>
</VRow>
</div> </div>
</template> </template>

View File

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

View File

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

View File

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

View File

@ -1,33 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
interface Store { import type { StoreData } from '@/models/storeData'
id_structure: number
nom: string
ip: string
telephone: string
photoLink: string
enseigne: string
}
interface Replication {
pendingReplicationOk: boolean
pendingReplications: number
minPendingReplicationDate: string
maxPendingReplicationDate: string
}
interface Transaction {
backOfficeTransactionOk: boolean
backOfficeTransactions: number
minBackOfficeTransactionDate: string
maxBackOfficeTransactionDate: string
backOfficeBusinessDate: string
}
interface StoreData {
store: Store
replication: Replication
transaction: Transaction
}
interface Props { interface Props {
storeData: StoreData storeData: StoreData
@ -37,14 +9,185 @@ const props = defineProps<Props>()
</script> </script>
<template> <template>
<!-- 👉 User fullName --> <VRow class="py-6">
<h6 class="text-h4 mt-4"> <!-- 👉 Business date & opening hours -->
{{ props.storeData.store.id_structure }} <VCol
</h6> cols="12"
<div> md="3"
<VCard title="STORE TAB GENERAL"> :class="$vuetify.display.mdAndUp ? 'border-e' : 'border-b'"
<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> <div class="ape-3">
</VCard> <div class="d-flex justify-space-between flex-wrap gap-4 flex-column flex-xs-row">
</div> <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">
{{ storeData.openingTransaction.maxDateT }}
</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-h4 font-weight-medium text-info">
{{ storeData.openingTransaction.maxDateH }}{{ storeData.closingTransaction.maxDateH ? ` / ${storeData.closingTransaction.maxDateH}` : '' }}
</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">
{{ props.storeData.replication.pendingReplications }}
</div>
<span class="mb-1">
<div>
<VChip
variant="tonal"
color="secondary"
style="inline-size:45px"
>
Min
</VChip>
{{ props.storeData.replication.minPendingReplicationDate }}
</div>
</span>
<span class="mb-1">
<div>
<VChip
variant="tonal"
color="secondary"
style="inline-size:45px"
>
Max
</VChip>
{{ props.storeData.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">
{{ props.storeData.saleTransaction.count }}
</div>
<span class="mb-1">
<div>
<VChip
variant="tonal"
color="secondary"
style="inline-size:45px"
>
Min
</VChip>
{{ props.storeData.saleTransaction.minDate }}
</div>
</span>
<span class="mb-1">
<div>
<VChip
variant="tonal"
color="secondary"
style="inline-size:45px"
>
Max
</VChip>
{{ props.storeData.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">
{{ props.storeData.transaction.backOfficeTransactions }}
</div>
<span class="mb-1">
<div>
<VChip
variant="tonal"
color="secondary"
style="inline-size:45px"
>
Min
</VChip>
{{ props.storeData.transaction.minBackOfficeTransactionDate }}
</div>
</span>
<span class="mb-1">
<div>
<VChip
variant="tonal"
color="secondary"
style="inline-size:45px"
>
Max
</VChip>
{{ props.storeData.transaction.maxBackOfficeTransactionDate }}
</div>
</span>
</div>
</div>
</VCol>
</VRow>
</template> </template>

View File

@ -1,8 +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> <template>
<div> <div>
<VCard title="STORE TAB ITEM"> <VRow>
<VCardText>We carefully crafted JWT flow so you can implement JWT with ease and with minimum efforts.</VCardText> <VCol cols="12">
<VCardText>Please read our JWT Documentation to get more out of JWT authentication.</VCardText> <!-- 👉 Item search -->
</VCard> <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> </div>
</template> </template>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,121 @@
<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:']
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) => {
const url = `vnc://${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 class="text-center">
<img
:src="logoVNC"
size="128"
>
<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>