ClientBulkAdjustModal.tsx 3.9 KB

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