From 24503a190a5ec159ee170395cecfba6e3c21f285 Mon Sep 17 00:00:00 2001 From: Edie Lemoine <edie@myparcel.nl> Date: Wed, 15 Mar 2023 17:43:46 +0100 Subject: [PATCH] feat: make bulk edit/export/print work --- .../src/components/DefaultSelectInput.vue | 2 +- apps/admin/src/common.ts | 3 +- .../queries/orders/useFetchOrdersQuery.ts | 6 +- .../Modals/ShipmentOptionsModal.vue | 56 ++++++------- .../Modals/ShipmentOptionsModalForm.vue | 24 +----- .../OrderBox/OrderShipmentsTableRow.vue | 2 +- .../components/common/ShipmentOptionsForm.vue | 7 +- .../composables/useInputWithOptionsContext.ts | 6 +- .../frontend/core/src/composables/useOrder.ts | 18 ++++- .../core/src/forms/helpers/setFieldProp.ts | 9 +-- .../createShipmentOptionsForm.ts | 81 ++++++++++++------- .../helpers/addBulkEditNotification.ts | 18 +++++ .../helpers/getCarrierOptions.ts | 3 +- .../helpers/getPackageTypes.ts | 4 +- .../forms/shipmentOptions/helpers/index.ts | 1 + .../core/src/forms/shipmentOptions/index.ts | 1 + .../src/sdk/composables/api/useCarrier.ts | 6 +- .../core/src/stores/useNotificationStore.ts | 25 +++--- .../frontend/core/src/stores/useQueryStore.ts | 6 +- libs/frontend/core/src/types/common.types.ts | 10 ++- .../src/utils/forms/createShipmentFormName.ts | 8 +- libs/frontend/core/src/utils/getOrderId.ts | 3 +- 22 files changed, 174 insertions(+), 125 deletions(-) create mode 100644 libs/frontend/core/src/forms/shipmentOptions/helpers/addBulkEditNotification.ts diff --git a/apps/admin-components/src/components/DefaultSelectInput.vue b/apps/admin-components/src/components/DefaultSelectInput.vue index 7f6f83d53..307feca5b 100644 --- a/apps/admin-components/src/components/DefaultSelectInput.vue +++ b/apps/admin-components/src/components/DefaultSelectInput.vue @@ -23,5 +23,5 @@ import {ElementInstance, OptionsProp, useSelectInputContext} from '@myparcel-pdk const props = defineProps<{element: ElementInstance<OptionsProp>; modelValue: string | number}>(); const emit = defineEmits<(e: 'update:modelValue', value: string | number) => void>(); -const {options} = useSelectInputContext(props, emit); +const {options, id, model} = useSelectInputContext(props, emit); </script> diff --git a/apps/admin/src/common.ts b/apps/admin/src/common.ts index c8eaee1d4..6e2710510 100644 --- a/apps/admin/src/common.ts +++ b/apps/admin/src/common.ts @@ -1,7 +1,6 @@ export type { Account, AdminComponentMap, - AdminComponent, Base, Carrier, Form, @@ -84,4 +83,4 @@ export { webhooksDeleteAction, } from '@myparcel-pdk/frontend-core/src'; -export {AdminView, Size, Status, Variant} from '@myparcel-pdk/common/src'; +export {AdminComponent, AdminView, Size, Status, Variant} from '@myparcel-pdk/common/src'; diff --git a/libs/frontend/core/src/actions/composables/queries/orders/useFetchOrdersQuery.ts b/libs/frontend/core/src/actions/composables/queries/orders/useFetchOrdersQuery.ts index a0a80c91a..4d6674364 100644 --- a/libs/frontend/core/src/actions/composables/queries/orders/useFetchOrdersQuery.ts +++ b/libs/frontend/core/src/actions/composables/queries/orders/useFetchOrdersQuery.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import {useQuery, useQueryClient} from '@tanstack/vue-query'; +import {QueryKey, useQuery, useQueryClient} from '@tanstack/vue-query'; import {BackendEndpoint} from '@myparcel-pdk/common/src'; import {EndpointResponse} from '../../../../types'; import {QUERY_KEY_ORDER} from '../queryKeys'; @@ -7,8 +7,8 @@ import {encodeArrayParameter} from '../../../../utils'; import {fillOrderQueryData} from '../../../../pdk'; import {usePdkAdminApi} from '../../../../sdk'; -export const useFetchOrdersQuery = (externalIdentifier: string) => { - const queryKey = [QUERY_KEY_ORDER, {id: externalIdentifier}] as const; +export const useFetchOrdersQuery = (externalIdentifier?: string) => { + const queryKey: QueryKey = [QUERY_KEY_ORDER, ...(externalIdentifier ? [{id: externalIdentifier}] : [])] as const; const queryClient = useQueryClient(); return useQuery<EndpointResponse<BackendEndpoint.FetchOrders>>( diff --git a/libs/frontend/core/src/components/Modals/ShipmentOptionsModal.vue b/libs/frontend/core/src/components/Modals/ShipmentOptionsModal.vue index 2f3244cf2..9b57d669e 100644 --- a/libs/frontend/core/src/components/Modals/ShipmentOptionsModal.vue +++ b/libs/frontend/core/src/components/Modals/ShipmentOptionsModal.vue @@ -7,8 +7,9 @@ </PdkModal> </template> -<script lang="ts"> -import {defineAsyncComponent, defineComponent} from 'vue'; +<script setup lang="ts"> +import {BACKEND_ENDPOINTS_ORDERS, BackendEndpoint} from '@myparcel-pdk/common/src'; +import {computed, defineAsyncComponent} from 'vue'; import { modalCloseAction, orderExportAction, @@ -18,38 +19,33 @@ import { } from '../../actions'; import {usePluginSettings, useStoreQuery} from '../../composables'; import {AdminModalKey} from '../../types'; -import {BackendEndpoint} from '@myparcel-pdk/common/src'; import {defineActions} from '../../services'; +import {get} from '@vueuse/core'; /** * Shipment options modal. Opened by clicking the "Create" button in the "Labels" column in the orders list. */ -export default defineComponent({ - name: 'ShipmentOptionsModal', - components: { - ShipmentOptionsModalForm: defineAsyncComponent(() => import('./ShipmentOptionsModalForm.vue')), - }, - - setup: () => { - const pluginSettings = usePluginSettings(); - const {orderMode} = pluginSettings.general; - - const exportOrdersQuery = useStoreQuery(BackendEndpoint.ExportOrders); - - return { - modalKey: AdminModalKey.ShipmentOptions, - actions: defineActions([ - { - ...modalCloseAction, - disabled: exportOrdersQuery.isLoading, - }, - { - ...ordersUpdateAction, - disabled: exportOrdersQuery.isLoading, - }, - ...(orderMode ? [orderExportAction] : [orderExportToShipmentsAction, ordersExportPrintShipmentsAction]), - ]), - }; - }, + +// eslint-disable-next-line @typescript-eslint/naming-convention +const ShipmentOptionsModalForm = defineAsyncComponent(() => import('./ShipmentOptionsModalForm.vue')); + +const pluginSettings = usePluginSettings(); + +const {orderMode} = pluginSettings.general; + +const orderQueries = BACKEND_ENDPOINTS_ORDERS.map((endpoint: BackendEndpoint) => useStoreQuery(endpoint)); + +const modalKey = AdminModalKey.ShipmentOptions; + +const actions = computed(() => { + const disabled = orderQueries.some((query) => get(query.isLoading)); + + const actions = [ + modalCloseAction, + ordersUpdateAction, + ...(orderMode ? [orderExportAction] : [orderExportToShipmentsAction, ordersExportPrintShipmentsAction]), + ]; + + return defineActions(actions.map((action) => ({...action, disabled}))); }); </script> diff --git a/libs/frontend/core/src/components/Modals/ShipmentOptionsModalForm.vue b/libs/frontend/core/src/components/Modals/ShipmentOptionsModalForm.vue index 500501bb6..471483a98 100644 --- a/libs/frontend/core/src/components/Modals/ShipmentOptionsModalForm.vue +++ b/libs/frontend/core/src/components/Modals/ShipmentOptionsModalForm.vue @@ -1,28 +1,10 @@ <template> - <ShipmentOptionsForm - v-if="order" - :order="order" /> + <ShipmentOptionsForm v-if="queries.some((query) => query.data)" /> </template> <script setup lang="ts"> import ShipmentOptionsForm from '../common/ShipmentOptionsForm.vue'; -import {computed} from 'vue'; -import {useFetchOrdersQuery} from '../../actions'; -import {useModalStore} from '../../stores'; +import {useOrders} from '../../composables/useOrder'; -const modalStore = useModalStore(); - -const order = computed(() => { - if (!modalStore.context) { - return null; - } - - const orderQuery = useFetchOrdersQuery(modalStore.context); - - if (!orderQuery.data.value) { - return null; - } - - return orderQuery.data.value; -}); +const queries = useOrders(); </script> diff --git a/libs/frontend/core/src/components/OrderBox/OrderShipmentsTableRow.vue b/libs/frontend/core/src/components/OrderBox/OrderShipmentsTableRow.vue index bebb81cbc..e50443a90 100644 --- a/libs/frontend/core/src/components/OrderBox/OrderShipmentsTableRow.vue +++ b/libs/frontend/core/src/components/OrderBox/OrderShipmentsTableRow.vue @@ -1,5 +1,5 @@ <template> - <PdkTableRow :loading="loading"> + <PdkTableRow> <PdkTableCol> <PdkCheckboxInput v-model="selected" diff --git a/libs/frontend/core/src/components/common/ShipmentOptionsForm.vue b/libs/frontend/core/src/components/common/ShipmentOptionsForm.vue index 8a960bd65..358ba6a9f 100644 --- a/libs/frontend/core/src/components/common/ShipmentOptionsForm.vue +++ b/libs/frontend/core/src/components/common/ShipmentOptionsForm.vue @@ -6,11 +6,10 @@ import {MagicForm} from '@myparcel/vue-form-builder/src'; import {createShipmentOptionsForm} from '../../forms'; import {get} from '@vueuse/core'; -import {markRaw} from 'vue'; -import {useOrder} from '../../composables/useOrder'; +import {useOrders} from '../../composables/useOrder'; -const query = useOrder(); +const queries = useOrders(); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -const shipmentOptionsForm = createShipmentOptionsForm(markRaw(get(query.data)!)); +const shipmentOptionsForm = createShipmentOptionsForm(queries.map((query) => get(query.data)!)); </script> diff --git a/libs/frontend/core/src/composables/useInputWithOptionsContext.ts b/libs/frontend/core/src/composables/useInputWithOptionsContext.ts index ea23512b7..484ee4e15 100644 --- a/libs/frontend/core/src/composables/useInputWithOptionsContext.ts +++ b/libs/frontend/core/src/composables/useInputWithOptionsContext.ts @@ -1,10 +1,10 @@ import {ComputedRef, Ref, WritableComputedRef, computed, onMounted, watch} from 'vue'; import {ElementInstance, OptionsProp} from '../types'; +import {get, useVModel} from '@vueuse/core'; import {SelectOptionWithLabel} from '@myparcel-pdk/common'; import {generateFieldId} from '../utils'; import {translateSelectOption} from '../helpers'; import {useLanguage} from './translations'; -import {useVModel} from '@vueuse/core'; export type SelectInputProps<T = unknown> = { modelValue: T; @@ -55,7 +55,7 @@ export const useInputWithOptionsContext: UseInputWithOptionsContext = (props, em watch( options, (newOptions) => { - const hasExistingValue = model.value && newOptions.some((option) => option.value === model.value); + const hasExistingValue = get(model) && newOptions.some((option) => option.value === get(model)); if (hasExistingValue || newOptions.length === 0) { return; @@ -63,7 +63,7 @@ export const useInputWithOptionsContext: UseInputWithOptionsContext = (props, em model.value = newOptions[0].value; }, - {immediate: Number(options.value?.length) > 0}, + {immediate: Number(get(options)?.length) > 0}, ); }); diff --git a/libs/frontend/core/src/composables/useOrder.ts b/libs/frontend/core/src/composables/useOrder.ts index 5036a2745..60e31698c 100644 --- a/libs/frontend/core/src/composables/useOrder.ts +++ b/libs/frontend/core/src/composables/useOrder.ts @@ -1,10 +1,24 @@ import {BackendEndpoint} from '@myparcel-pdk/common/src'; import {ResolvedQuery} from '../stores'; import {getOrderId} from '../utils'; +import {toArray} from '@myparcel/ts-utils'; import {useStoreQuery} from './useStoreQuery'; export const useOrder = (externalIdentifier?: string): ResolvedQuery<`${BackendEndpoint.FetchOrders}.${string}`> => { - externalIdentifier ??= getOrderId(); + const resolvedExternalIdentifier = externalIdentifier ?? getOrderId(); - return useStoreQuery(BackendEndpoint.FetchOrders, externalIdentifier); + if (Array.isArray(resolvedExternalIdentifier)) { + throw new Error('use useOrders for multiple orders'); + } + + return useStoreQuery(BackendEndpoint.FetchOrders, resolvedExternalIdentifier); +}; + +export const useOrders = ( + externalIdentifiers?: string[], +): ResolvedQuery<`${BackendEndpoint.FetchOrders}.${string}`>[] => { + return toArray(externalIdentifiers ?? getOrderId()).map((externalIdentifier) => { + console.log(externalIdentifier); + return useOrder(externalIdentifier); + }); }; diff --git a/libs/frontend/core/src/forms/helpers/setFieldProp.ts b/libs/frontend/core/src/forms/helpers/setFieldProp.ts index 0b8c212ba..e96088bef 100644 --- a/libs/frontend/core/src/forms/helpers/setFieldProp.ts +++ b/libs/frontend/core/src/forms/helpers/setFieldProp.ts @@ -1,5 +1,6 @@ import {ElementInstance} from '../../types'; import {FormInstance} from '@myparcel/vue-form-builder/src'; +import {get} from '@vueuse/core'; import {isOfType} from '@myparcel/ts-utils'; type SetFieldProp = { @@ -13,7 +14,7 @@ export const setFieldProp: SetFieldProp = (arg1, arg2, arg3, arg4) => { let value = arg3; if (isOfType<FormInstance>(arg1, 'fields')) { - const foundField = arg1.fields.value.find((field) => field.name === arg2); + const foundField = get(arg1.fields).find((field) => field.name === arg2); if (!foundField) { throw new Error(`Field ${arg2} not found`); @@ -28,11 +29,5 @@ export const setFieldProp: SetFieldProp = (arg1, arg2, arg3, arg4) => { return; } - console.log({ - field: field.name, - key, - value, - }); - field.props[key] = value as never; }; diff --git a/libs/frontend/core/src/forms/shipmentOptions/createShipmentOptionsForm.ts b/libs/frontend/core/src/forms/shipmentOptions/createShipmentOptionsForm.ts index 04786e77f..40ffea9de 100644 --- a/libs/frontend/core/src/forms/shipmentOptions/createShipmentOptionsForm.ts +++ b/libs/frontend/core/src/forms/shipmentOptions/createShipmentOptionsForm.ts @@ -14,55 +14,76 @@ import { SAME_DAY_DELIVERY, SIGNATURE, } from './field'; -import {AdminComponent, Plugin} from '@myparcel-pdk/common/src'; +import {AdminComponent, Plugin, SelectOptionWithPlainLabel} from '@myparcel-pdk/common/src'; import {AdminContextKey, AdminModalKey, ElementInstance} from '../../types'; import {CarrierName, PackageTypeName} from '@myparcel/constants'; +import {OneOrMore, toArray} from '@myparcel/ts-utils'; +import {addBulkEditNotification, getPackageTypes, hasShipmentOption, isPackageTypePackage} from './helpers'; import {defineFormField, resolveFormComponent, setFieldProp} from '../helpers'; -import {getPackageTypes, hasShipmentOption, isPackageTypePackage} from './helpers'; +import {markRaw, ref} from 'vue'; import {useAdminConfig, useContext, useLocalizedFormatter} from '../../composables'; import {createShipmentFormName} from '../../utils'; import {defineForm} from '@myparcel/vue-form-builder/src'; +import {get} from '@vueuse/core'; import {getFormattedInsurancePossibilities} from './helpers/getFormattedInsurancePossibilities'; -import {ref} from 'vue'; import {useCarrier} from '../../sdk'; import {useModalStore} from '../../stores'; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types,max-lines-per-function,complexity -export const createShipmentOptionsForm = (order: Plugin.ModelPdkOrder) => { +export const createShipmentOptionsForm = (orders?: OneOrMore<Plugin.ModelPdkOrder>) => { + const ordersArray = toArray(orders ?? []); + ordersArray.forEach((order) => markRaw(order)); + const dynamicContext = useContext(AdminContextKey.Dynamic); + const config = useAdminConfig(); const modalStore = useModalStore(); - - const carrierNames = dynamicContext.carrierOptions.map((options) => options.carrier.name); const formatter = useLocalizedFormatter(); - return defineForm(createShipmentFormName(order.externalIdentifier), { - ...(modalStore.opened === AdminModalKey.ShipmentOptions ? config.formConfigOverrides?.modal : null), + const isBulk = ordersArray.length > 1; + const isModal = modalStore.opened === AdminModalKey.ShipmentOptions; + + if (isBulk) { + addBulkEditNotification(isModal); + } + + const values: Partial<Plugin.ModelPdkOrder> = isBulk ? {} : ordersArray[0]; + + return defineForm(createShipmentFormName(values.externalIdentifier), { + ...(isModal ? config.formConfigOverrides?.modal : null), ...config.formConfigOverrides?.shipmentOptions, fields: [ defineFormField({ name: CARRIER, label: 'carrier', - ref: ref<CarrierName>(order.deliveryOptions?.carrier as CarrierName), + ref: ref<CarrierName>(values.deliveryOptions?.carrier as CarrierName), component: resolveFormComponent(AdminComponent.SelectInput), + props: { + options: [], + }, // @ts-expect-error todo onBeforeMount: async (field) => { - const carriers = await Promise.all( - carrierNames.map(async (name) => { - const carrier = useCarrier(name); - return carrier.suspense(); + const carrierSelectOptions = await Promise.all( + dynamicContext.carrierOptions.map(async (options): Promise<SelectOptionWithPlainLabel> => { + const query = useCarrier(options.carrier.name); + await query.suspense(); + const data = get(query.data); + + return { + plainLabel: data?.human ?? '', + value: data?.name ?? '', + }; }), ); - setFieldProp( - field, - PROP_OPTIONS, - carriers.map((carrier) => ({ - plainLabel: carrier.data?.human, - value: carrier.data?.name, - })), - ); + setFieldProp(field, PROP_OPTIONS, carrierSelectOptions); + + // if (!get(field.ref)) { + // field.ref = carrierSelectOptions[0].value; + // } + + field.afterUpdate(field); }, afterUpdate: (field) => { @@ -74,7 +95,7 @@ export const createShipmentOptionsForm = (order: Plugin.ModelPdkOrder) => { defineFormField({ name: LABEL_AMOUNT, label: 'label_amount', - ref: ref(order.deliveryOptions?.labelAmount ?? 1), + ref: ref(values.deliveryOptions?.labelAmount ?? 1), component: resolveFormComponent(AdminComponent.NumberInput), props: { min: 1, @@ -85,14 +106,14 @@ export const createShipmentOptionsForm = (order: Plugin.ModelPdkOrder) => { defineFormField({ name: PACKAGE_TYPE, label: 'shipment_options_package_type', - ref: ref<PackageTypeName>((order.deliveryOptions?.packageType as PackageTypeName) ?? PackageTypeName.Package), + ref: ref<PackageTypeName>((values.deliveryOptions?.packageType as PackageTypeName) ?? PackageTypeName.Package), component: resolveFormComponent(AdminComponent.SelectInput), }), defineFormField({ name: SIGNATURE, label: 'shipment_options_signature', - ref: ref(order.deliveryOptions?.shipmentOptions.signature ?? false), + ref: ref(values.deliveryOptions?.shipmentOptions.signature ?? false), component: resolveFormComponent(AdminComponent.ToggleInput), visibleWhen: ({form}) => isPackageTypePackage(form) && hasShipmentOption(form, 'signature'), }), @@ -100,7 +121,7 @@ export const createShipmentOptionsForm = (order: Plugin.ModelPdkOrder) => { defineFormField({ name: ONLY_RECIPIENT, label: 'shipment_options_only_recipient', - ref: ref(order.deliveryOptions?.shipmentOptions.onlyRecipient ?? false), + ref: ref(values.deliveryOptions?.shipmentOptions.onlyRecipient ?? false), component: resolveFormComponent(AdminComponent.ToggleInput), visibleWhen: ({form}) => isPackageTypePackage(form) && hasShipmentOption(form, 'onlyRecipient'), }), @@ -108,7 +129,7 @@ export const createShipmentOptionsForm = (order: Plugin.ModelPdkOrder) => { defineFormField({ name: AGE_CHECK, component: resolveFormComponent(AdminComponent.ToggleInput), - ref: ref(order.deliveryOptions?.shipmentOptions.ageCheck ?? false), + ref: ref(values.deliveryOptions?.shipmentOptions.ageCheck ?? false), label: 'shipment_options_age_check', visibleWhen: ({form}) => isPackageTypePackage(form) && hasShipmentOption(form, 'ageCheck'), }), @@ -116,7 +137,7 @@ export const createShipmentOptionsForm = (order: Plugin.ModelPdkOrder) => { defineFormField({ name: DIRECT_RETURN, component: resolveFormComponent(AdminComponent.ToggleInput), - ref: ref(order.deliveryOptions?.shipmentOptions.return ?? false), + ref: ref(values.deliveryOptions?.shipmentOptions.return ?? false), label: 'shipment_options_return', visibleWhen: ({form}) => isPackageTypePackage(form) && hasShipmentOption(form, 'return'), }), @@ -124,7 +145,7 @@ export const createShipmentOptionsForm = (order: Plugin.ModelPdkOrder) => { defineFormField({ name: LARGE_FORMAT, component: resolveFormComponent(AdminComponent.ToggleInput), - ref: ref(order.deliveryOptions?.shipmentOptions.largeFormat ?? false), + ref: ref(values.deliveryOptions?.shipmentOptions.largeFormat ?? false), label: 'shipment_options_large_format', visibleWhen: ({form}) => isPackageTypePackage(form) && hasShipmentOption(form, 'largeFormat'), }), @@ -132,7 +153,7 @@ export const createShipmentOptionsForm = (order: Plugin.ModelPdkOrder) => { defineFormField({ name: SAME_DAY_DELIVERY, component: resolveFormComponent(AdminComponent.ToggleInput), - ref: ref(order.deliveryOptions?.shipmentOptions.sameDayDelivery ?? false), + ref: ref(values.deliveryOptions?.shipmentOptions.sameDayDelivery ?? false), label: 'shipment_options_same_day_delivery', visibleWhen: ({form}) => isPackageTypePackage(form) && hasShipmentOption(form, 'sameDayDelivery'), }), @@ -140,7 +161,7 @@ export const createShipmentOptionsForm = (order: Plugin.ModelPdkOrder) => { defineFormField({ name: INSURANCE, component: resolveFormComponent(AdminComponent.SelectInput), - ref: ref(order.deliveryOptions?.shipmentOptions.insurance ?? 0), + ref: ref(values.deliveryOptions?.shipmentOptions.insurance ?? 0), label: 'shipment_options_insurance', props: { options: [], diff --git a/libs/frontend/core/src/forms/shipmentOptions/helpers/addBulkEditNotification.ts b/libs/frontend/core/src/forms/shipmentOptions/helpers/addBulkEditNotification.ts new file mode 100644 index 000000000..bd7cbbe5d --- /dev/null +++ b/libs/frontend/core/src/forms/shipmentOptions/helpers/addBulkEditNotification.ts @@ -0,0 +1,18 @@ +import {Notification, NotificationCategory} from '../../../types'; +import {Variant} from '@myparcel-pdk/common'; +import {useNotificationStore} from '../../../stores'; + +const TRANSLATION_KEY = 'bulk_orders_warning'; + +export const addBulkEditNotification = (isModal: boolean): void => { + const notificationStore = useNotificationStore(); + + notificationStore.add({ + id: TRANSLATION_KEY, + variant: Variant.Info, + category: isModal ? NotificationCategory.Modal : undefined, + content: `${TRANSLATION_KEY}_description`, + title: TRANSLATION_KEY, + timeout: false, + } as Notification); +}; diff --git a/libs/frontend/core/src/forms/shipmentOptions/helpers/getCarrierOptions.ts b/libs/frontend/core/src/forms/shipmentOptions/helpers/getCarrierOptions.ts index fd1aa8068..4aa938154 100644 --- a/libs/frontend/core/src/forms/shipmentOptions/helpers/getCarrierOptions.ts +++ b/libs/frontend/core/src/forms/shipmentOptions/helpers/getCarrierOptions.ts @@ -2,11 +2,12 @@ import {AdminContextKey} from '../../../types'; import {CARRIER} from '../field'; import {Carrier} from '@myparcel-pdk/common/src'; import {FormInstance} from '@myparcel/vue-form-builder/src'; +import {get} from '@vueuse/core'; import {useContext} from '../../../composables'; export const getCarrierOptions = (form: FormInstance): Carrier.ModelCarrierOptions | undefined => { const dynamicContext = useContext(AdminContextKey.Dynamic); - const chosenCarrier = form.model[CARRIER].ref.value; + const chosenCarrier = get(form.model[CARRIER].ref); return dynamicContext.carrierOptions.find((options) => options.carrier.name === chosenCarrier); }; diff --git a/libs/frontend/core/src/forms/shipmentOptions/helpers/getPackageTypes.ts b/libs/frontend/core/src/forms/shipmentOptions/helpers/getPackageTypes.ts index 82cbc3034..a78fd9e05 100644 --- a/libs/frontend/core/src/forms/shipmentOptions/helpers/getPackageTypes.ts +++ b/libs/frontend/core/src/forms/shipmentOptions/helpers/getPackageTypes.ts @@ -2,11 +2,9 @@ import {FormInstance} from '@myparcel/vue-form-builder/src'; import {PackageTypeName} from '@myparcel/constants'; import {SelectOption} from '@myparcel-pdk/common/src'; import {getCarrierOptions} from './getCarrierOptions'; -import {useLanguage} from '../../../composables'; export const getPackageTypes = (form?: FormInstance): SelectOption[] => { let array = Object.values(PackageTypeName); - const {translate} = useLanguage(); if (form) { const carrierOptions = getCarrierOptions(form); @@ -15,7 +13,7 @@ export const getPackageTypes = (form?: FormInstance): SelectOption[] => { } return array.map((name) => ({ - label: translate(`package_type_${name}`), + label: `package_type_${name}`, value: name, })); }; diff --git a/libs/frontend/core/src/forms/shipmentOptions/helpers/index.ts b/libs/frontend/core/src/forms/shipmentOptions/helpers/index.ts index e39190c92..49fe6a175 100644 --- a/libs/frontend/core/src/forms/shipmentOptions/helpers/index.ts +++ b/libs/frontend/core/src/forms/shipmentOptions/helpers/index.ts @@ -3,3 +3,4 @@ export * from './getInsurancePossibilities'; export * from './getPackageTypes'; export * from './hasShipmentOption'; export * from './isPackageTypePackage'; +export {addBulkEditNotification} from './addBulkEditNotification'; diff --git a/libs/frontend/core/src/forms/shipmentOptions/index.ts b/libs/frontend/core/src/forms/shipmentOptions/index.ts index 72648f225..9ddb3f471 100644 --- a/libs/frontend/core/src/forms/shipmentOptions/index.ts +++ b/libs/frontend/core/src/forms/shipmentOptions/index.ts @@ -1,3 +1,4 @@ export * from './createShipmentOptionsForm'; export * from './field'; export * from './helpers'; +export {addBulkEditNotification} from './helpers'; diff --git a/libs/frontend/core/src/sdk/composables/api/useCarrier.ts b/libs/frontend/core/src/sdk/composables/api/useCarrier.ts index 0f99166e5..dd4e37b3a 100644 --- a/libs/frontend/core/src/sdk/composables/api/useCarrier.ts +++ b/libs/frontend/core/src/sdk/composables/api/useCarrier.ts @@ -5,15 +5,15 @@ import {CarrierName} from '@myparcel/constants'; import {QUERY_KEY_CARRIERS} from './useCarriers'; import {useMyParcelApi} from '../useMyParcelApi'; -export const useCarrier = (carrier: CarrierName) => { +export const useCarrier = (carrier?: CarrierName) => { const queryClient = useQueryClient(); - const queryKey = [QUERY_KEY_CARRIERS, carrier]; + const queryKey = [QUERY_KEY_CARRIERS, ...(carrier ? [carrier] : [])] as const; return useQuery<EndpointResponse<GetCarrier>[number]>( queryKey, async () => { const sdk = useMyParcelApi(); - const carriers = await sdk.getCarrier({path: {carrier}}); + const carriers = await sdk.getCarrier(carrier ? {path: {carrier}} : undefined); return carriers[0]; }, diff --git a/libs/frontend/core/src/stores/useNotificationStore.ts b/libs/frontend/core/src/stores/useNotificationStore.ts index 250e9d57f..2415d2035 100644 --- a/libs/frontend/core/src/stores/useNotificationStore.ts +++ b/libs/frontend/core/src/stores/useNotificationStore.ts @@ -1,8 +1,9 @@ -import {Notification, NotificationCategory} from '../'; +import {Notification, NotificationCategory, NotificationId} from '../'; import {defineStore} from 'pinia'; +import {isEnumValue} from '@myparcel/ts-utils'; import {ref} from 'vue'; -let id = 0; +let autoId = 0; const DEFAULT_TIMEOUT = 5000; @@ -17,29 +18,33 @@ export const useNotificationStore = defineStore('notifications', () => { */ add(notification: Notification) { notification.category ??= NotificationCategory.General; - const notificationId = id++; + const id = notification.id ?? autoId++; - if (notification.timeout) { + if (notification.timeout !== false) { const timeout = typeof notification.timeout === 'number' ? notification.timeout : DEFAULT_TIMEOUT; setTimeout(() => { - this.remove(notificationId); + this.remove(id); }, timeout); } - notifications.value.push({...notification, id: notificationId}); + if (notifications.value.some((notification) => notification.id === id)) { + return; + } + + notifications.value.push({...notification, id}); }, /** * Remove one or more notifications from the store by category or id. */ - remove(input: number | NotificationCategory) { + remove(input: NotificationId | NotificationCategory) { let filter: (notification: Notification) => boolean; - if (typeof input === 'number') { - filter = (notification) => notification.id !== input; - } else { + if (isEnumValue(input, NotificationCategory)) { filter = (notification) => notification.category !== input; + } else { + filter = (notification) => notification.id !== input; } notifications.value = notifications.value.filter(filter); diff --git a/libs/frontend/core/src/stores/useQueryStore.ts b/libs/frontend/core/src/stores/useQueryStore.ts index c727e0794..671086bea 100644 --- a/libs/frontend/core/src/stores/useQueryStore.ts +++ b/libs/frontend/core/src/stores/useQueryStore.ts @@ -22,6 +22,7 @@ import {BackendEndpoint} from '@myparcel-pdk/common/src'; import {MutationMode} from '../services'; import {defineStore} from 'pinia'; import {getOrderId} from '../utils'; +import {toArray} from '@myparcel/ts-utils'; export type QueryKey = | BackendEndpoint @@ -132,8 +133,11 @@ export const useQueryStore = defineStore('query', () => { throw new Error('No order id found'); } - register(`${BackendEndpoint.FetchOrders}.${id}`, useFetchOrdersQuery(id)); + toArray(id).forEach((orderId) => { + register(`${BackendEndpoint.FetchOrders}.${orderId}`, useFetchOrdersQuery(orderId)); + }); + register(BackendEndpoint.FetchOrders, useFetchOrdersQuery()); register(BackendEndpoint.ExportOrders, useExportOrdersMutation(mode)); register(BackendEndpoint.PrintOrders, usePrintOrdersMutation()); register(BackendEndpoint.UpdateOrders, useUpdateOrdersMutation()); diff --git a/libs/frontend/core/src/types/common.types.ts b/libs/frontend/core/src/types/common.types.ts index bd0a90869..b917f5aa5 100644 --- a/libs/frontend/core/src/types/common.types.ts +++ b/libs/frontend/core/src/types/common.types.ts @@ -1,8 +1,16 @@ import {OneOrMore} from '@myparcel/ts-utils'; import {Variant} from '@myparcel-pdk/common/src'; +export type NotificationId = number | string; + export interface Notification { - id?: number; + /** + * ID of a notification. If not provided, a unique id will be generated. It may be a string, but make sure this string + * does not exist in NotificationCategory. + * + * @see NotificationCategory + */ + id?: NotificationId; category?: NotificationCategory; title?: string; content?: OneOrMore<string>; diff --git a/libs/frontend/core/src/utils/forms/createShipmentFormName.ts b/libs/frontend/core/src/utils/forms/createShipmentFormName.ts index a1ae5635f..9370e56f5 100644 --- a/libs/frontend/core/src/utils/forms/createShipmentFormName.ts +++ b/libs/frontend/core/src/utils/forms/createShipmentFormName.ts @@ -1,3 +1,9 @@ import {AdminModalKey} from '../../types'; -export const createShipmentFormName = (orderId: string): string => `${AdminModalKey.ShipmentOptions}_${orderId}`; +export const createShipmentFormName = (orderId?: string): string => { + if (!orderId) { + orderId = 'bulk'; + } + + return `${AdminModalKey.ShipmentOptions}_${orderId}`; +}; diff --git a/libs/frontend/core/src/utils/getOrderId.ts b/libs/frontend/core/src/utils/getOrderId.ts index 48c4c9baf..151a1c920 100644 --- a/libs/frontend/core/src/utils/getOrderId.ts +++ b/libs/frontend/core/src/utils/getOrderId.ts @@ -1,8 +1,9 @@ import {useInstanceContext, useModalOrder} from '../composables'; import {AdminInstance} from '../data'; import {AdminInstanceContextKey} from '../types'; +import {OneOrMore} from '@myparcel/ts-utils'; -export const getOrderId = (instance?: AdminInstance): undefined | string => { +export const getOrderId = (instance?: AdminInstance): undefined | OneOrMore<string> => { return ( useModalOrder() ?? instance?.context?.[AdminInstanceContextKey.OrderIdentifier] ??