mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
✨ feat(navigation): implement title management and enhance tab system This commit introduces a comprehensive title management system and extends the tab functionality with subtitle support, improving navigation clarity and user experience across the application. Key changes: Title Management System: - Add @isa/common/title-management library with dual approach: - IsaTitleStrategy for route-based static titles - usePageTitle() for component-based dynamic titles - Implement TitleRegistryService for nested component hierarchies - Automatic ISA prefix addition and TabService integration - Comprehensive test coverage (1,158 lines of tests) Tab System Enhancement: - Add subtitle field to tab schema for additional context - Update TabService API (addTab, patchTab) to support subtitles - Extend Zod schemas with subtitle validation - Update documentation with usage examples Routing Modernization: - Consolidate route guards using ActivateProcessIdWithConfigKeyGuard - Replace 4+ specific guards with generic config-key-based approach - Add title attributes to 100+ routes across all modules - Remove deprecated ProcessIdGuard in favor of ActivateProcessIdGuard Code Cleanup: - Remove deprecated preview component and related routes - Clean up unused imports and exports - Update TypeScript path aliases Dependencies: - Update package.json and package-lock.json - Add @isa/common/title-management to tsconfig path mappings Refs: #5351, #5418, #5419, #5420
312 lines
9.0 KiB
TypeScript
312 lines
9.0 KiB
TypeScript
import { createReducer, on } from '@ngrx/store';
|
|
import {
|
|
initialCheckoutState,
|
|
storeCheckoutAdapter,
|
|
} from './domain-checkout.state';
|
|
|
|
import * as DomainCheckoutActions from './domain-checkout.actions';
|
|
import { Dictionary } from '@ngrx/entity';
|
|
import { CheckoutEntity } from './defs/checkout.entity';
|
|
import { isNullOrUndefined } from '@utils/common';
|
|
|
|
const _domainCheckoutReducer = createReducer(
|
|
initialCheckoutState,
|
|
on(
|
|
DomainCheckoutActions.setShoppingCart,
|
|
(s, { processId, shoppingCart }) => {
|
|
const entity = getOrCreateCheckoutEntity({
|
|
processId,
|
|
entities: s.entities,
|
|
});
|
|
|
|
const addedShoppingCartItems =
|
|
shoppingCart?.items
|
|
?.filter(
|
|
(item) =>
|
|
!entity.shoppingCart?.items?.find((i) => i.id === item.id),
|
|
)
|
|
?.map((item) => item.data) ?? [];
|
|
|
|
entity.shoppingCart = shoppingCart;
|
|
|
|
entity.itemAvailabilityTimestamp = entity.itemAvailabilityTimestamp
|
|
? { ...entity.itemAvailabilityTimestamp }
|
|
: {};
|
|
|
|
const now = Date.now();
|
|
|
|
for (const shoppingCartItem of addedShoppingCartItems) {
|
|
if (shoppingCartItem.features?.orderType) {
|
|
entity.itemAvailabilityTimestamp[
|
|
`${shoppingCartItem.id}_${shoppingCartItem.features.orderType}`
|
|
] = now;
|
|
}
|
|
}
|
|
|
|
return storeCheckoutAdapter.setOne(entity, s);
|
|
},
|
|
),
|
|
on(
|
|
DomainCheckoutActions.setShoppingCartByShoppingCartId,
|
|
(s, { shoppingCartId, shoppingCart }) => {
|
|
let entity = getCheckoutEntityByShoppingCartId({
|
|
shoppingCartId,
|
|
entities: s.entities,
|
|
});
|
|
|
|
if (!entity) {
|
|
// No entity found for this shoppingCartId, cannot update
|
|
return s;
|
|
}
|
|
|
|
entity = { ...entity, shoppingCart };
|
|
|
|
return storeCheckoutAdapter.setOne(entity, s);
|
|
},
|
|
),
|
|
on(DomainCheckoutActions.setCheckout, (s, { processId, checkout }) => {
|
|
const entity = getOrCreateCheckoutEntity({
|
|
processId,
|
|
entities: s.entities,
|
|
});
|
|
entity.checkout = checkout;
|
|
return storeCheckoutAdapter.setOne(entity, s);
|
|
}),
|
|
on(
|
|
DomainCheckoutActions.setBuyerCommunicationDetails,
|
|
(s, { processId, email, mobile }) => {
|
|
const entity = getOrCreateCheckoutEntity({
|
|
processId,
|
|
entities: s.entities,
|
|
});
|
|
const communicationDetails = { ...entity.buyer.communicationDetails };
|
|
communicationDetails.email = email || communicationDetails.email;
|
|
communicationDetails.mobile = mobile || communicationDetails.mobile;
|
|
entity.buyer = {
|
|
...entity.buyer,
|
|
communicationDetails,
|
|
};
|
|
return storeCheckoutAdapter.setOne(entity, s);
|
|
},
|
|
),
|
|
on(
|
|
DomainCheckoutActions.setNotificationChannels,
|
|
(s, { processId, notificationChannels }) => {
|
|
const entity = getOrCreateCheckoutEntity({
|
|
processId,
|
|
entities: s.entities,
|
|
});
|
|
return storeCheckoutAdapter.setOne(
|
|
{ ...entity, notificationChannels },
|
|
s,
|
|
);
|
|
},
|
|
),
|
|
on(
|
|
DomainCheckoutActions.setCheckoutDestination,
|
|
(s, { processId, destination }) => {
|
|
const entity = getOrCreateCheckoutEntity({
|
|
processId,
|
|
entities: s.entities,
|
|
});
|
|
entity.checkout = {
|
|
...entity.checkout,
|
|
destinations: entity.checkout.destinations.map((dest) => {
|
|
if (dest.id === destination.id) {
|
|
return { ...dest, ...destination };
|
|
}
|
|
return { ...dest };
|
|
}),
|
|
};
|
|
return storeCheckoutAdapter.setOne(entity, s);
|
|
},
|
|
),
|
|
on(
|
|
DomainCheckoutActions.setShippingAddress,
|
|
(s, { processId, shippingAddress }) => {
|
|
const entity = getOrCreateCheckoutEntity({
|
|
processId,
|
|
entities: s.entities,
|
|
});
|
|
entity.shippingAddress = shippingAddress;
|
|
return storeCheckoutAdapter.setOne(entity, s);
|
|
},
|
|
),
|
|
on(DomainCheckoutActions.setBuyer, (s, { processId, buyer }) => {
|
|
const entity = getOrCreateCheckoutEntity({
|
|
processId,
|
|
entities: s.entities,
|
|
});
|
|
entity.buyer = buyer;
|
|
return storeCheckoutAdapter.setOne(entity, s);
|
|
}),
|
|
on(DomainCheckoutActions.setPayer, (s, { processId, payer }) => {
|
|
const entity = getOrCreateCheckoutEntity({
|
|
processId,
|
|
entities: s.entities,
|
|
});
|
|
entity.payer = payer;
|
|
return storeCheckoutAdapter.setOne(entity, s);
|
|
}),
|
|
on(
|
|
DomainCheckoutActions.setSpecialComment,
|
|
(s, { processId, agentComment }) => {
|
|
const entity = getOrCreateCheckoutEntity({
|
|
processId,
|
|
entities: s.entities,
|
|
});
|
|
entity.specialComment = agentComment;
|
|
return storeCheckoutAdapter.setOne(entity, s);
|
|
},
|
|
),
|
|
on(DomainCheckoutActions.removeCheckoutWithProcessId, (s, { processId }) => {
|
|
return storeCheckoutAdapter.removeOne(processId, s);
|
|
}),
|
|
on(DomainCheckoutActions.setOrders, (s, { orders }) => ({
|
|
...s,
|
|
orders: [...s.orders, ...orders],
|
|
})),
|
|
on(DomainCheckoutActions.updateOrderItem, (s, { item }) => {
|
|
const orders = [...s.orders];
|
|
|
|
const orderToUpdate = orders?.find((order) =>
|
|
order.items?.find((i) => i.id === item?.id),
|
|
);
|
|
const orderToUpdateIndex = orders?.indexOf(orderToUpdate);
|
|
|
|
const orderItemToUpdate = orderToUpdate?.items?.find(
|
|
(i) => i.id === item?.id,
|
|
);
|
|
const orderItemToUpdateIndex =
|
|
orderToUpdate?.items?.indexOf(orderItemToUpdate);
|
|
|
|
const items = [...(orderToUpdate?.items ?? [])];
|
|
items[orderItemToUpdateIndex] = item;
|
|
|
|
orders[orderToUpdateIndex] = {
|
|
...orderToUpdate,
|
|
items: [...items],
|
|
};
|
|
|
|
return { ...s, orders: [...orders] };
|
|
}),
|
|
on(DomainCheckoutActions.removeAllOrders, (s) => ({
|
|
...s,
|
|
orders: [],
|
|
})),
|
|
on(DomainCheckoutActions.setOlaError, (s, { processId, olaErrorIds }) => {
|
|
const entity = getOrCreateCheckoutEntity({
|
|
processId,
|
|
entities: s.entities,
|
|
});
|
|
entity.olaErrorIds = olaErrorIds;
|
|
return storeCheckoutAdapter.setOne(entity, s);
|
|
}),
|
|
on(DomainCheckoutActions.setCustomer, (s, { processId, customer }) => {
|
|
const entity = getOrCreateCheckoutEntity({
|
|
processId,
|
|
entities: s.entities,
|
|
});
|
|
entity.customer = customer;
|
|
return storeCheckoutAdapter.setOne(entity, s);
|
|
}),
|
|
on(
|
|
DomainCheckoutActions.addShoppingCartItemAvailabilityToHistory,
|
|
(s, { processId, shoppingCartItemId, availability }) => {
|
|
const entity = getOrCreateCheckoutEntity({
|
|
processId,
|
|
entities: s.entities,
|
|
});
|
|
|
|
const itemAvailabilityTimestamp = entity?.itemAvailabilityTimestamp
|
|
? { ...entity?.itemAvailabilityTimestamp }
|
|
: {};
|
|
|
|
const item = entity?.shoppingCart?.items?.find(
|
|
(i) => i.id === shoppingCartItemId,
|
|
)?.data;
|
|
|
|
if (!item?.features?.orderType) return s;
|
|
|
|
itemAvailabilityTimestamp[`${item.id}_${item?.features?.orderType}`] =
|
|
Date.now();
|
|
|
|
entity.itemAvailabilityTimestamp = itemAvailabilityTimestamp;
|
|
|
|
return storeCheckoutAdapter.setOne(entity, s);
|
|
},
|
|
),
|
|
on(
|
|
DomainCheckoutActions.addShoppingCartItemAvailabilityToHistoryByShoppingCartId,
|
|
(s, { shoppingCartId, shoppingCartItemId, availability }) => {
|
|
const entity = getCheckoutEntityByShoppingCartId({
|
|
shoppingCartId,
|
|
entities: s.entities,
|
|
});
|
|
|
|
const itemAvailabilityTimestamp = entity?.itemAvailabilityTimestamp
|
|
? { ...entity?.itemAvailabilityTimestamp }
|
|
: {};
|
|
|
|
const item = entity?.shoppingCart?.items?.find(
|
|
(i) => i.id === shoppingCartItemId,
|
|
)?.data;
|
|
|
|
if (!item?.features?.orderType) return s;
|
|
|
|
itemAvailabilityTimestamp[`${item.id}_${item?.features?.orderType}`] =
|
|
Date.now();
|
|
|
|
entity.itemAvailabilityTimestamp = itemAvailabilityTimestamp;
|
|
|
|
return storeCheckoutAdapter.setOne(entity, s);
|
|
},
|
|
),
|
|
);
|
|
|
|
export function domainCheckoutReducer(state, action) {
|
|
return _domainCheckoutReducer(state, action);
|
|
}
|
|
|
|
function getOrCreateCheckoutEntity({
|
|
entities,
|
|
processId,
|
|
}: {
|
|
entities: Dictionary<CheckoutEntity>;
|
|
processId: number;
|
|
}): CheckoutEntity {
|
|
const entity = entities[processId];
|
|
|
|
if (isNullOrUndefined(entity)) {
|
|
return {
|
|
processId,
|
|
checkout: undefined,
|
|
shoppingCart: undefined,
|
|
shippingAddress: undefined,
|
|
orders: [],
|
|
payer: undefined,
|
|
buyer: undefined,
|
|
specialComment: '',
|
|
notificationChannels: 0,
|
|
olaErrorIds: [],
|
|
customer: undefined,
|
|
// availabilityHistory: [],
|
|
itemAvailabilityTimestamp: {},
|
|
};
|
|
}
|
|
|
|
return { ...entity };
|
|
}
|
|
|
|
function getCheckoutEntityByShoppingCartId({
|
|
entities,
|
|
shoppingCartId,
|
|
}: {
|
|
entities: Dictionary<CheckoutEntity>;
|
|
shoppingCartId: number;
|
|
}): CheckoutEntity {
|
|
return Object.values(entities).find(
|
|
(entity) => entity.shoppingCart?.id === shoppingCartId,
|
|
);
|
|
}
|