import { useState, useMemo } from 'react'; import { Button, Table, Popover, Typography, List, Flex } from 'antd'; import { useTranslation } from 'react-i18next'; import { HT_HOST } from '@/config'; import { fetchJSON } from '@/utils/request'; import { formatGroupSize } from '@/hooks/useProductsSets'; import { isEmpty, isNotEmpty } from '@/utils/commons'; import { chunkBy } from '@/hooks/useProductsQuotationFormat'; /** * 产品价格日志 */ const getPPLogAction = async (params) => { const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_product_price_log`, params) return errcode !== 0 ? [] : result; }; /** * 产品价格: 已发布的 */ const getPPRunningAction = async (params) => { const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_product_price_running`, params) return errcode !== 0 ? [] : result; }; /** * 产品价格快照 */ const getPPSnapshotAction = async (params) => { const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_product_price_snapshot`, params) return errcode !== 0 ? [] : result; } 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'), btnText: t('versionHistory'), fetchData: async (params) => { const data = await getPPLogAction(params); return {data}; }, }, 'published': { title: '✅' + t('versionPublished'), btnText: t('versionPublished'), fetchData: async (params) => { const { travel_agency_id, product_id, price_id, use_year } = params; const data = await getPPRunningAction({ travel_agency_id, product_id_list: product_id, use_year }); return {data: data?.[0]?.quotation || []}; }, }, 'snapshot': { title: '📷' + t('versionSnapshot'), btnText: t('versionSnapshot'), subTitle: t('点击左侧价格版本查看具体价格'), fetchData: async (params) => { const { price_id, ..._params } = params; const data = await getPPSnapshotAction(_params); 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' | 'snapshot'} 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) * @param {number} props.use_year - Year to use for fetching data (used in data fetching) * @param {Function} props.onOpenChange - Callback function to be called when the popover opens or closes */ const ProductQuotationSnapshotPopover = ({ method, triggerProps = {}, onOpenChange, ...props }) => { const { travel_agency_id, product_id, price_id, use_year } = props; const { t } = useTranslation('products'); const [open, setOpen] = useState(false); const [logData, setLogData] = useState([]); const { title, subTitle, btnText: methodBtnText, fetchData } = useLogMethod(method); const tablePagination = useMemo(() => method === 'history' ? { pageSize: 5, position: ['bottomLeft']} : { pageSize: 10, position: ['bottomLeft']}, [method]); const [viewSnapshotItem, setViewSnapshotItem] = useState([]); const [loading, setLoading] = useState(false); const getData = async () => { setLoading(true); const { data } = await fetchData({ travel_agency_id, product_id, price_id, use_year }); setLogData(data); invokeOpenChange(true); setLoading(false); }; const invokeOpenChange = (_open) => { if (typeof onOpenChange === 'function') { onOpenChange(_open); } }; const onClickSnapshotItem = (item) => { console.log(item) setViewSnapshotItem(item); console.log('cc\n'); const chunk = chunkBy(2025, [{...item, quotation: item.quotation.map(q => ({...q, WPI_SN: product_id })), info: { id: product_id }}], ['quote_season', 'quote_size']); console.log(chunk) }; const columns = [...columnsSets(t, false), { title: '时间', dataIndex: 'updatetime', key: 'updatetime' }]; return ( {title} {subTitle && {subTitle}} } content={ <> ( onClickSnapshotItem(item)} className={viewSnapshotItem.version === item.version ? 'active' : ''}> {item.version} )} pagination={{ pageSize: 5, size: 'small', showLessItems: true, simple: { readOnly: true } }} className=' cursor-pointer basis-48 flex flex-col [&>*:first-child]:flex-1 [&_.ant-list-pagination]:m-1 [&_.ant-list-item]:py-1 [&_.ant-list-item.active]:bg-blue-100' />
} trigger={['click']} open={open} onOpenChange={(v) => { setOpen(v); invokeOpenChange(v); if (v === false) { setLogData([]); setViewSnapshotItem([]); } }}> ); }; export default ProductQuotationSnapshotPopover;