diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx
index c317af1..d0bc1a4 100644
--- a/src/views/products/Audit.jsx
+++ b/src/views/products/Audit.jsx
@@ -1,10 +1,10 @@
import { useEffect, useState } from 'react';
import { useParams, Link } from 'react-router-dom';
-import { App, Empty, Button, Collapse, Table, Space, Alert, Tooltip, Popover, Typography } from 'antd';
-import { useProductsTypes, useProductsAuditStatesMapVal, formatGroupSize } from '@/hooks/useProductsSets';
+import { App, Empty, Button, Collapse, Table, Space, Alert } from 'antd';
+import { useProductsTypes, useProductsAuditStatesMapVal } from '@/hooks/useProductsSets';
import SecondHeaderWrapper from '@/components/SecondHeaderWrapper';
import { useTranslation } from 'react-i18next';
-import useProductsStore, { getPPLogAction, getPPRunningAction, postProductsQuoteAuditAction, } from '@/stores/Products/Index';
+import useProductsStore, { postProductsQuoteAuditAction, } from '@/stores/Products/Index';
import { cloneDeep, groupBy, isEmpty, isNotEmpty } from '@/utils/commons';
import useAuthStore from '@/stores/Auth';
import RequireAuth from '@/components/RequireAuth';
@@ -12,165 +12,8 @@ import { PERM_PRODUCTS_MANAGEMENT, PERM_PRODUCTS_OFFER_AUDIT, PERM_PRODUCTS_OFFE
import Header from './Detail/Header';
import dayjs from 'dayjs';
import { usingStorage } from '@/hooks/usingStorage';
-import { ClockCircleFilled, ClockCircleOutlined, PlusCircleFilled, PlusCircleOutlined } from '@ant-design/icons';
-
-const parseJson = (str) => {
- let result;
- if (str === null || str === undefined || str === '') {
- return {};
- }
- try {
- result = JSON.parse(str);
- return Array.isArray(result) ? result.reduce((acc, cur) => ({ ...acc, ...cur }), {}) : result;
- } catch (e) {
- return {};
- }
-};
-
- const columnsSets = (t, colorize = true) => [
- {
- key: 'adult',
- title: t('AgeType.Adult'),
- width: '12rem',
- render: (_, { adult_cost, currency, unit_id, unit_name, audit_state_id, lastedit_changed }) => {
- const _changed = parseJson(lastedit_changed);
- const ifCompare = colorize && ![-1, 1, 2].includes(audit_state_id);
- const ifData = isNotEmpty(_changed.adult_cost) || isNotEmpty(_changed.unit_id) || isNotEmpty(_changed.currency);
- const preValue =
- ifCompare && ifData ? (
-
{`${_changed.adult_cost} ${_changed.currency || currency} / ${t(`PriceUnit.${_changed.unit_id || unit_id}`)}`}
- ) : null;
- const editCls = ifCompare && ifData ? 'text-danger' : '';
- return (
-
- {preValue}
- {`${adult_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}`}
-
- );
- },
- },
- {
- key: 'child',
- title: t('AgeType.Child'),
- width: '12rem',
- render: (_, { child_cost, currency, unit_id, unit_name, audit_state_id, lastedit_changed }) => {
- const _changed = parseJson(lastedit_changed);
- const ifCompare = colorize && ![-1, 1, 2].includes(audit_state_id);
- const ifData = isNotEmpty(_changed.child_cost) || isNotEmpty(_changed.unit_id) || isNotEmpty(_changed.currency);
- const preValue =
- ifCompare && ifData ? (
- {`${_changed.child_cost} ${_changed.currency || currency} / ${t(`PriceUnit.${_changed.unit_id || unit_id}`)}`}
- ) : null;
- const editCls = ifCompare && ifData ? 'text-danger' : '';
- return (
-
- {preValue}
- {`${child_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}`}
-
- );
- },
- },
- // {key: 'unit', title: t('Unit'), },
- {
- key: 'groupSize',
- dataIndex: ['group_size_min'],
- title: t('group_size'),
- width: '6rem',
- render: (_, { audit_state_id, group_size_min, group_size_max, lastedit_changed }) => {
- const _changed = parseJson(lastedit_changed);
- const preValue =
- colorize && ![-1, 1, 2].includes(audit_state_id) && (isNotEmpty(_changed.group_size_min) || isNotEmpty(_changed.group_size_max)) ? (
- {`${_changed.group_size_min ?? group_size_min} - ${_changed.group_size_max ?? group_size_max}`}
- ) : null;
- const editCls = colorize && ![-1, 1, 2].includes(audit_state_id) && (isNotEmpty(_changed.group_size_min) || isNotEmpty(_changed.group_size_max)) ? 'text-danger' : '';
- return (
-
- {preValue}
- {formatGroupSize(group_size_min, group_size_max)}
-
- );
- },
- },
- {
- key: 'useDates',
- dataIndex: ['use_dates_start'],
- title: t('use_dates'),
- width: '12rem',
- render: (_, { use_dates_start, use_dates_end, weekdays, audit_state_id, lastedit_changed }) => {
- const _changed = parseJson(lastedit_changed);
- const preValue =
- colorize && ![-1, 1, 2].includes(audit_state_id) && (isNotEmpty(_changed.use_dates_start) || isNotEmpty(_changed.use_dates_end)) ? (
-
- {isNotEmpty(_changed.use_dates_start) ? {_changed.use_dates_start} : use_dates_start} ~{' '}
- {isNotEmpty(_changed.use_dates_end) ? {_changed.use_dates_end} : use_dates_end}
-
- ) : null;
- const editCls = colorize && ![-1, 1, 2].includes(audit_state_id) && (isNotEmpty(_changed.use_dates_start) || isNotEmpty(_changed.use_dates_end)) ? 'text-danger' : '';
- return (
-
- {preValue}
- {`${use_dates_start} ~ ${use_dates_end}`}
-
- );
- },
- },
- {
- key: 'weekdays',
- dataIndex: ['weekdays'],
- title: t('Weekdays'),
- width: '6rem',
- render: (text, { weekdays, audit_state_id, lastedit_changed }) => {
- const _changed = parseJson(lastedit_changed);
- const ifCompare = colorize && ![-1, 1, 2].includes(audit_state_id);
- const ifData = !isEmpty((_changed.weekdayList || []).filter(s => s));
- const preValue =
- ifCompare && ifData ? (
- {_changed.weekdayList}
- ) : null;
- const editCls = ifCompare && ifData ? 'text-danger' : '';
- return {preValue}{text || t('Unlimited')}
;
- },
- },
- ];
-
-const PriceLogPopover = ({ title, fetchData, triggerProps={}, ...props}) => {
- const { t } = useTranslation('products');
- const [open, setOpen] = useState(false);
- const [logData, setLogData] = useState([]);
- const getData = async () => {
- const data = await fetchData();
- setLogData(data);
- };
- const columns = [...columnsSets(t, false), { title: '时间', dataIndex: 'updatetime', key: 'updatetime'}];
- return (
-
- {title}
-
-
- }
- content={
- <>
-
- >
- }
- trigger={['click']}
- placement='bottom'
- className=''
- rootClassName='w-5/6'
- open={open}
- onOpenChange={(v) => {
- setOpen(v);
- }}>
-
-
- );
-};
+import { ClockCircleOutlined, PlusCircleFilled } from '@ant-design/icons';
+import ProductQuotationLogPopover, { columnsSets } from './Detail/ProductQuotationLogPopover';
const PriceTable = ({ productType, dataSource, refresh }) => {
const { t } = useTranslation('products');
@@ -210,16 +53,6 @@ const PriceTable = ({ productType, dataSource, refresh }) => {
});
};
- const getLog = async ({ product_id, price_id }) => {
- const data = await getPPLogAction({ travel_agency_id, product_id, price_id });
- return data;
- };
-
- const getRunning = async ({ product_id }) => {
- const data = await getPPRunningAction({ travel_agency_id, product_id_list: product_id });
- return data?.[0]?.quotation || [];
- };
-
const rowStyle = (r, tri) => {
const trCls = tri%2 !== 0 ? ' bg-stone-50' : ''; // 奇偶行
const [infoI, quoteI] = r.rowSpanI;
@@ -255,7 +88,7 @@ const PriceTable = ({ productType, dataSource, refresh }) => {
- getLog({ travel_agency_id, product_id: r.info.id, price_id: r.id })} {...{ travel_agency_id, product_id: r.info.id, price_id: r.id }} />
+
) : null,
@@ -267,13 +100,12 @@ const PriceTable = ({ productType, dataSource, refresh }) => {
render: (_, r) => {
const showPublicBtn = null; // r.pendingQuotation ? : null;
const btn2 = r.pendingQuotation ? (
- getRunning({ travel_agency_id, product_id: r.info.id, price_id: r.id })}
- {...{ travel_agency_id, product_id: r.info.id, price_id: r.id }}
/>
) : null;
return {btn2}
;
diff --git a/src/views/products/Detail/ProductQuotationLogPopover.jsx b/src/views/products/Detail/ProductQuotationLogPopover.jsx
new file mode 100644
index 0000000..26aed6b
--- /dev/null
+++ b/src/views/products/Detail/ProductQuotationLogPopover.jsx
@@ -0,0 +1,211 @@
+import { useState } from 'react';
+import { Button, Table, Popover, Typography } from 'antd';
+import { useTranslation } from 'react-i18next';
+import { getPPLogAction, getPPRunningAction } from '@/stores/Products/Index';
+import { formatGroupSize } from '@/hooks/useProductsSets';
+import { isEmpty, isNotEmpty } from '@/utils/commons';
+
+const parseJson = (str) => {
+ let result;
+ if (str === null || str === undefined || str === '') {
+ return {};
+ }
+ try {
+ result = JSON.parse(str);
+ return Array.isArray(result) ? result.reduce((acc, cur) => ({ ...acc, ...cur }), {}) : result;
+ } catch (e) {
+ return {};
+ }
+};
+
+export const columnsSets = (t, colorize = true) => [
+ {
+ key: 'adult',
+ title: t('AgeType.Adult'),
+ width: '12rem',
+ render: (_, { adult_cost, currency, unit_id, unit_name, audit_state_id, lastedit_changed }) => {
+ const _changed = parseJson(lastedit_changed);
+ const ifCompare = colorize && ![-1, 1, 2].includes(audit_state_id);
+ const ifData = isNotEmpty(_changed.adult_cost) || isNotEmpty(_changed.unit_id) || isNotEmpty(_changed.currency);
+ const preValue =
+ ifCompare && ifData ? (
+ {`${_changed.adult_cost} ${_changed.currency || currency} / ${t(`PriceUnit.${_changed.unit_id || unit_id}`)}`}
+ ) : null;
+ const editCls = ifCompare && ifData ? 'text-danger' : '';
+ return (
+
+ {preValue}
+ {`${adult_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}`}
+
+ );
+ },
+ },
+ {
+ key: 'child',
+ title: t('AgeType.Child'),
+ width: '12rem',
+ render: (_, { child_cost, currency, unit_id, unit_name, audit_state_id, lastedit_changed }) => {
+ const _changed = parseJson(lastedit_changed);
+ const ifCompare = colorize && ![-1, 1, 2].includes(audit_state_id);
+ const ifData = isNotEmpty(_changed.child_cost) || isNotEmpty(_changed.unit_id) || isNotEmpty(_changed.currency);
+ const preValue =
+ ifCompare && ifData ? (
+ {`${_changed.child_cost} ${_changed.currency || currency} / ${t(`PriceUnit.${_changed.unit_id || unit_id}`)}`}
+ ) : null;
+ const editCls = ifCompare && ifData ? 'text-danger' : '';
+ return (
+
+ {preValue}
+ {`${child_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}`}
+
+ );
+ },
+ },
+ // {key: 'unit', title: t('Unit'), },
+ {
+ key: 'groupSize',
+ dataIndex: ['group_size_min'],
+ title: t('group_size'),
+ width: '6rem',
+ render: (_, { audit_state_id, group_size_min, group_size_max, lastedit_changed }) => {
+ const _changed = parseJson(lastedit_changed);
+ const preValue =
+ colorize && ![-1, 1, 2].includes(audit_state_id) && (isNotEmpty(_changed.group_size_min) || isNotEmpty(_changed.group_size_max)) ? (
+ {`${_changed.group_size_min ?? group_size_min} - ${_changed.group_size_max ?? group_size_max}`}
+ ) : null;
+ const editCls = colorize && ![-1, 1, 2].includes(audit_state_id) && (isNotEmpty(_changed.group_size_min) || isNotEmpty(_changed.group_size_max)) ? 'text-danger' : '';
+ return (
+
+ {preValue}
+ {formatGroupSize(group_size_min, group_size_max)}
+
+ );
+ },
+ },
+ {
+ key: 'useDates',
+ dataIndex: ['use_dates_start'],
+ title: t('use_dates'),
+ width: '12rem',
+ render: (_, { use_dates_start, use_dates_end, weekdays, audit_state_id, lastedit_changed }) => {
+ const _changed = parseJson(lastedit_changed);
+ const preValue =
+ colorize && ![-1, 1, 2].includes(audit_state_id) && (isNotEmpty(_changed.use_dates_start) || isNotEmpty(_changed.use_dates_end)) ? (
+
+ {isNotEmpty(_changed.use_dates_start) ? {_changed.use_dates_start} : use_dates_start} ~{' '}
+ {isNotEmpty(_changed.use_dates_end) ? {_changed.use_dates_end} : use_dates_end}
+
+ ) : null;
+ const editCls = colorize && ![-1, 1, 2].includes(audit_state_id) && (isNotEmpty(_changed.use_dates_start) || isNotEmpty(_changed.use_dates_end)) ? 'text-danger' : '';
+ return (
+
+ {preValue}
+ {`${use_dates_start} ~ ${use_dates_end}`}
+
+ );
+ },
+ },
+ {
+ key: 'weekdays',
+ dataIndex: ['weekdays'],
+ title: t('Weekdays'),
+ width: '6rem',
+ render: (text, { weekdays, audit_state_id, lastedit_changed }) => {
+ const _changed = parseJson(lastedit_changed);
+ const ifCompare = colorize && ![-1, 1, 2].includes(audit_state_id);
+ const ifData = !isEmpty((_changed.weekdayList || []).filter((s) => s));
+ const preValue = ifCompare && ifData ? {_changed.weekdayList}
: null;
+ const editCls = ifCompare && ifData ? 'text-danger' : '';
+ return (
+
+ {preValue}
+ {text || t('Unlimited')}
+
+ );
+ },
+ },
+];
+
+const useLogMethod = (method) => {
+ const { t } = useTranslation('products');
+ const methodMap = {
+ 'history': {
+ title: t('versionHistory'),
+ fetchData: async (params) => {
+ const data = await getPPLogAction(params);
+ return data;
+ },
+ },
+ 'published': {
+ title: t('versionPublished'),
+ fetchData: async (params) => {
+ const { travel_agency_id, product_id, price_id } = params;
+ const data = await getPPRunningAction({ travel_agency_id, product_id_list: product_id });
+ return data?.[0]?.quotation || [];
+ },
+ },
+ };
+ return methodMap[method];
+};
+
+/**
+ * ProductQuotationLogPopover - A popover component that displays product quotation change logs or published data
+ *
+ * This component shows a history of price changes for a specific product quotation in a popover table.
+ * It supports displaying different data sources (history logs or published data) and shows
+ * comparison between previous and current values with visual indicators.
+ *
+ * @param {Object} props - Component props
+ * @param {string} props.btnText - The text to display on the trigger button and in the popover header
+ * @param {'history' | 'published'} props.method - Determines data source - "history" for change logs or "published" for published quotations
+ * @param {Object} props.triggerProps - Additional props to pass to the trigger button
+ * @param {number} props.travel_agency_id - ID of the travel agency (used in data fetching)
+ * @param {number} props.product_id - ID of the product (used in data fetching)
+ * @param {number} props.price_id - ID of the price entry (used in data fetching)
+ */
+const ProductQuotationLogPopover = ({ method, triggerProps = {}, ...props }) => {
+ const { travel_agency_id, product_id, price_id } = props;
+
+ const { t } = useTranslation('products');
+ const [open, setOpen] = useState(false);
+ const [logData, setLogData] = useState([]);
+
+ const { title, fetchData } = useLogMethod(method);
+
+ const getData = async () => {
+ const data = await fetchData({ travel_agency_id, product_id, price_id });
+ setLogData(data);
+ };
+
+ const columns = [...columnsSets(t, false), { title: '时间', dataIndex: 'updatetime', key: 'updatetime' }];
+ return (
+
+ {title}
+
+
+ }
+ content={
+ <>
+
+ >
+ }
+ trigger={['click']}
+ placement='bottom'
+ className=''
+ rootClassName='w-5/6'
+ open={open}
+ onOpenChange={(v) => {
+ setOpen(v);
+ }}>
+
+
+ );
+};
+export default ProductQuotationLogPopover;