From aa1ef7a559eacd6042d1282697838f0971b07864 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 2 Mar 2026 09:57:21 +0700 Subject: [PATCH] feat(FE): Add constants transformation utilities and API service --- src/lib/helper.ts | 126 ++++++++++++++++++++++++ src/services/api/constants/constants.ts | 25 +++++ src/types/api/constants/constants.d.ts | 86 ++++++++++++++++ 3 files changed, 237 insertions(+) create mode 100644 src/services/api/constants/constants.ts create mode 100644 src/types/api/constants/constants.d.ts diff --git a/src/lib/helper.ts b/src/lib/helper.ts index 9a802f80..9029321a 100644 --- a/src/lib/helper.ts +++ b/src/lib/helper.ts @@ -3,6 +3,12 @@ import 'moment/locale/id'; import { twMerge } from 'tailwind-merge'; import clsx, { ClassValue } from 'clsx'; import { SidebarMenuItem } from '@/components/molecules/SidebarMenu'; +import { OptionType } from '@/components/input/SelectInput'; +import { + ConstantsApiResponse, + ProductFlagMappingUI, + TransformedConstants, +} from '@/types/api/constants/constants'; // set locale globally moment.locale('id'); @@ -179,3 +185,123 @@ export function findMenuPath( return null; } + +/** + * Transform a string value to OptionType with formatted label + * Example: "AYAM-AFKIR" -> { label: "Ayam Afkir", value: "AYAM-AFKIR" } + */ +export function toOption(value: string): OptionType { + return { + value, + label: formatConstantLabel(value), + }; +} + +/** + * Format constant label by: + * 1. Replacing underscores/hyphens with spaces + * 2. Converting to title case + * 3. Handling special cases + */ +export function formatConstantLabel(value: string): string { + const specialCases: Record = { + 'PRE-STARTER': 'Pre Starter', + BOP: 'BOP', + SAPRONAK: 'SAPRONAK', + OVK: 'OVK', + DOC: 'DOC', + }; + + if (specialCases[value]) { + return specialCases[value]; + } + + const withSpaces = value.replace(/[-_]/g, ' '); + + return formatTitleCase(withSpaces); +} + +/** + * Transform product_flag_mapping from API format to UI format + */ +export function transformProductFlagMapping( + mapping: ConstantsApiResponse['product_flag_mapping'] +): ProductFlagMappingUI { + return { + flags: mapping.flags.map(toOption), + options: mapping.options.map((opt) => ({ + flag: toOption(opt.flag), + sub_flags: opt.sub_flags.map(toOption), + allow_without_sub_flag: opt.allow_without_sub_flag, + })), + sub_flag_to_flag: mapping.sub_flag_to_flag, + }; +} + +/** + * Transform approval workflows from API format to UI format + */ +export function transformApprovalWorkflows( + workflows: ConstantsApiResponse['approval_workflows'] +) { + return workflows.map((workflow) => ({ + key: workflow.key, + steps: workflow.steps.map((step) => ({ + value: String(step.step_number), + label: step.step_name, + })), + })); +} + +/** + * Transform adjustment transaction subtypes from API format to UI format + */ +export function transformAdjustmentSubtypes( + subtypes: ConstantsApiResponse['adjustment']['transaction_subtypes'] +) { + return { + RECORDING: subtypes.RECORDING.map(toOption), + PENJUALAN: subtypes.PENJUALAN.map(toOption), + PEMBELIAN: subtypes.PEMBELIAN.map(toOption), + }; +} + +/** + * Transform legacy flag aliases from API format to UI format + */ +export function transformLegacyFlagAliases( + aliases: ConstantsApiResponse['legacy_flag_aliases'] +): OptionType[] { + return Object.entries(aliases).map(([key, value]) => ({ + value: key, + label: formatConstantLabel(key), + })); +} + +/** + * Transform the entire constants API response to UI format + */ +export function transformConstants( + data: ConstantsApiResponse +): TransformedConstants { + return { + warehouse_types: data.warehouse_types.map(toOption), + supplier_categories: data.supplier_categories.map(toOption), + customer_supplier_types: data.customer_supplier_types.map(toOption), + adjustment: { + transaction_subtypes: transformAdjustmentSubtypes( + data.adjustment.transaction_subtypes + ), + }, + approval_workflows: transformApprovalWorkflows(data.approval_workflows), + flags: data.flags.map(toOption), + product_flag_mapping: transformProductFlagMapping( + data.product_flag_mapping + ), + legacy_flag_aliases: transformLegacyFlagAliases(data.legacy_flag_aliases), + stock_log: { + log_types: data.stock_log.log_types.map(toOption), + transaction_types: data.stock_log.transaction_types.map(toOption), + }, + }; +} diff --git a/src/services/api/constants/constants.ts b/src/services/api/constants/constants.ts new file mode 100644 index 00000000..0b7c2242 --- /dev/null +++ b/src/services/api/constants/constants.ts @@ -0,0 +1,25 @@ +import { httpClient } from '@/services/http/client'; +import { + ConstantsApiResponse, + TransformedConstants, +} from '@/types/api/constants/constants'; +import { transformConstants } from '@/lib/helper'; + +class ConstantsApiService { + async fetchConstants(): Promise { + try { + const response = await httpClient('/constants'); + return response; + } catch { + return undefined; + } + } + + async fetchTransformedConstants(): Promise { + const data = await this.fetchConstants(); + if (!data) return undefined; + return transformConstants(data); + } +} + +export const ConstantsApi = new ConstantsApiService(); diff --git a/src/types/api/constants/constants.d.ts b/src/types/api/constants/constants.d.ts new file mode 100644 index 00000000..e2a7c670 --- /dev/null +++ b/src/types/api/constants/constants.d.ts @@ -0,0 +1,86 @@ +import { OptionType } from '@/components/input/SelectInput'; + +export type ApprovalWorkflowStep = { + step_number: number; + step_name: string; +}; + +export type ApprovalWorkflow = { + key: string; + steps: ApprovalWorkflowStep[]; +}; + +export type ProductFlagMappingOption = { + flag: string; + sub_flags: string[]; + allow_without_sub_flag: boolean; +}; + +export type ProductFlagMappingApiResponse = { + flags: string[]; + options: ProductFlagMappingOption[]; + sub_flag_to_flag: Record; +}; + +export type AdjustmentTransactionSubtypes = { + RECORDING: string[]; + PENJUALAN: string[]; + PEMBELIAN: string[]; +}; + +export type StockLogConfig = { + log_types: string[]; + transaction_types: string[]; +}; + +export type ConstantsApiResponse = { + warehouse_types: string[]; + supplier_categories: string[]; + customer_supplier_types: string[]; + adjustment: { + transaction_subtypes: AdjustmentTransactionSubtypes; + }; + approval_workflows: ApprovalWorkflow[]; + flags: string[]; + product_flag_mapping: ProductFlagMappingApiResponse; + legacy_flag_aliases: Record; + stock_log: StockLogConfig; +}; + +export type ProductFlagMappingOptionUI = { + flag: OptionType; + sub_flags: OptionType[]; + allow_without_sub_flag: boolean; +}; + +export type ProductFlagMappingUI = { + flags: OptionType[]; + options: ProductFlagMappingOptionUI[]; + sub_flag_to_flag: Record; +}; + +export type ApprovalWorkflowUI = { + key: string; + steps: OptionType[]; +}; + +export type TransformedConstants = { + warehouse_types: OptionType[]; + supplier_categories: OptionType[]; + customer_supplier_types: OptionType[]; + adjustment: { + transaction_subtypes: { + RECORDING: OptionType[]; + PENJUALAN: OptionType[]; + PEMBELIAN: OptionType[]; + }; + }; + approval_workflows: ApprovalWorkflowUI[]; + flags: OptionType[]; + product_flag_mapping: ProductFlagMappingUI; + legacy_flag_aliases: OptionType[]; + stock_log: { + log_types: OptionType[]; + transaction_types: OptionType[]; + }; +};