new-api / web /src /hooks /redemptions /useRedemptionsData.jsx
liuzhao521
Deploy New API v0.9.25+ (commit b47cf4ef) to HuggingFace Spaces
4674012
/*
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 <https://www.gnu.org/licenses/>.
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,
};
};