/* Copyright (C) 2025 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import { useState, useEffect } from 'react'; import { API, showError, showSuccess, copy } from '../../helpers'; import { ITEMS_PER_PAGE } from '../../constants'; import { REDEMPTION_ACTIONS, REDEMPTION_STATUS, } from '../../constants/redemption.constants'; import { Modal } from '@douyinfe/semi-ui'; import { useTranslation } from 'react-i18next'; import { useTableCompactMode } from '../common/useTableCompactMode'; export const useRedemptionsData = () => { const { t } = useTranslation(); // Basic state const [redemptions, setRedemptions] = useState([]); const [loading, setLoading] = useState(true); const [searching, setSearching] = useState(false); const [activePage, setActivePage] = useState(1); const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE); const [tokenCount, setTokenCount] = useState(0); const [selectedKeys, setSelectedKeys] = useState([]); // Edit state const [editingRedemption, setEditingRedemption] = useState({ id: undefined, }); const [showEdit, setShowEdit] = useState(false); // Form API const [formApi, setFormApi] = useState(null); // UI state const [compactMode, setCompactMode] = useTableCompactMode('redemptions'); // Form state const formInitValues = { searchKeyword: '', }; // Get form values const getFormValues = () => { const formValues = formApi ? formApi.getValues() : {}; return { searchKeyword: formValues.searchKeyword || '', }; }; // Set redemption data format const setRedemptionFormat = (redemptions) => { setRedemptions(redemptions); }; // Load redemption list const loadRedemptions = async (page = 1, pageSize) => { setLoading(true); try { const res = await API.get( `/api/redemption/?p=${page}&page_size=${pageSize}`, ); const { success, message, data } = res.data; if (success) { const newPageData = data.items; setActivePage(data.page <= 0 ? 1 : data.page); setTokenCount(data.total); setRedemptionFormat(newPageData); } else { showError(message); } } catch (error) { showError(error.message); } setLoading(false); }; // Search redemption codes const searchRedemptions = async () => { const { searchKeyword } = getFormValues(); if (searchKeyword === '') { await loadRedemptions(1, pageSize); return; } setSearching(true); try { const res = await API.get( `/api/redemption/search?keyword=${searchKeyword}&p=1&page_size=${pageSize}`, ); const { success, message, data } = res.data; if (success) { const newPageData = data.items; setActivePage(data.page || 1); setTokenCount(data.total); setRedemptionFormat(newPageData); } else { showError(message); } } catch (error) { showError(error.message); } setSearching(false); }; // Manage redemption codes (CRUD operations) const manageRedemption = async (id, action, record) => { setLoading(true); let data = { id }; let res; try { switch (action) { case REDEMPTION_ACTIONS.DELETE: res = await API.delete(`/api/redemption/${id}/`); break; case REDEMPTION_ACTIONS.ENABLE: data.status = REDEMPTION_STATUS.UNUSED; res = await API.put('/api/redemption/?status_only=true', data); break; case REDEMPTION_ACTIONS.DISABLE: data.status = REDEMPTION_STATUS.DISABLED; res = await API.put('/api/redemption/?status_only=true', data); break; default: throw new Error('Unknown operation type'); } const { success, message } = res.data; if (success) { showSuccess('操作成功完成!'); let redemption = res.data.data; let newRedemptions = [...redemptions]; if (action !== REDEMPTION_ACTIONS.DELETE) { record.status = redemption.status; } setRedemptions(newRedemptions); } else { showError(message); } } catch (error) { showError(error.message); } setLoading(false); }; // Refresh data const refresh = async (page = activePage) => { const { searchKeyword } = getFormValues(); if (searchKeyword === '') { await loadRedemptions(page, pageSize); } else { await searchRedemptions(); } }; // Handle page change const handlePageChange = (page) => { setActivePage(page); const { searchKeyword } = getFormValues(); if (searchKeyword === '') { loadRedemptions(page, pageSize); } else { searchRedemptions(); } }; // Handle page size change const handlePageSizeChange = (size) => { setPageSize(size); setActivePage(1); const { searchKeyword } = getFormValues(); if (searchKeyword === '') { loadRedemptions(1, size); } else { searchRedemptions(); } }; // Row selection configuration const rowSelection = { onSelect: (record, selected) => {}, onSelectAll: (selected, selectedRows) => {}, onChange: (selectedRowKeys, selectedRows) => { setSelectedKeys(selectedRows); }, }; // Row style handling - using isExpired function const handleRow = (record, index) => { // Local isExpired function const isExpired = (rec) => { return ( rec.status === REDEMPTION_STATUS.UNUSED && rec.expired_time !== 0 && rec.expired_time < Math.floor(Date.now() / 1000) ); }; if (record.status !== REDEMPTION_STATUS.UNUSED || isExpired(record)) { return { style: { background: 'var(--semi-color-disabled-border)', }, }; } else { return {}; } }; // Copy text const copyText = async (text) => { if (await copy(text)) { showSuccess('已复制到剪贴板!'); } else { Modal.error({ title: '无法复制到剪贴板,请手动复制', content: text, size: 'large', }); } }; // Batch copy redemption codes const batchCopyRedemptions = async () => { if (selectedKeys.length === 0) { showError(t('请至少选择一个兑换码!')); return; } let keys = ''; for (let i = 0; i < selectedKeys.length; i++) { keys += selectedKeys[i].name + ' ' + selectedKeys[i].key + '\n'; } await copyText(keys); }; // Batch delete redemption codes (clear invalid) const batchDeleteRedemptions = async () => { Modal.confirm({ title: t('确定清除所有失效兑换码?'), content: t('将删除已使用、已禁用及过期的兑换码,此操作不可撤销。'), onOk: async () => { setLoading(true); const res = await API.delete('/api/redemption/invalid'); const { success, message, data } = res.data; if (success) { showSuccess(t('已删除 {{count}} 条失效兑换码', { count: data })); await refresh(); } else { showError(message); } setLoading(false); }, }); }; // Close edit modal const closeEdit = () => { setShowEdit(false); setTimeout(() => { setEditingRedemption({ id: undefined, }); }, 500); }; // Remove record (for UI update after deletion) const removeRecord = (key) => { let newDataSource = [...redemptions]; if (key != null) { let idx = newDataSource.findIndex((data) => data.key === key); if (idx > -1) { newDataSource.splice(idx, 1); setRedemptions(newDataSource); } } }; // Initialize data loading useEffect(() => { loadRedemptions(1, pageSize) .then() .catch((reason) => { showError(reason); }); }, [pageSize]); return { // Data state redemptions, loading, searching, activePage, pageSize, tokenCount, selectedKeys, // Edit state editingRedemption, showEdit, // Form state formApi, formInitValues, // UI state compactMode, setCompactMode, // Data operations loadRedemptions, searchRedemptions, manageRedemption, refresh, copyText, removeRecord, // State updates setActivePage, setPageSize, setSelectedKeys, setEditingRedemption, setShowEdit, setFormApi, setLoading, // Event handlers handlePageChange, handlePageSizeChange, rowSelection, handleRow, closeEdit, getFormValues, // Batch operations batchCopyRedemptions, batchDeleteRedemptions, // Translation function t, }; };