ClientBulkAdjustModal.tsx 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. import { useEffect, useState } from 'react';
  2. import { useTranslation } from 'react-i18next';
  3. import { Alert, Form, InputNumber, Modal, message } from 'antd';
  4. const GB = 1024 * 1024 * 1024;
  5. interface ClientBulkAdjustModalProps {
  6. open: boolean;
  7. count: number;
  8. onOpenChange: (open: boolean) => void;
  9. onSubmit: (addDays: number, addBytes: number) => Promise<{ adjusted: number; skipped?: { email: string; reason: string }[] } | null>;
  10. }
  11. export default function ClientBulkAdjustModal({ open, count, onOpenChange, onSubmit }: ClientBulkAdjustModalProps) {
  12. const { t } = useTranslation();
  13. const [messageApi, messageContextHolder] = message.useMessage();
  14. const [addDays, setAddDays] = useState<number>(0);
  15. const [addGB, setAddGB] = useState<number>(0);
  16. const [submitting, setSubmitting] = useState(false);
  17. useEffect(() => {
  18. if (open) {
  19. setAddDays(0);
  20. setAddGB(0);
  21. }
  22. }, [open]);
  23. async function handleOk() {
  24. const days = Math.trunc(Number(addDays) || 0);
  25. const gb = Number(addGB) || 0;
  26. if (days === 0 && gb === 0) {
  27. messageApi.warning(t('pages.clients.bulkAdjustNothing'));
  28. return;
  29. }
  30. setSubmitting(true);
  31. try {
  32. const bytes = Math.trunc(gb * GB);
  33. const result = await onSubmit(days, bytes);
  34. if (!result) return;
  35. const ok = result.adjusted ?? 0;
  36. const skipped = result.skipped?.length ?? 0;
  37. if (skipped === 0) {
  38. messageApi.success(t('pages.clients.toasts.bulkAdjusted', { count: ok }));
  39. } else {
  40. const firstReason = result.skipped?.[0]?.reason ?? '';
  41. messageApi.warning(firstReason
  42. ? `${t('pages.clients.toasts.bulkAdjustedMixed', { ok, skipped })} — ${firstReason}`
  43. : t('pages.clients.toasts.bulkAdjustedMixed', { ok, skipped }));
  44. }
  45. onOpenChange(false);
  46. } finally {
  47. setSubmitting(false);
  48. }
  49. }
  50. return (
  51. <>
  52. {messageContextHolder}
  53. <Modal
  54. open={open}
  55. title={t('pages.clients.bulkAdjustTitle', { count })}
  56. okText={t('apply')}
  57. cancelText={t('cancel')}
  58. confirmLoading={submitting}
  59. onOk={handleOk}
  60. onCancel={() => onOpenChange(false)}
  61. destroyOnHidden
  62. >
  63. <Alert
  64. type="info"
  65. showIcon
  66. style={{ marginBottom: 16 }}
  67. message={t('pages.clients.bulkAdjustHint')}
  68. />
  69. <Form layout="vertical">
  70. <Form.Item label={t('pages.clients.addDays')}>
  71. <InputNumber
  72. value={addDays}
  73. onChange={(v) => setAddDays(Number(v) || 0)}
  74. style={{ width: '100%' }}
  75. step={1}
  76. precision={0}
  77. />
  78. </Form.Item>
  79. <Form.Item label={t('pages.clients.addTrafficGB')}>
  80. <InputNumber
  81. value={addGB}
  82. onChange={(v) => setAddGB(Number(v) || 0)}
  83. style={{ width: '100%' }}
  84. step={1}
  85. />
  86. </Form.Item>
  87. </Form>
  88. </Modal>
  89. </>
  90. );
  91. }