ClientBulkAdjustModal.tsx 3.2 KB

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