@omnsight/osint-entity-components 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/index.js +8 -8
  2. package/dist/index.mjs +1429 -1146
  3. package/package.json +23 -2
  4. package/src/App.tsx +397 -141
  5. package/src/assets/icons/generated/boxicons-file-report.tsx +20 -0
  6. package/src/assets/icons/generated/bx-plus-medical.tsx +8 -0
  7. package/src/assets/icons/generated/bx-run.tsx +8 -0
  8. package/src/assets/icons/generated/fa-solid-fist-raised.tsx +8 -0
  9. package/src/assets/icons/generated/fluent-emoji-high-contrast-ballot-box-with-ballot.tsx +8 -0
  10. package/src/assets/icons/generated/glyphs-handshake-bold.tsx +8 -0
  11. package/src/assets/icons/generated/icon-park-currency.tsx +8 -0
  12. package/src/assets/icons/generated/icon-park-great-wall.tsx +8 -0
  13. package/src/assets/icons/generated/iconoir-commodity.tsx +8 -0
  14. package/src/assets/icons/generated/lsicon-work-order-abnormal-outline.tsx +8 -0
  15. package/src/assets/icons/generated/material-symbols-light-drone.tsx +8 -0
  16. package/src/assets/icons/generated/material-symbols-satellite-alt.tsx +8 -0
  17. package/src/assets/icons/generated/mdi-anchor.tsx +19 -0
  18. package/src/assets/icons/generated/ph-mask-happy-fill.tsx +8 -0
  19. package/src/assets/icons/generated/streamline-ultimate-meeting-remote-bold.tsx +19 -0
  20. package/src/assets/icons/generated/tabler-barrier-block.tsx +8 -0
  21. package/src/assets/icons/generated/typcn-flash.tsx +8 -0
  22. package/src/avatars/layouts/AvatarDropdown.tsx +1 -1
  23. package/src/forms/BaseForm.tsx +138 -0
  24. package/src/forms/EditableAttributes.tsx +131 -0
  25. package/src/forms/EventForm/EditableForm.tsx +78 -0
  26. package/src/forms/EventForm/EditingForm.tsx +401 -0
  27. package/src/forms/EventForm/IconFormSection.tsx +54 -0
  28. package/src/forms/EventForm/StaticForm.tsx +272 -0
  29. package/src/forms/EventForm/index.ts +1 -0
  30. package/src/forms/InsightForm/EditableForm.tsx +70 -0
  31. package/src/forms/InsightForm/EditingForm.tsx +79 -0
  32. package/src/forms/InsightForm/StaticForm.tsx +139 -0
  33. package/src/forms/InsightForm/index.ts +1 -0
  34. package/src/forms/MonitoringSourceForm/EditableForm.tsx +59 -0
  35. package/src/forms/MonitoringSourceForm/EditingForm.tsx +192 -0
  36. package/src/forms/MonitoringSourceForm/StaticForm.tsx +107 -0
  37. package/src/forms/MonitoringSourceForm/index.ts +1 -0
  38. package/src/forms/OrganizationForm/EditableForm.tsx +74 -0
  39. package/src/forms/OrganizationForm/EditingForm.tsx +177 -0
  40. package/src/forms/OrganizationForm/IconFormSection.tsx +60 -0
  41. package/src/forms/OrganizationForm/StaticForm.tsx +209 -0
  42. package/src/forms/OrganizationForm/index.ts +1 -0
  43. package/src/forms/PersonForm/EditableForm.tsx +74 -0
  44. package/src/forms/PersonForm/EditingForm.tsx +187 -0
  45. package/src/forms/PersonForm/IconFormSection.tsx +54 -0
  46. package/src/forms/PersonForm/StaticForm.tsx +202 -0
  47. package/src/forms/PersonForm/index.ts +1 -0
  48. package/src/forms/RelationForm/EditableForm.tsx +74 -0
  49. package/src/forms/RelationForm/EditingForm.tsx +147 -0
  50. package/src/forms/RelationForm/StaticForm.tsx +182 -0
  51. package/src/forms/RelationForm/index.ts +1 -0
  52. package/src/forms/SourceForm/EditableForm.tsx +74 -0
  53. package/src/forms/SourceForm/EditingForm.tsx +199 -0
  54. package/src/forms/SourceForm/IconFormSection.tsx +54 -0
  55. package/src/forms/SourceForm/StaticForm.tsx +209 -0
  56. package/src/forms/SourceForm/index.ts +1 -0
  57. package/src/forms/WebsiteForm/EditableForm.tsx +74 -0
  58. package/src/forms/WebsiteForm/EditingForm.tsx +216 -0
  59. package/src/forms/WebsiteForm/IconFormSection.tsx +54 -0
  60. package/src/forms/WebsiteForm/StaticForm.tsx +225 -0
  61. package/src/forms/WebsiteForm/index.ts +1 -0
  62. package/src/forms/accessLevel.ts +48 -0
  63. package/src/forms/index.ts +8 -0
  64. package/src/icons/Event/Select.tsx +7 -6
  65. package/src/icons/Event/icons.ts +112 -4
  66. package/src/icons/Organization/Select.tsx +7 -6
  67. package/src/icons/Person/Select.tsx +7 -6
  68. package/src/icons/Source/Select.tsx +7 -6
  69. package/src/icons/Website/Select.tsx +9 -8
  70. package/src/icons/index.ts +1 -0
  71. package/src/inputs/CountrySelect.tsx +45 -0
  72. package/src/inputs/CustomDatePicker.tsx +51 -0
  73. package/src/inputs/CustomDateTimePicker.tsx +51 -0
  74. package/src/inputs/RangeDatePicker.tsx +99 -0
  75. package/src/inputs/TimezoneSelect.tsx +20 -0
  76. package/src/inputs/index.ts +5 -0
  77. package/src/locales/en.json +153 -1
  78. package/src/locales/zh.json +153 -1
  79. package/src/main.tsx +20 -4
@@ -1,3 +1,20 @@
1
+ import { IconTypcnFlash } from '../../assets/icons/generated/typcn-flash';
2
+ import { IconMaterialSymbolsSatelliteAlt } from '../../assets/icons/generated/material-symbols-satellite-alt';
3
+ import { IconMaterialSymbolsLightDrone } from '../../assets/icons/generated/material-symbols-light-drone';
4
+ import { IconBxPlusMedical } from '../../assets/icons/generated/bx-plus-medical';
5
+ import { IconIconParkGreatWall } from '../../assets/icons/generated/icon-park-great-wall';
6
+ import { IconFaSolidFistRaised } from '../../assets/icons/generated/fa-solid-fist-raised';
7
+ import { IconBxRun } from '../../assets/icons/generated/bx-run';
8
+ import { IconPhMaskHappyFill } from '../../assets/icons/generated/ph-mask-happy-fill';
9
+ import { IconIconoirCommodity } from '../../assets/icons/generated/iconoir-commodity';
10
+ import { IconGlyphsHandshakeBold } from '../../assets/icons/generated/glyphs-handshake-bold';
11
+ import { IconIconParkCurrency } from '../../assets/icons/generated/icon-park-currency';
12
+ import { IconFluentEmojiHighContrastBallotBoxWithBallot } from '../../assets/icons/generated/fluent-emoji-high-contrast-ballot-box-with-ballot';
13
+ import { IconTablerBarrierBlock } from '../../assets/icons/generated/tabler-barrier-block';
14
+ import { IconLsiconWorkOrderAbnormalOutline } from '../../assets/icons/generated/lsicon-work-order-abnormal-outline';
15
+ import { IconBoxiconsFileReport } from '../../assets/icons/generated/boxicons-file-report';
16
+ import { IconMdiAnchor } from '../../assets/icons/generated/mdi-anchor';
17
+ import { IconStreamlineUltimateMeetingRemoteBold } from '../../assets/icons/generated/streamline-ultimate-meeting-remote-bold';
1
18
  import { IconRiSpyFill } from '../../assets/icons/generated/ri-spy-fill';
2
19
  import { IconFluentEmojiHighContrastMilitaryHelmet } from '../../assets/icons/generated/fluent-emoji-high-contrast-military-helmet';
3
20
  import { IconMdiTank } from '../../assets/icons/generated/mdi-tank';
@@ -16,6 +33,7 @@ import { IconIcSharpOilBarrel } from '../../assets/icons/generated/ic-sharp-oil-
16
33
  import { IconRiExchangeBoxFill } from '../../assets/icons/generated/ri-exchange-box-fill';
17
34
  import { IconMingcutePhoneCallFill } from '../../assets/icons/generated/mingcute-phone-call-fill';
18
35
  import { IconBoxiconsAnnouncement } from '../../assets/icons/generated/boxicons-announcement';
36
+ import { IconMdiFactory } from '../../assets/icons/generated/mdi-factory';
19
37
 
20
38
  interface IconOption {
21
39
  value: string;
@@ -30,30 +48,85 @@ export const ICON_OPTIONS: IconOption[] = [
30
48
  label: 'announcement',
31
49
  icon: IconBoxiconsAnnouncement, // icon: boxicons:announcement
32
50
  },
51
+ {
52
+ value: 'conference',
53
+ label: 'conference',
54
+ icon: IconStreamlineUltimateMeetingRemoteBold, // icon: streamline-ultimate:meeting-remote-bold
55
+ },
33
56
  {
34
57
  value: 'conversation',
35
58
  label: 'conversation',
36
59
  icon: IconMingcutePhoneCallFill, // icon: mingcute:phone-call-fill
37
60
  },
61
+ {
62
+ value: 'policy',
63
+ label: 'policy',
64
+ icon: IconLsiconWorkOrderAbnormalOutline, // icon: lsicon:work-order-abnormal-outline
65
+ },
66
+ {
67
+ value: 'border',
68
+ label: 'border',
69
+ icon: IconTablerBarrierBlock, // icon: tabler:barrier-block
70
+ },
71
+ {
72
+ value: 'report',
73
+ label: 'report',
74
+ icon: IconBoxiconsFileReport, // icon: boxicons:file-report
75
+ },
76
+ {
77
+ value: 'election',
78
+ label: 'election',
79
+ icon: IconFluentEmojiHighContrastBallotBoxWithBallot, // icon: fluent-emoji-high-contrast:ballot-box-with-ballot
80
+ },
38
81
  {
39
82
  value: 'trade',
40
83
  label: 'trade',
41
84
  icon: IconRiExchangeBoxFill, // icon: ri:exchange-box-fill
42
85
  },
43
86
  {
44
- value: 'oil',
45
- label: 'oil',
46
- icon: IconIcSharpOilBarrel, // icon: ic:sharp-oil-barrel
87
+ value: 'currency',
88
+ label: 'currency',
89
+ icon: IconIconParkCurrency, // icon: icon-park:currency
47
90
  },
48
91
  {
49
92
  value: 'exchange',
50
93
  label: 'exchange',
51
94
  icon: IconHugeiconsTradeUp, // icon: hugeicons:trade-up
52
95
  },
96
+ {
97
+ value: 'acquisition',
98
+ label: 'acquisition',
99
+ icon: IconGlyphsHandshakeBold, // icon: glyphs:handshake-bold
100
+ },
101
+ {
102
+ value: 'oil',
103
+ label: 'oil',
104
+ icon: IconIcSharpOilBarrel, // icon: ic:sharp-oil-barrel
105
+ },
106
+ {
107
+ value: 'commodity',
108
+ label: 'commodity',
109
+ icon: IconIconoirCommodity, // icon: iconoir:commodity
110
+ },
111
+ {
112
+ value: 'manufacturing',
113
+ label: 'manufacturing',
114
+ icon: IconMdiFactory, // icon: mdi:factory
115
+ },
53
116
  {
54
117
  value: 'supplychain-risk',
55
118
  label: 'supplychain-risk',
56
- icon: IconFluentEmojiHighContrastBrokenChain, // icon: fluent-emoji-high-contrast:broken-chain
119
+ icon: IconFluentEmojiHighContrastBrokenChain, // icon: fluent-emoji-high-contrast:broken-chain fa/flash
120
+ },
121
+ {
122
+ value: 'electricity',
123
+ label: 'electricity',
124
+ icon: IconTypcnFlash, // icon: typcn:flash
125
+ },
126
+ {
127
+ value: 'harbor',
128
+ label: 'harbor',
129
+ icon: IconMdiAnchor, // icon: mdi:anchor
57
130
  },
58
131
  {
59
132
  value: 'ship',
@@ -75,11 +148,36 @@ export const ICON_OPTIONS: IconOption[] = [
75
148
  label: 'crime',
76
149
  icon: IconMdiHandcuffs, // icon: mdi:handcuffs
77
150
  },
151
+ {
152
+ value: 'cyber',
153
+ label: 'cyber',
154
+ icon: IconPhMaskHappyFill, // icon: ph:mask-happy-fill
155
+ },
78
156
  {
79
157
  value: 'shot',
80
158
  label: 'shot',
81
159
  icon: IconGameIconsPistolGun, // icon: game-icons:pistol-gun
82
160
  },
161
+ {
162
+ value: 'evacuation',
163
+ label: 'evacuation',
164
+ icon: IconBxRun, // icon: bx:run
165
+ },
166
+ {
167
+ value: 'civil-unrest',
168
+ label: 'civil-unrest',
169
+ icon: IconFaSolidFistRaised, // icon: fa-solid:fist-raised
170
+ },
171
+ {
172
+ value: 'defense',
173
+ label: 'defense',
174
+ icon: IconIconParkGreatWall, // icon: icon-park:great-wall
175
+ },
176
+ {
177
+ value: 'medical',
178
+ label: 'medical',
179
+ icon: IconBxPlusMedical, // icon: bx:plus-medical
180
+ },
83
181
  {
84
182
  value: 'bomb',
85
183
  label: 'bomb',
@@ -95,6 +193,11 @@ export const ICON_OPTIONS: IconOption[] = [
95
193
  label: 'plane',
96
194
  icon: IconMdiAirplane, // icon: mdi:airplane
97
195
  },
196
+ {
197
+ value: 'drone',
198
+ label: 'drone',
199
+ icon: IconMaterialSymbolsLightDrone, // icon: material-symbols-light:drone
200
+ },
98
201
  {
99
202
  value: 'naval',
100
203
  label: 'naval',
@@ -115,4 +218,9 @@ export const ICON_OPTIONS: IconOption[] = [
115
218
  label: 'intelligence',
116
219
  icon: IconRiSpyFill, // icon: ri:spy-fill
117
220
  },
221
+ {
222
+ value: 'satellite',
223
+ label: 'satellite',
224
+ icon: IconMaterialSymbolsSatelliteAlt, // icon: material-symbols:satellite-alt
225
+ },
118
226
  ]
@@ -24,8 +24,8 @@ export const OrganizationIconSelector: React.FC<
24
24
  return (
25
25
  <Select
26
26
  leftSection={<OrganizationIcon organization={data} />}
27
- defaultValue={translatedOptions[0].value}
28
- value={value ?? ""}
27
+ value={value}
28
+ placeholder={t("input.icon")}
29
29
  onChange={onChange}
30
30
  data={translatedOptions}
31
31
  error={error}
@@ -34,7 +34,7 @@ export const OrganizationIconSelector: React.FC<
34
34
  };
35
35
 
36
36
  interface OrganizationColorSelectorProps {
37
- value?: string | null;
37
+ value?: string;
38
38
  onChange: (value: string | null) => void;
39
39
  error?: string;
40
40
  }
@@ -42,6 +42,7 @@ interface OrganizationColorSelectorProps {
42
42
  export const OrganizationColorSelector: React.FC<
43
43
  OrganizationColorSelectorProps
44
44
  > = ({ value, onChange, error }) => {
45
+ const { t } = useTranslation();
45
46
  const colors = [
46
47
  "#0089ff",
47
48
  "#ff0000",
@@ -54,8 +55,8 @@ export const OrganizationColorSelector: React.FC<
54
55
 
55
56
  return (
56
57
  <ColorInput
57
- defaultValue={colors[0]}
58
- value={value ?? ""}
58
+ value={value}
59
+ placeholder={t("input.color")}
59
60
  onChange={onChange}
60
61
  swatches={colors}
61
62
  error={error}
@@ -91,7 +92,7 @@ export const OrganizationIconSelect: React.FC<OrganizationIconSelectProps> = ({
91
92
  onChange={handleTypeChange}
92
93
  />
93
94
  <OrganizationColorSelector
94
- value={String(value.attributes?.icon_color)}
95
+ value={value.attributes?.icon_color as string | undefined}
95
96
  onChange={handleColorChange}
96
97
  />
97
98
  </Group>
@@ -27,8 +27,8 @@ export const PersonIconSelector: React.FC<PersonIconSelectorProps> = ({
27
27
  return (
28
28
  <Select
29
29
  leftSection={<PersonIcon person={data} />}
30
- defaultValue={translatedOptions[0].value}
31
- value={value ?? ""}
30
+ value={value}
31
+ placeholder={t("input.icon")}
32
32
  onChange={onChange}
33
33
  data={translatedOptions}
34
34
  error={error}
@@ -37,7 +37,7 @@ export const PersonIconSelector: React.FC<PersonIconSelectorProps> = ({
37
37
  };
38
38
 
39
39
  interface PersonColorSelectorProps {
40
- value?: string | null;
40
+ value?: string;
41
41
  onChange: (value: string | null) => void;
42
42
  error?: string;
43
43
  }
@@ -47,6 +47,7 @@ export const PersonColorSelector: React.FC<PersonColorSelectorProps> = ({
47
47
  onChange,
48
48
  error,
49
49
  }) => {
50
+ const { t } = useTranslation();
50
51
  const colors = [
51
52
  "#0089ff",
52
53
  "#ff0000",
@@ -59,8 +60,8 @@ export const PersonColorSelector: React.FC<PersonColorSelectorProps> = ({
59
60
 
60
61
  return (
61
62
  <ColorInput
62
- defaultValue={colors[0]}
63
- value={value ?? ""}
63
+ value={value}
64
+ placeholder={t("input.color")}
64
65
  onChange={onChange}
65
66
  swatches={colors}
66
67
  error={error}
@@ -96,7 +97,7 @@ export const PersonIconSelect: React.FC<PersonIconSelectProps> = ({
96
97
  onChange={handleTypeChange}
97
98
  />
98
99
  <PersonColorSelector
99
- value={String(value.attributes?.icon_color)}
100
+ value={value.attributes?.icon_color as string | undefined}
100
101
  onChange={handleColorChange}
101
102
  />
102
103
  </Group>
@@ -27,8 +27,8 @@ export const SourceIconSelector: React.FC<SourceIconSelectorProps> = ({
27
27
  return (
28
28
  <Select
29
29
  leftSection={<SourceIcon source={data} />}
30
- defaultValue={translatedOptions[0].value}
31
- value={value ?? ""}
30
+ value={value}
31
+ placeholder={t("input.icon")}
32
32
  onChange={onChange}
33
33
  data={translatedOptions}
34
34
  error={error}
@@ -37,7 +37,7 @@ export const SourceIconSelector: React.FC<SourceIconSelectorProps> = ({
37
37
  };
38
38
 
39
39
  interface SourceColorSelectorProps {
40
- value?: string | null;
40
+ value?: string;
41
41
  onChange: (value: string | null) => void;
42
42
  error?: string;
43
43
  }
@@ -47,6 +47,7 @@ export const SourceColorSelector: React.FC<SourceColorSelectorProps> = ({
47
47
  onChange,
48
48
  error,
49
49
  }) => {
50
+ const { t } = useTranslation();
50
51
  const colors = [
51
52
  "#ababab",
52
53
  "#0089ff",
@@ -59,8 +60,8 @@ export const SourceColorSelector: React.FC<SourceColorSelectorProps> = ({
59
60
 
60
61
  return (
61
62
  <ColorInput
62
- defaultValue={colors[0]}
63
- value={value ?? ""}
63
+ value={value}
64
+ placeholder={t("input.color")}
64
65
  onChange={onChange}
65
66
  swatches={colors}
66
67
  error={error}
@@ -96,7 +97,7 @@ export const SourceIconSelect: React.FC<SourceIconSelectProps> = ({
96
97
  onChange={handleTypeChange}
97
98
  />
98
99
  <SourceColorSelector
99
- value={String(value.attributes?.icon_color ?? "")}
100
+ value={value.attributes?.icon_color as string | undefined}
100
101
  onChange={handleColorChange}
101
102
  />
102
103
  </Group>
@@ -27,8 +27,8 @@ export const WebsiteIconSelector: React.FC<WebsiteIconSelectorProps> = ({
27
27
  return (
28
28
  <Select
29
29
  leftSection={<WebsiteIcon website={data} />}
30
- defaultValue={translatedOptions[0].value}
31
- value={value ?? ""}
30
+ value={value}
31
+ placeholder={t("input.icon")}
32
32
  onChange={onChange}
33
33
  data={translatedOptions}
34
34
  error={error}
@@ -37,7 +37,7 @@ export const WebsiteIconSelector: React.FC<WebsiteIconSelectorProps> = ({
37
37
  };
38
38
 
39
39
  interface WebsiteColorSelectorProps {
40
- value?: string | null;
40
+ value?: string;
41
41
  onChange: (value: string | null) => void;
42
42
  error?: string;
43
43
  }
@@ -47,6 +47,7 @@ export const WebsiteColorSelector: React.FC<WebsiteColorSelectorProps> = ({
47
47
  onChange,
48
48
  error,
49
49
  }) => {
50
+ const { t } = useTranslation();
50
51
  const colors = [
51
52
  "#0089ff",
52
53
  "#ff0000",
@@ -59,8 +60,8 @@ export const WebsiteColorSelector: React.FC<WebsiteColorSelectorProps> = ({
59
60
 
60
61
  return (
61
62
  <ColorInput
62
- defaultValue={colors[0]}
63
- value={value ?? ""}
63
+ value={value}
64
+ placeholder={t("input.color")}
64
65
  onChange={onChange}
65
66
  swatches={colors}
66
67
  error={error}
@@ -78,7 +79,7 @@ export const WebsiteIconSelect: React.FC<WebsiteIconSelectProps> = ({
78
79
  onChange,
79
80
  }) => {
80
81
  const handleTypeChange = (type: string | null) => {
81
- onChange({ ...value, title: type || undefined });
82
+ onChange({ ...value, type: type || undefined });
82
83
  };
83
84
 
84
85
  const handleColorChange = (color: string | null) => {
@@ -92,11 +93,11 @@ export const WebsiteIconSelect: React.FC<WebsiteIconSelectProps> = ({
92
93
  <Group>
93
94
  <WebsiteIconSelector
94
95
  data={value}
95
- value={value.title}
96
+ value={value.type}
96
97
  onChange={handleTypeChange}
97
98
  />
98
99
  <WebsiteColorSelector
99
- value={String(value.attributes?.icon_color)}
100
+ value={value.attributes?.icon_color as string | undefined}
100
101
  onChange={handleColorChange}
101
102
  />
102
103
  </Group>
@@ -8,3 +8,4 @@ export { SourceIcon } from './Source/Icon';
8
8
  export { SourceIconSelect, SourceColorSelector, SourceIconSelector } from './Source/Select';
9
9
  export { WebsiteIcon } from './Website/Icon';
10
10
  export { WebsiteIconSelect, WebsiteColorSelector, WebsiteIconSelector } from './Website/Select';
11
+ export { IconMdiAnchor } from '../assets/icons/generated/mdi-anchor';
@@ -0,0 +1,45 @@
1
+ import React, { useMemo } from 'react';
2
+ import { Select } from '@mantine/core';
3
+ import { useTranslation } from 'react-i18next';
4
+ import countries from 'i18n-iso-countries';
5
+ import enLocale from 'i18n-iso-countries/langs/en.json';
6
+ import zhLocale from 'i18n-iso-countries/langs/zh.json';
7
+
8
+ // Register locales
9
+ countries.registerLocale(enLocale);
10
+ countries.registerLocale(zhLocale);
11
+
12
+ interface Props {
13
+ country?: string;
14
+ setCountry: (country: string) => void;
15
+ }
16
+
17
+ export const CountrySelect: React.FC<Props> = ({ country, setCountry }) => {
18
+ const { i18n, t } = useTranslation();
19
+
20
+ const countryOptions = useMemo(() => {
21
+ // Map i18next language code to i18n-iso-countries language code
22
+ // 'zh' in i18next -> 'zh' in i18n-iso-countries
23
+ const lang = i18n.language.startsWith('zh') ? 'zh' : 'en';
24
+ const countryObj = countries.getNames(lang, { select: 'official' });
25
+
26
+ return Object.entries(countryObj)
27
+ .map(([code, name]) => ({
28
+ value: code,
29
+ label: name,
30
+ }))
31
+ .sort((a, b) => a.label.localeCompare(b.label));
32
+ }, [i18n.language]);
33
+
34
+ return (
35
+ <Select
36
+ placeholder={t('components.inputs.CountrySelect.select')}
37
+ data={countryOptions}
38
+ value={country}
39
+ onChange={(val) => val && setCountry(val)}
40
+ searchable
41
+ clearable
42
+ style={{ width: 200 }}
43
+ />
44
+ );
45
+ };
@@ -0,0 +1,51 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { DatePickerInput } from '@mantine/dates';
3
+ import { Group } from '@mantine/core';
4
+ import { TimezoneSelectComponent } from './TimezoneSelect';
5
+ import { toZonedTime, fromZonedTime } from 'date-fns-tz';
6
+
7
+ interface Props {
8
+ value: Date | null;
9
+ onChange: (date: Date | null) => void;
10
+ placeholder?: string;
11
+ error?: string;
12
+ }
13
+
14
+ export const CustomDatePicker: React.FC<Props> = ({ value, onChange, placeholder, error }) => {
15
+ const [timezone, setTimezone] = useState(Intl.DateTimeFormat().resolvedOptions().timeZone);
16
+ const [zonedDate, setZonedDate] = useState<Date | null>(null);
17
+
18
+ useEffect(() => {
19
+ if (value) {
20
+ setZonedDate(toZonedTime(value, timezone));
21
+ } else {
22
+ setZonedDate(null);
23
+ }
24
+ }, [value, timezone]);
25
+
26
+ const handleDateChange = (date: Date | string | null) => {
27
+ if (date) {
28
+ const dateObj = new Date(date);
29
+ if (!isNaN(dateObj.getTime())) {
30
+ const utcDate = fromZonedTime(dateObj, timezone);
31
+ onChange(utcDate);
32
+ } else {
33
+ onChange(null);
34
+ }
35
+ } else {
36
+ onChange(null);
37
+ }
38
+ };
39
+
40
+ return (
41
+ <Group grow>
42
+ <DatePickerInput
43
+ value={zonedDate}
44
+ onChange={handleDateChange}
45
+ placeholder={placeholder}
46
+ error={error}
47
+ />
48
+ <TimezoneSelectComponent timezone={timezone} setTimezone={setTimezone} />
49
+ </Group>
50
+ );
51
+ };
@@ -0,0 +1,51 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { DateTimePicker } from '@mantine/dates';
3
+ import { Group } from '@mantine/core';
4
+ import { TimezoneSelectComponent } from './TimezoneSelect';
5
+ import { toZonedTime, fromZonedTime } from 'date-fns-tz';
6
+
7
+ interface Props {
8
+ value: Date | null;
9
+ onChange: (date: Date | null) => void;
10
+ placeholder?: string;
11
+ error?: string;
12
+ }
13
+
14
+ export const CustomDateTimePicker: React.FC<Props> = ({ value, onChange, placeholder, error }) => {
15
+ const [timezone, setTimezone] = useState(Intl.DateTimeFormat().resolvedOptions().timeZone);
16
+ const [zonedDate, setZonedDate] = useState<Date | null>(null);
17
+
18
+ useEffect(() => {
19
+ if (value) {
20
+ setZonedDate(toZonedTime(value, timezone));
21
+ } else {
22
+ setZonedDate(null);
23
+ }
24
+ }, [value, timezone]);
25
+
26
+ const handleDateChange = (date: string | null) => {
27
+ if (date) {
28
+ const dateObj = new Date(date);
29
+ if (!isNaN(dateObj.getTime())) {
30
+ const utcDate = fromZonedTime(dateObj, timezone);
31
+ onChange(utcDate);
32
+ } else {
33
+ onChange(null);
34
+ }
35
+ } else {
36
+ onChange(null);
37
+ }
38
+ };
39
+
40
+ return (
41
+ <Group grow>
42
+ <DateTimePicker
43
+ value={zonedDate}
44
+ onChange={handleDateChange}
45
+ placeholder={placeholder}
46
+ error={error}
47
+ />
48
+ <TimezoneSelectComponent timezone={timezone} setTimezone={setTimezone} />
49
+ </Group>
50
+ );
51
+ };
@@ -0,0 +1,99 @@
1
+ import React from 'react';
2
+ import { Group, Button, Stack, Text } from '@mantine/core';
3
+ import { DatePicker } from '@mantine/dates';
4
+ import { notifications } from '@mantine/notifications';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ interface Props {
8
+ dateRange: [Date | undefined, Date | undefined];
9
+ setDateRange: (val: [Date | undefined, Date | undefined]) => void;
10
+ }
11
+
12
+ export const RangeDatePicker: React.FC<Props> = ({ dateRange, setDateRange }) => {
13
+ const { i18n, t } = useTranslation();
14
+
15
+ const handleDateChange = (val: [string | null, string | null]) => {
16
+ if (val[0] && val[1]) {
17
+ const [sYear, sMonth, sDay] = val[0].split('-').map(Number);
18
+ const [eYear, eMonth, eDay] = val[1].split('-').map(Number);
19
+ const start = new Date(sYear, sMonth - 1, sDay);
20
+ const end = new Date(eYear, eMonth - 1, eDay);
21
+ end.setHours(23, 59, 59, 999);
22
+
23
+ const maxEndDate = new Date(start);
24
+ maxEndDate.setMonth(maxEndDate.getMonth() + 1);
25
+
26
+ if (end > maxEndDate) {
27
+ notifications.show({
28
+ title: t('components.inputs.RangeDatePicker.dateLimitExceeded'),
29
+ message: t('components.inputs.RangeDatePicker.dateLimitMessage'),
30
+ color: 'orange',
31
+ });
32
+ setDateRange([start, maxEndDate]);
33
+ } else {
34
+ setDateRange([start, end]);
35
+ }
36
+ } else {
37
+ setDateRange([undefined, undefined]);
38
+ }
39
+ };
40
+
41
+ const applyPreset = (type: 'today' | 'yesterday' | 'lastWeek' | 'lastMonth') => {
42
+ const end = new Date();
43
+ const start = new Date();
44
+
45
+ start.setHours(0, 0, 0, 0);
46
+ end.setHours(23, 59, 59, 999);
47
+
48
+ switch (type) {
49
+ case 'today':
50
+ // start and end are already today
51
+ break;
52
+ case 'yesterday':
53
+ start.setDate(start.getDate() - 1);
54
+ end.setDate(end.getDate() - 1);
55
+ break;
56
+ case 'lastWeek':
57
+ start.setDate(start.getDate() - 7);
58
+ break;
59
+ case 'lastMonth':
60
+ start.setMonth(start.getMonth() - 1);
61
+ break;
62
+ }
63
+ setDateRange([start, end]);
64
+ };
65
+
66
+ return (
67
+ <Group align="flex-start">
68
+ <Stack gap="xs">
69
+ <Text size="sm" fw={500} mb={5}>
70
+ {t('components.inputs.RangeDatePicker.dateRange')}
71
+ </Text>
72
+ <Button variant="light" size="xs" onClick={() => applyPreset('today')}>
73
+ {t('components.inputs.RangeDatePicker.today')}
74
+ </Button>
75
+ <Button variant="light" size="xs" onClick={() => applyPreset('yesterday')}>
76
+ {t('components.inputs.RangeDatePicker.yesterday')}
77
+ </Button>
78
+ <Button variant="light" size="xs" onClick={() => applyPreset('lastWeek')}>
79
+ {t('components.inputs.RangeDatePicker.lastWeek')}
80
+ </Button>
81
+ <Button variant="light" size="xs" onClick={() => applyPreset('lastMonth')}>
82
+ {t('components.inputs.RangeDatePicker.lastMonth')}
83
+ </Button>
84
+ </Stack>
85
+ <Stack gap="xs">
86
+ <Text size="sm" fw={500} mb={5}>
87
+ {t('components.inputs.RangeDatePicker.selectDateRange')}
88
+ </Text>
89
+ <DatePicker
90
+ type="range"
91
+ locale={i18n.language}
92
+ value={dateRange[0] && dateRange[1] && [dateRange[0], dateRange[1]]}
93
+ onChange={handleDateChange}
94
+ numberOfColumns={2}
95
+ />
96
+ </Stack>
97
+ </Group>
98
+ );
99
+ };
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import TimezoneSelect from 'react-timezone-select';
4
+
5
+ interface Props {
6
+ timezone: string;
7
+ setTimezone: (timezone: string) => void;
8
+ }
9
+
10
+ export const TimezoneSelectComponent: React.FC<Props> = ({ timezone, setTimezone }) => {
11
+ const { t } = useTranslation();
12
+
13
+ return (
14
+ <TimezoneSelect
15
+ value={timezone}
16
+ onChange={(tz) => setTimezone(tz.value)}
17
+ placeholder={t('components.inputs.TimezoneSelect.select')}
18
+ />
19
+ );
20
+ };
@@ -0,0 +1,5 @@
1
+ export * from './CountrySelect';
2
+ export * from './CustomDatePicker';
3
+ export * from './CustomDateTimePicker';
4
+ export * from './RangeDatePicker';
5
+ export * from './TimezoneSelect';