mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-11 15:45:00 +03:00
ee9a6067c2
The repo's type-aware deprecation sweep (eslint.deprecated.config.js) reported fourteen findings; it now reports zero. Alert message becomes title and closable+onClose becomes closable.onClose; Select optionFilterProp moves into showSearch.optionFilterProp and suffixIcon becomes suffix; Drawer width becomes size; Progress trailColor becomes railColor. Behavior is unchanged apart from a few single-mode selects gaining type-to-filter, which the old prop already implied.
97 lines
3.0 KiB
TypeScript
97 lines
3.0 KiB
TypeScript
import { useMemo } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Progress, Tag } from 'antd';
|
|
import { ClockCircleOutlined, ThunderboltOutlined } from '@ant-design/icons';
|
|
|
|
import './SubUsageSummary.css';
|
|
|
|
interface SubUsageSummaryProps {
|
|
usedByte: number;
|
|
totalByte: number;
|
|
usedLabel: string;
|
|
totalLabel: string;
|
|
remainedLabel: string;
|
|
expireMs: number;
|
|
isActive: boolean;
|
|
}
|
|
|
|
function pickStrokeColor(pct: number): { from: string; to: string } {
|
|
if (pct >= 90) return { from: '#ff7875', to: '#ff4d4f' };
|
|
if (pct >= 75) return { from: '#ffc53d', to: '#fa8c16' };
|
|
return { from: '#5fc983', to: '#36b37e' };
|
|
}
|
|
|
|
function formatExpiryChip(expireMs: number): { label: string; color: string } | null {
|
|
if (expireMs <= 0) return null;
|
|
const diff = expireMs - Date.now();
|
|
if (diff <= 0) return { label: 'Expired', color: 'red' };
|
|
const days = Math.floor(diff / 86400000);
|
|
if (days >= 1) return { label: `${days}d`, color: days <= 3 ? 'orange' : 'blue' };
|
|
const hours = Math.max(1, Math.floor(diff / 3600000));
|
|
return { label: `${hours}h`, color: 'orange' };
|
|
}
|
|
|
|
export default function SubUsageSummary({
|
|
usedByte,
|
|
totalByte,
|
|
usedLabel,
|
|
totalLabel,
|
|
remainedLabel,
|
|
expireMs,
|
|
isActive,
|
|
}: SubUsageSummaryProps) {
|
|
const { t } = useTranslation();
|
|
const pct = useMemo(() => {
|
|
if (totalByte <= 0) return 0;
|
|
const v = (usedByte / totalByte) * 100;
|
|
if (!Number.isFinite(v)) return 0;
|
|
return Math.max(0, Math.min(100, v));
|
|
}, [usedByte, totalByte]);
|
|
|
|
const expiry = formatExpiryChip(expireMs);
|
|
const isUnlimited = totalByte <= 0;
|
|
const stroke = pickStrokeColor(pct);
|
|
|
|
return (
|
|
<div className={`usage-summary ${!isActive ? 'is-inactive' : ''}`}>
|
|
<div className="usage-summary-head">
|
|
<div className="usage-summary-labels">
|
|
<span className="usage-summary-used">{usedLabel}</span>
|
|
<span className="usage-summary-sep">/</span>
|
|
<span className="usage-summary-total">{isUnlimited ? '∞' : totalLabel}</span>
|
|
</div>
|
|
<div className="usage-summary-chips">
|
|
{isUnlimited && (
|
|
<Tag color="purple" icon={<ThunderboltOutlined />}>
|
|
{t('subscription.unlimited')}
|
|
</Tag>
|
|
)}
|
|
{expiry && (
|
|
<Tag color={expiry.color} icon={<ClockCircleOutlined />}>
|
|
{expiry.label}
|
|
</Tag>
|
|
)}
|
|
</div>
|
|
</div>
|
|
{!isUnlimited && (
|
|
<Progress
|
|
percent={pct}
|
|
showInfo={false}
|
|
strokeColor={{ '0%': stroke.from, '100%': stroke.to }}
|
|
railColor="var(--ant-color-fill-secondary)"
|
|
strokeWidth={10}
|
|
className="usage-summary-bar"
|
|
/>
|
|
)}
|
|
<div className="usage-summary-foot">
|
|
{!isUnlimited && (
|
|
<>
|
|
<span className="usage-summary-remained">{remainedLabel}</span>
|
|
<span className="usage-summary-pct">{pct.toFixed(1)}%</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|