feat: obi visualization

feat/issue-3/obi
Frédérik Benoist 2024-01-13 06:47:33 +01:00
parent 5419bb538e
commit 7353016937
12 changed files with 390 additions and 106 deletions

View File

@ -59,5 +59,6 @@
- vite-plugin-vue-devtools : [npm](https://www.npmjs.com/package/vite-plugin-vue-devtools), [GitHub](https://github.com/antfu/vite-plugin-vue-devtools) - Plugin Vite pour intégrer les outils de développement Vue.js
- vite-plugin-vue-layouts : [npm](https://www.npmjs.com/package/vite-plugin-vue-layouts), [GitHub](https://github.com/JohnCampionJr/vite-plugin-vue-layouts) - Plugin Vite pour la gestion des mises en page Vue.js
- vite-plugin-vuetify : [npm](https://www.npmjs.com/package/vite-plugin-vuetify), [GitHub](https://github.com/antfu/vite-plugin-vuetify) - Plugin Vite pour l'intégration de Vuetify
- vue-json-pretty : [npm](https://www.npmjs.com/package/vue-json-pretty), [GitHub](https://github.com/leezng/vue-json-pretty) - A pretty-print JSON Vue-component
- vue-shepherd : [npm](https://www.npmjs.com/package/vue-shepherd), [GitHub](https://github.com/hipstersmoothie/vue-shepherd) - Tour de guide pour les applications Vue.js
- vue-tsc : [npm](https://www.npmjs.com/package/vue-tsc), [GitHub](https://github.com/johnsoncodehk/vue-tsc) - Plugin TypeScript pour la compilation de fichiers `.vue`

View File

@ -47,6 +47,7 @@
"vue-chartjs": "^5.2.0",
"vue-flatpickr-component": "11.0.3",
"vue-i18n": "^9.5.0",
"vue-json-pretty": "^2.3.0",
"vue-prism-component": "^2.0.0",
"vue-router": "^4.2.5",
"vue3-apexcharts": "^1.4.4",

View File

@ -115,6 +115,9 @@ dependencies:
vue-i18n:
specifier: ^9.5.0
version: 9.6.5(vue@3.3.8)
vue-json-pretty:
specifier: ^2.3.0
version: 2.3.0(vue@3.3.8)
vue-prism-component:
specifier: ^2.0.0
version: 2.0.0
@ -7978,6 +7981,15 @@ packages:
'@vue/devtools-api': 6.5.1
vue: 3.3.8(typescript@5.2.2)
/vue-json-pretty@2.3.0(vue@3.3.8):
resolution: {integrity: sha512-iBul6Xg7vZfMV2MQC/gGtzbyg8FLk6cJ8KG91f37UEkQyXqHg91VQJ24bDBXNVuOSP04BUKxWagD3V2N/WEy0g==}
engines: {node: '>= 10.0.0', npm: '>= 5.0.0'}
peerDependencies:
vue: '>=3.0.0'
dependencies:
vue: 3.3.8(typescript@5.2.2)
dev: false
/vue-prism-component@2.0.0:
resolution: {integrity: sha512-1ofrL+GCZOv4HqtX5W3EgkhSAgadSeuD8FDTXbwhLy8kS+28RCR8t2S5VTeM9U/peAaXLBpSgRt3J25ao8KTeg==}
dev: false

View File

@ -1,4 +1,6 @@
import { useStorage } from '@vueuse/core'
import { setDefaultOptions } from 'date-fns'
import { enUS, fr } from 'date-fns/locale'
import flatpickr from 'flatpickr'
import { French } from 'flatpickr/dist/l10n/fr'
import { useTheme } from 'vuetify'
@ -19,10 +21,14 @@ const _syncAppRtl = () => {
if (locale.value === 'fr') {
if (flatpickr.l10ns.fr)
flatpickr.localize(French)
setDefaultOptions({ locale: fr })
}
else {
if (flatpickr.l10ns.en)
flatpickr.localize(flatpickr.l10ns.en)
setDefaultOptions({ locale: enUS })
}
// watch and change lang attribute of html on language change
@ -39,10 +45,14 @@ const _syncAppRtl = () => {
if (storedLang.value === 'fr') {
if (flatpickr.l10ns.fr)
flatpickr.localize(flatpickr.l10ns.fr)
setDefaultOptions({ locale: fr })
}
else {
if (flatpickr.l10ns.en)
flatpickr.localize(flatpickr.l10ns.en)
setDefaultOptions({ locale: enUS })
}
// set isAppRtl value based on selected language

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -38,5 +38,13 @@ export const useApi = createFetch({
return { data: parsedData, response }
},
onFetchError(ctx) {
const errorData = ctx.data ? JSON.parse(ctx.data) : { title: 'Hunter x Hunter' }
const status = ctx.response ? ctx.response.status : 'unknown'
ctx.error = { status, error: errorData.error || errorData.title }
return ctx
},
},
})

View File

@ -12,9 +12,6 @@ interface transaction {
transactionDate: Date
transactionNo: string
transactionTypeDescription: string
transactionSubtotal: number
transactionTax: number
transactionTotal: number
}
interface Common {
@ -22,8 +19,6 @@ interface Common {
requestingLocationCd: string
requestingSystemCd: string
customerId: string
customerFirstName: string
customerLastName: string
status: string
submitOrdMsg: string
fdateCreation: Date
@ -66,3 +61,26 @@ export interface Order {
lines: Lines[]
history: History[]
}
export interface OrderOms {
common: OrderOmsCommon
oms: Proximis
}
export interface OrderOmsCommon {
urlApp: string
}
export interface Proximis {
item: ProximisItem
}
export interface ProximisItem {
id: number
modificationDate: Date
code: string
fulfillSystem: string
creationDate: Date
fulfillStoreCode: string
status: string
}

View File

@ -16,12 +16,21 @@ const tabs = [
{ title: 'Flow' },
]
const { data, error } = await useApi<Order>(`/obi/order/${route.params.id}`)
const { data, error, execute: fetchOrder } = await useApi<Order>(`/obi/order/${route.params.id}`)
if (error.value)
console.log(error.value)
else if (data.value)
orderData.value = data.value
const handleData = () => {
if (error.value)
console.log(error.value)
else if (data.value)
orderData.value = data.value
}
const refreshData = async () => {
await fetchOrder()
handleData()
}
handleData()
</script>
<template>
@ -36,33 +45,40 @@ else if (data.value)
<div>
<!-- 👉 Header -->
<div class="d-flex justify-space-between align-center flex-wrap gap-y-4 mb-6">
<div>
<div class="d-flex gap-2 align-center mb-0 flex-wrap">
<h4 class="text-h3 font-weight-medium">
Order Id: {{ orderData && orderData.common.orderId }}
</h4>
<div class="d-flex gap-2">
<VChip
label
color="success"
size="x-large"
>
{{ orderData && orderData.common.statusData.code }}
</VChip>
<VChip
label
size="x-large"
:color="orderData && orderData.common.consumed ? 'success' : 'error'"
>
{{ orderData && orderData.common.consumed ? 'Consumed' : 'Not consumed' }}
</VChip>
</div>
<div
v-if="orderData"
class="d-flex gap-2 align-center mb-0 flex-wrap"
>
<h4 class="text-h3 font-weight-medium">
Order: {{ orderData.common.orderId }}
</h4>
<div class="d-flex gap-2">
<VChip
label
color="success"
size="x-large"
>
{{ orderData.common.statusData.code }}
</VChip>
<VChip
label
size="x-large"
:color="orderData.common.consumed ? 'success' : 'error'"
>
{{ orderData.common.consumed ? 'Consumed' : 'Not consumed' }}
</VChip>
</div>
</div>
<div v-else>
Order not found ...
</div>
<!-- 👉 Reload button -->
<div class="d-flex gap-4">
<VBtn
variant="tonal"
color="primary"
@click="refreshData"
>
Reload
</VBtn>

View File

@ -1,4 +1,5 @@
<script setup lang="ts">
import { format } from 'date-fns'
import { VDataTableServer } from 'vuetify/labs/VDataTable'
import { paginationMeta } from '@api-utils/paginationMeta'
@ -16,8 +17,8 @@ const searchQuery = ref('')
// Data table options
const itemsPerPage = ref(10)
const page = ref(1)
const sortBy = ref()
const orderBy = ref()
const sortBy = ref('meta.id')
const orderBy = ref('desc')
// Data table Headers
const headers = [
@ -28,11 +29,14 @@ const headers = [
{ title: 'Type', key: 'common.transaction.transactionTypeDescription' },
]
// Update data table options
const updateOptions = (options: any) => {
page.value = options.page
sortBy.value = options.sortBy[0]?.key
orderBy.value = options.sortBy[0]?.order
// updateOptions was called systematically by VDataTableServer with an empty object
if (options.sortBy[0]?.key) {
sortBy.value = options.sortBy[0].key
orderBy.value = options.sortBy[0].order
}
}
const resolveStatus = (status: string) => {
@ -48,13 +52,25 @@ const resolveStatus = (status: string) => {
return { text: status, color: 'error' }
}
const sortByMapping = (sortby: string) => {
const headerMapping: { [key: string]: string } = {
'meta.id': 'request_id',
'common.orderId': 'order_id',
'common.transaction.transactionDate': 'transaction_date',
'common.statusData.code': 'status',
'common.transaction.transactionTypeDescription': 'transaction_type_description',
}
return headerMapping[sortby] || sortby
}
const { data: ordersData } = await useApi<any>(createUrl('/obi/order',
{
query: {
q: searchQuery,
page,
itemsPerPage,
sortBy,
sortBy: sortByMapping(sortBy.value),
orderBy,
},
},
@ -164,6 +180,8 @@ const totalOrder = computed(() => ordersData.value.total)
<VDataTableServer
v-model:items-per-page="itemsPerPage"
v-model:page="page"
v-model:sort-desc="orderBy"
v-model:sort-by:="sortBy"
:headers="headers"
:items="orders"
:items-length="totalOrder"
@ -181,6 +199,11 @@ const totalOrder = computed(() => ordersData.value.total)
</RouterLink>
</template>
<!-- Date -->
<template #item.common.transaction.transactionDate="{ item }">
{{ format(item.common.transaction.transactionDate, "EEEE d MMMM yyyy HH:mm:ss") }}
</template>
<!-- Status -->
<template #item.common.statusData.code="{ item }">
<VChip

View File

@ -0,0 +1,239 @@
<script setup lang="ts">
import { format } from 'date-fns'
import VueJsonPretty from 'vue-json-pretty'
import type { Order, OrderOms } from '@/models/Order'
import logoProximis from '@images/misc/logo-proximis.png'
import 'vue-json-pretty/lib/styles.css'
const props = defineProps<Props>()
interface Props {
orderData: Order
}
const omsData = ref<OrderOms>()
const omsError = ref()
const OrderMessageDialogVisible = ref(false)
const OrderMessageData = ref()
const { data: apiDataOms, error: apiErrorOms } = await useApi<any>(`/proximis/project/fulfillment/${props.orderData.common.orderId}`)
if (apiErrorOms.value)
omsError.value = `${apiErrorOms.value.status} - ${apiErrorOms.value.error}`
else
omsData.value = apiDataOms.value
const openOrderMessageDialog = async (): Promise<void> => {
const { data: apiDataMessage, error: apiErrorMessage } = await useApi<any>(`/obi/order/${props.orderData.meta.id}/orderMessage`)
if (apiErrorMessage.value) {
OrderMessageData.value = `${apiErrorMessage.value.status} - ${apiErrorMessage.value.error}`
OrderMessageDialogVisible.value = true
}
else {
OrderMessageData.value = apiDataMessage.value
OrderMessageDialogVisible.value = true
}
}
const openOmsOrderPage = (): void => {
// Check if omsData is defined before accessing its value property
if (omsData.value) {
const url = `${omsData.value.common.urlApp}/ua.php/orderManager/Order/${omsData.value.oms.item.id}`
window.open(url, '_blank')
}
else {
// Handle the case when omsData is not defined
console.error('omsData is not defined')
}
}
</script>
<template>
<!-- 👉 Proximis -->
<VCard>
<VRow no-gutters>
<VCol cols="6">
<VCardText class="pa-4">
<h6 class="text-lg text-no-wrap font-weight-medium">
Proximis
</h6>
<p class="mb-2">
OMS Partner
</p>
</VCardText>
</VCol>
<VCol
cols="6"
class="text-right"
>
<VCardText class="pa-3">
<VImg
:src="logoProximis"
height="50"
/>
</VCardText>
</VCol>
<VCol cols="12">
<VCardText>
<div v-if="omsData">
<VList class="card-list mt-0">
<div class="text-disabled text-uppercase text-sm">
Transaction details
</div>
<VListItem>
<div class="text-body-1 mt-2">
<span class="font-weight-bold me-2">Id:</span>
<span>
{{ omsData.oms.item.id }} / {{ omsData.oms.item.code }}
</span>
</div>
</VListItem>
</VList>
<VList class="card-list mt-2">
<VListItem>
<div class="text-body-1">
<span class="font-weight-bold me-2">Last status:</span>
<span>
{{ omsData.oms.item.status }}
</span>
</div>
</VListItem>
</VList>
<VList class="card-list mt-2">
<VListItem>
<div class="text-body-1">
<span class="font-weight-bold me-2">Added:</span>
<span>
{{ format(omsData.oms.item.modificationDate, "EEEE d MMMM yyyy HH:mm:ss") }}
</span>
</div>
</VListItem>
</VList>
<VList class="card-list mt-2">
<VListItem>
<div class="text-body-1">
<span class="font-weight-bold me-2">fulfillSystem:</span>
<span>
{{ omsData.oms.item.fulfillSystem }} / {{ omsData.oms.item.fulfillStoreCode }}
</span>
</div>
</VListItem>
</VList>
<VList class="card-list mt-2">
<VListItem>
<div class="text-body-1">
<span class="font-weight-bold me-2">Modified:</span>
<span>
{{ format(omsData.oms.item.creationDate, "EEEE d MMMM yyyy HH:mm:ss") }}
</span>
</div>
</VListItem>
</VList>
</div>
<div v-else>
Error: {{ omsError }}
</div>
</VCardText>
</VCol>
<VDivider />
<VCol cols="6">
<VCardText class="pa-4">
<VBtn
class="mt-0"
@click="openOrderMessageDialog"
>
Transaction
</VBtn>
</VCardText>
</VCol>
<VCol
v-if="omsData"
cols="6"
class="text-right"
>
<VCardText class="pa-4">
<VBtn
class="mt-0"
@click="openOmsOrderPage"
>
Open order
</VBtn>
</VCardText>
</VCol>
</VRow>
</VCard>
<VDialog
v-model="OrderMessageDialogVisible"
fullscreen
:scrim="false"
transition="dialog-bottom-transition"
>
<!-- Dialog Content -->
<VCard>
<!-- Toolbar -->
<div>
<VToolbar color="primary">
<VBtn
icon
variant="plain"
@click="OrderMessageDialogVisible = false"
>
<VIcon
color="white"
icon="tabler-x"
/>
</VBtn>
<VToolbarTitle>Order transaction</VToolbarTitle>
<VSpacer />
<VToolbarItems>
<VBtn
variant="text"
@click="OrderMessageDialogVisible = false"
>
Close
</VBtn>
</VToolbarItems>
</VToolbar>
</div>
<!-- pretty json viewer -->
<VCardText>
<VueJsonPretty
:show-length="false"
show-line-number
show-icon
:show-line="false"
:data="OrderMessageData"
/>
</VCardText>
</VCard>
</VDialog>
</template>
<style lang="scss">
.dialog-bottom-transition-enter-active,
.dialog-bottom-transition-leave-active {
transition: transform 0.2s ease-in-out;
}
.full-screen-dialog-list{
.v-list-item[tabindex="-2"].v-list-item--active{
.v-list-item-action{
.v-icon{
color: #fff;
}
}
}
}
</style>

View File

@ -1,17 +1,10 @@
import { propsToCopy } from '@iconify/utils/lib/icon-set/get-icons';
<script setup lang="ts">
import { format } from 'date-fns'
import type { Order } from '@/models/Order'
import OrderOms from '@/views/pages/order/view/OrderOms.vue'
const props = defineProps<Props>()
const resolveConsumed = (consumed: boolean) => {
if (consumed)
return { text: 'Consumed', color: 'success' }
else
return { text: 'Not consumed', color: 'error' }
}
interface Props {
orderData: Order
}
@ -19,9 +12,11 @@ interface Props {
<template>
<VRow>
<!-- SECTION Order -->
<VCol cols="12">
<VCard v-if="props.orderData">
<VCard
v-if="props.orderData"
class="mb-4"
>
<!-- 👉 Order Details -->
<VCardText>
<div class="text-disabled text-uppercase text-sm">
@ -44,19 +39,11 @@ interface Props {
</span>
</div>
</VListItem>
<VListItem>
<div class="text-body-1">
<span class="font-weight-bold me-2">Cust. name:</span>
<span>
{{ props.orderData.common.customerLastName }} {{ props.orderData.common.customerFirstName }}
</span>
</div>
</VListItem>
<VListItem>
<div class="text-body-1">
<span class="font-weight-bold me-2">Date creation:</span>
<span>
{{ format(props.orderData.common.fdateCreation, "yyyy-MM-dd HH:mm:ss") }}
{{ format(props.orderData.common.fdateCreation, "EEEE d MMMM yyyy HH:mm:ss") }}
</span>
</div>
</VListItem>
@ -85,7 +72,7 @@ interface Props {
<div class="text-body-1">
<span class="font-weight-bold me-2">Date:</span>
<span>
{{ format(props.orderData.common.transaction.transactionDate, "yyyy-MM-dd HH:mm:ss") }}
{{ format(props.orderData.common.transaction.transactionDate, "EEEE d MMMM yyyy HH:mm:ss") }}
</span>
</div>
</VListItem>
@ -99,30 +86,15 @@ interface Props {
</VListItem>
</VList>
</VCardText>
<VCardText class="text-center">
<VBtn>
Edit XML Details
</VBtn>
</VCardText>
<VCardText class="text-center">
<VBtn>
Proximis
</VBtn>
</VCardText>
<VCardText class="text-center">
<VBtn>
SQL Obi
</VBtn>
</VCardText>
</VCard>
<!-- 👉 OMS Proximis -->
<OrderOms :order-data="orderData" />
</VCol>
</vrow>
</VRow>
</template>
<style lang="scss" scoped>
<style lang="scss">
.card-list {
--v-card-list-gap: 0.75rem;
}

View File

@ -18,6 +18,7 @@ const headers = computed(() => [
{ title: t('Qty'), key: 'orderedLineQty' },
{ title: t('Date'), key: 'transactionDate' },
{ title: t('Status'), key: 'status' },
{ title: t('Consumed'), key: 'consumed' },
{ title: t('Created'), key: 'fdateCreation' },
])
</script>
@ -39,7 +40,7 @@ const headers = computed(() => [
:headers="headers"
:items="props.orderData.lines"
density="compact"
class="text-no-wrap dt-row-striped"
class="text-no-wrap"
expand-on-click
>
<!-- Expanded Row Data -->
@ -86,42 +87,25 @@ const headers = computed(() => [
{{ format(new Date(item.transactionDate), 'dd/MM/yyyy HH:mm:ss') }}
</template>
<template #item.consumed="{ item }">
<VChip
label
size="x-small"
:color="item.consumed ? 'success' : 'error'"
>
{{ item.consumed ? 'Consumed' : 'Not consumed' }}
</VChip>
</template>
<template #item.fdateCreation="{ item }">
{{ format(new Date(item.fdateCreation), 'dd/MM/yyyy HH:mm:ss') }}
</template>
<template #bottom />
</VDataTable>
<VDivider />
<VCardText>
<div class="d-flex align-end flex-column">
<table class="text-high-emphasis">
<tbody>
<tr>
<td class="text-high-emphasis font-weight-medium">
Tax:
</td>
<td style="text-align: end;">
{{ parseFloat(orderData.common.transaction.transactionTax.toString()).toFixed(2) }}
</td>
</tr>
<tr>
<td class="text-high-emphasis font-weight-medium">
Total:
</td>
<td style="text-align: end;">
{{ parseFloat(orderData.common.transaction.transactionTotal.toString()).toFixed(2) }}
</td>
</tr>
</tbody>
</table>
</div>
</VCardText>
</VCard>
<!-- 👉 Order History -->
<VCard title="Order History">
<VCard title="Status order history">
<VCardText>
<VTimeline
truncate-line="both"