@omnsight/osint-entity-components 0.2.4 → 0.2.6

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 (60) hide show
  1. package/dist/index.js +8 -8
  2. package/dist/index.mjs +796 -781
  3. package/package.json +15 -2
  4. package/src/App.tsx +397 -141
  5. package/src/avatars/layouts/AvatarDropdown.tsx +1 -1
  6. package/src/avatars/layouts/AvatarSpan.tsx +7 -2
  7. package/src/forms/BaseForm.tsx +138 -0
  8. package/src/forms/EditableAttributes.tsx +131 -0
  9. package/src/forms/EventForm/EditableForm.tsx +78 -0
  10. package/src/forms/EventForm/EditingForm.tsx +401 -0
  11. package/src/forms/EventForm/IconFormSection.tsx +54 -0
  12. package/src/forms/EventForm/StaticForm.tsx +272 -0
  13. package/src/forms/EventForm/index.ts +1 -0
  14. package/src/forms/InsightForm/EditableForm.tsx +70 -0
  15. package/src/forms/InsightForm/EditingForm.tsx +79 -0
  16. package/src/forms/InsightForm/StaticForm.tsx +139 -0
  17. package/src/forms/InsightForm/index.ts +1 -0
  18. package/src/forms/MonitoringSourceForm/EditableForm.tsx +59 -0
  19. package/src/forms/MonitoringSourceForm/EditingForm.tsx +192 -0
  20. package/src/forms/MonitoringSourceForm/StaticForm.tsx +107 -0
  21. package/src/forms/MonitoringSourceForm/index.ts +1 -0
  22. package/src/forms/OrganizationForm/EditableForm.tsx +74 -0
  23. package/src/forms/OrganizationForm/EditingForm.tsx +177 -0
  24. package/src/forms/OrganizationForm/IconFormSection.tsx +60 -0
  25. package/src/forms/OrganizationForm/StaticForm.tsx +209 -0
  26. package/src/forms/OrganizationForm/index.ts +1 -0
  27. package/src/forms/PersonForm/EditableForm.tsx +74 -0
  28. package/src/forms/PersonForm/EditingForm.tsx +187 -0
  29. package/src/forms/PersonForm/IconFormSection.tsx +54 -0
  30. package/src/forms/PersonForm/StaticForm.tsx +202 -0
  31. package/src/forms/PersonForm/index.ts +1 -0
  32. package/src/forms/RelationForm/EditableForm.tsx +74 -0
  33. package/src/forms/RelationForm/EditingForm.tsx +147 -0
  34. package/src/forms/RelationForm/StaticForm.tsx +182 -0
  35. package/src/forms/RelationForm/index.ts +1 -0
  36. package/src/forms/SourceForm/EditableForm.tsx +74 -0
  37. package/src/forms/SourceForm/EditingForm.tsx +199 -0
  38. package/src/forms/SourceForm/IconFormSection.tsx +54 -0
  39. package/src/forms/SourceForm/StaticForm.tsx +209 -0
  40. package/src/forms/SourceForm/index.ts +1 -0
  41. package/src/forms/WebsiteForm/EditableForm.tsx +74 -0
  42. package/src/forms/WebsiteForm/EditingForm.tsx +216 -0
  43. package/src/forms/WebsiteForm/IconFormSection.tsx +54 -0
  44. package/src/forms/WebsiteForm/StaticForm.tsx +225 -0
  45. package/src/forms/WebsiteForm/index.ts +1 -0
  46. package/src/forms/accessLevel.ts +48 -0
  47. package/src/forms/index.ts +8 -0
  48. package/src/icons/Event/Select.tsx +7 -6
  49. package/src/icons/Organization/Select.tsx +7 -6
  50. package/src/icons/Person/Select.tsx +7 -6
  51. package/src/icons/Source/Select.tsx +7 -6
  52. package/src/icons/Website/Select.tsx +9 -8
  53. package/src/inputs/CountrySelect.tsx +45 -0
  54. package/src/inputs/CustomDatePicker.tsx +51 -0
  55. package/src/inputs/CustomDateTimePicker.tsx +51 -0
  56. package/src/inputs/RangeDatePicker.tsx +99 -0
  57. package/src/inputs/TimezoneSelect.tsx +20 -0
  58. package/src/locales/en.json +135 -0
  59. package/src/locales/zh.json +135 -0
  60. package/src/main.tsx +20 -4
@@ -0,0 +1,216 @@
1
+ import React, { useState, type CSSProperties } from 'react';
2
+ import {
3
+ Stack,
4
+ Group,
5
+ Text,
6
+ ActionIcon,
7
+ Divider,
8
+ UnstyledButton,
9
+ Collapse,
10
+ Title,
11
+ TextInput,
12
+ Textarea,
13
+ TagsInput,
14
+ } from '@mantine/core';
15
+ import { CustomDatePicker } from '../../inputs/CustomDatePicker';
16
+ import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline';
17
+ import { useTranslation } from 'react-i18next';
18
+ import { type Website } from 'omni-osint-crud-client';
19
+ import { EditableAttributes } from '../EditableAttributes';
20
+ import { ChevronDownIcon } from '@heroicons/react/24/outline';
21
+ import { Controller } from 'react-hook-form';
22
+ import { BaseForm } from '../BaseForm';
23
+ import { WebsiteIcon } from '@omnsight/osint-entity-components/icons';
24
+ import { WebsiteIconFormSection } from './IconFormSection';
25
+
26
+ interface Props {
27
+ website: Website;
28
+ onSubmit?: (data: Website) => void;
29
+ onUpdate?: (data: Partial<Website>) => void;
30
+ onClose?: () => void;
31
+ children?: React.ReactNode;
32
+ style?: CSSProperties;
33
+ }
34
+
35
+ export const EditingForm: React.FC<Props> = ({
36
+ website,
37
+ onSubmit,
38
+ onUpdate,
39
+ onClose,
40
+ children,
41
+ style,
42
+ }) => {
43
+ const { t } = useTranslation();
44
+ const [attributesOpen, setAttributesOpen] = useState(false);
45
+
46
+ const handlClose = () => {
47
+ onClose?.();
48
+ };
49
+
50
+ return (
51
+ <BaseForm<Website>
52
+ style={style}
53
+ icon={<WebsiteIcon website={website} />}
54
+ title={t('components.forms.WebsiteForm.title')}
55
+ onClose={handlClose}
56
+ defaultValues={website}
57
+ onSubmit={onSubmit}
58
+ onUpdate={onUpdate}
59
+ onlyShowEditOnDirty={false}
60
+ >
61
+ {({ control, formState: { errors } }) => (
62
+ <Stack
63
+ pos="relative"
64
+ gap="xs"
65
+ style={{ cursor: 'default' }}
66
+ >
67
+ <Group gap="xs">
68
+ <Text size="sm" fw={500}>
69
+ {t('placeholder.title')}
70
+ </Text>
71
+ <Controller
72
+ name="title"
73
+ control={control}
74
+ rules={{ required: t('common.required') }}
75
+ render={({ field }) => (
76
+ <TextInput
77
+ {...field}
78
+ value={field.value || ''}
79
+ placeholder={t('placeholder.title')}
80
+ style={{ flex: 'initial' }}
81
+ error={errors.title?.message}
82
+ />
83
+ )}
84
+ />
85
+ {website.url && (
86
+ <ActionIcon
87
+ component="a"
88
+ href={website.url}
89
+ target="_blank"
90
+ variant="subtle"
91
+ size="sm"
92
+ >
93
+ <ArrowTopRightOnSquareIcon style={{ width: '70%', height: '70%' }} />
94
+ </ActionIcon>
95
+ )}
96
+ </Group>
97
+
98
+ <Group gap={4}>
99
+ <Text>{t('placeholder.url')}:</Text>
100
+ <Controller
101
+ name="url"
102
+ control={control}
103
+ rules={{ required: t('common.required') }}
104
+ render={({ field }) => (
105
+ <TextInput
106
+ {...field}
107
+ value={field.value ?? ''}
108
+ placeholder={t('placeholder.url')}
109
+ error={errors.url?.message}
110
+ />
111
+ )}
112
+ />
113
+ </Group>
114
+
115
+ <Group gap={4}>
116
+ <Text>{t('placeholder.description')}:</Text>
117
+ <Controller
118
+ name="description"
119
+ control={control}
120
+ render={({ field }) => (
121
+ <Textarea
122
+ {...field}
123
+ value={field.value || ''}
124
+ placeholder={t('placeholder.description')}
125
+ />
126
+ )}
127
+ />
128
+ </Group>
129
+
130
+ <Group gap={4}>
131
+ <Text size="sm" c="dimmed">
132
+ {t('placeholder.type')}:
133
+ </Text>
134
+ <WebsiteIconFormSection data={website} />
135
+ </Group>
136
+
137
+ <Group gap={4}>
138
+ <Text size="sm" c="dimmed">
139
+ {t('placeholder.foundedDate')}:
140
+ </Text>
141
+ <Controller
142
+ name="founded_at"
143
+ control={control}
144
+ render={({ field }) => (
145
+ <CustomDatePicker
146
+ value={field.value ? new Date(field.value * 1000) : null}
147
+ onChange={(date) => field.onChange(date ? date.getTime() / 1000 : 0)}
148
+ placeholder={t('placeholder.foundedDate')}
149
+ />
150
+ )}
151
+ />
152
+ </Group>
153
+
154
+ <Group gap={4}>
155
+ <Text size="sm" c="dimmed">
156
+ {t('placeholder.discoveredDate')}:
157
+ </Text>
158
+ <Controller
159
+ name="discovered_at"
160
+ control={control}
161
+ render={({ field }) => (
162
+ <CustomDatePicker
163
+ value={field.value ? new Date(field.value * 1000) : null}
164
+ onChange={(date) => field.onChange(date ? date.getTime() / 1000 : 0)}
165
+ placeholder={t('placeholder.discoveredDate')}
166
+ />
167
+ )}
168
+ />
169
+ </Group>
170
+
171
+ <Text size="sm" fw={500}>
172
+ {t('placeholder.tags')}
173
+ </Text>
174
+ <Controller
175
+ name="tags"
176
+ control={control}
177
+ render={({ field }) => (
178
+ <TagsInput
179
+ {...field}
180
+ value={field.value || []}
181
+ placeholder={t('placeholder.tags')}
182
+ />
183
+ )}
184
+ />
185
+
186
+ {children}
187
+
188
+ <Divider my="sm" />
189
+
190
+ <UnstyledButton onClick={() => setAttributesOpen((o) => !o)}>
191
+ <Group justify="space-between">
192
+ <Title order={5}>{t('placeholder.attributes')}</Title>
193
+ <ChevronDownIcon
194
+ style={{
195
+ width: 16,
196
+ transform: attributesOpen ? 'rotate(180deg)' : 'rotate(0deg)',
197
+ transition: 'transform 200ms ease',
198
+ }}
199
+ />
200
+ </Group>
201
+ </UnstyledButton>
202
+
203
+ <Collapse in={attributesOpen}>
204
+ <Controller
205
+ name="attributes"
206
+ control={control}
207
+ render={({ field }) => (
208
+ <EditableAttributes {...field} value={field.value || {}} isEditing={true} />
209
+ )}
210
+ />
211
+ </Collapse>
212
+ </Stack>
213
+ )}
214
+ </BaseForm>
215
+ );
216
+ };
@@ -0,0 +1,54 @@
1
+ import { Group } from "@mantine/core";
2
+ import {
3
+ WebsiteColorSelector,
4
+ WebsiteIconSelector,
5
+ } from "@omnsight/osint-entity-components/icons";
6
+ import type { Website } from "omni-osint-crud-client";
7
+ import { Controller, useFormContext, useWatch } from "react-hook-form";
8
+ import { useTranslation } from "react-i18next";
9
+
10
+ export const WebsiteIconFormSection = ({ data }: { data: Website }) => {
11
+ const {
12
+ control,
13
+ formState: { errors },
14
+ } = useFormContext<Website>();
15
+ const { t } = useTranslation();
16
+ const type = useWatch({ control, name: "type" });
17
+ const iconColor = useWatch({ control, name: "attributes.icon_color" });
18
+
19
+ const modifiedData = {
20
+ ...data,
21
+ type: type,
22
+ attributes: { ...data.attributes, icon_color: iconColor },
23
+ };
24
+
25
+ return (
26
+ <Group grow>
27
+ <Controller
28
+ name="type"
29
+ control={control}
30
+ rules={{ required: t("common.required") }}
31
+ render={({ field }) => (
32
+ <WebsiteIconSelector
33
+ {...field}
34
+ data={modifiedData}
35
+ value={field.value}
36
+ error={errors.type?.message}
37
+ />
38
+ )}
39
+ />
40
+ <Controller
41
+ name="attributes.icon_color"
42
+ control={control}
43
+ rules={{ required: t("common.required") }}
44
+ render={({ field }) => (
45
+ <WebsiteColorSelector
46
+ {...field}
47
+ value={field.value as string | undefined}
48
+ error={errors.attributes?.icon_color?.message}
49
+ />
50
+ )}
51
+ />
52
+ </Group>
53
+ );
54
+ };
@@ -0,0 +1,225 @@
1
+ import React, {
2
+ useState,
3
+ type CSSProperties,
4
+ type PropsWithChildren,
5
+ } from "react";
6
+ import {
7
+ Stack,
8
+ Group,
9
+ Text,
10
+ ActionIcon,
11
+ Divider,
12
+ UnstyledButton,
13
+ Collapse,
14
+ Title,
15
+ rem,
16
+ Tooltip,
17
+ Box,
18
+ Select,
19
+ } from "@mantine/core";
20
+ import {
21
+ ArrowTopRightOnSquareIcon,
22
+ UserIcon,
23
+ } from "@heroicons/react/24/outline";
24
+ import { useTranslation } from "react-i18next";
25
+ import { type Website, type Permissive } from "omni-osint-crud-client";
26
+ import { EditableAttributes } from "../EditableAttributes";
27
+ import { ChevronDownIcon } from "@heroicons/react/24/outline";
28
+ import {
29
+ getAccessLevel,
30
+ getRoles,
31
+ useReadOptions,
32
+ useWriteOptions,
33
+ } from "../accessLevel";
34
+ import { Controller } from "react-hook-form";
35
+ import { BaseForm } from "../BaseForm";
36
+ import { WebsiteIcon } from "@omnsight/osint-entity-components/icons";
37
+
38
+ interface Props extends PropsWithChildren {
39
+ website: Website;
40
+ isAdmin?: boolean;
41
+ onUpdate?: (data: Permissive) => void;
42
+ onClose?: () => void;
43
+ onDoubleClick: () => void;
44
+ exitButton?: React.ReactNode;
45
+ style?: CSSProperties;
46
+ editModeEnabled: boolean;
47
+ }
48
+
49
+ export const StaticForm: React.FC<Props> = ({
50
+ website,
51
+ isAdmin = false,
52
+ onUpdate,
53
+ onClose,
54
+ onDoubleClick,
55
+ exitButton,
56
+ children,
57
+ style,
58
+ editModeEnabled,
59
+ }) => {
60
+ const { t } = useTranslation();
61
+ const [attributesOpen, setAttributesOpen] = useState(false);
62
+
63
+ const handlClose = () => {
64
+ onClose?.();
65
+ };
66
+
67
+ const readOptions = useReadOptions(isAdmin);
68
+ const writeOptions = useWriteOptions();
69
+
70
+ return (
71
+ <BaseForm<Website>
72
+ style={style}
73
+ icon={<WebsiteIcon website={website} />}
74
+ title={website.title || t("components.forms.WebsiteForm.title")}
75
+ onClose={handlClose}
76
+ defaultValues={website}
77
+ onUpdate={onUpdate}
78
+ exitButton={exitButton}
79
+ onlyShowEditOnDirty={true}
80
+ >
81
+ {({ control, formState: { errors } }) => (
82
+ <Stack
83
+ pos="relative"
84
+ gap="xs"
85
+ style={{ cursor: editModeEnabled ? "pointer" : "default" }}
86
+ onDoubleClick={onDoubleClick}
87
+ >
88
+ <Group gap="xs">
89
+ {website.url && (
90
+ <ActionIcon
91
+ component="a"
92
+ href={website.url}
93
+ target="_blank"
94
+ variant="subtle"
95
+ size="sm"
96
+ >
97
+ <ArrowTopRightOnSquareIcon
98
+ style={{ width: "70%", height: "70%" }}
99
+ />
100
+ </ActionIcon>
101
+ )}
102
+ </Group>
103
+
104
+ <Group gap={4}>
105
+ <Text>{website.url}</Text>
106
+ </Group>
107
+
108
+ <Group gap={4}>
109
+ <Text>{website.description || t("placeholder.description")}</Text>
110
+ </Group>
111
+
112
+ <Group gap={4}>
113
+ <Text size="sm">{website.type}</Text>
114
+ </Group>
115
+
116
+ <Group gap={4}>
117
+ <Text size="sm">
118
+ {website.founded_at
119
+ ? new Date(website.founded_at * 1000).toLocaleDateString()
120
+ : t("placeholder.foundedDate")}
121
+ </Text>
122
+ </Group>
123
+
124
+ <Group gap={4}>
125
+ <Text size="sm">
126
+ {website.discovered_at
127
+ ? new Date(website.discovered_at * 1000).toLocaleDateString()
128
+ : t("placeholder.discoveredDate")}
129
+ </Text>
130
+ </Group>
131
+
132
+ <Text size="sm">{(website.tags || []).join(", ")}</Text>
133
+
134
+ {children}
135
+
136
+ {onUpdate && (
137
+ <Group gap="xs" w="100%">
138
+ <Box
139
+ style={{
140
+ flex: 1,
141
+ display: "flex",
142
+ alignItems: "center",
143
+ justifyContent: "center",
144
+ }}
145
+ >
146
+ <Tooltip label={website.owner?.toUpperCase()[0] || ""}>
147
+ <UserIcon style={{ width: rem(18), height: rem(18) }} />
148
+ </Tooltip>
149
+ </Box>
150
+ <Box
151
+ style={{
152
+ flex: 3,
153
+ display: "flex",
154
+ }}
155
+ >
156
+ {t("placeholder.accessLabel")}:
157
+ </Box>
158
+ <Controller
159
+ name="read"
160
+ control={control}
161
+ rules={{ required: t("common.required") }}
162
+ render={({ field }) => {
163
+ return (
164
+ <Box style={{ flex: 4 }}>
165
+ <Select
166
+ value={getAccessLevel(field.value ?? [])}
167
+ onChange={(value) => field.onChange(getRoles(value))}
168
+ placeholder={t("placeholder.readAccess")}
169
+ data={readOptions}
170
+ clearable
171
+ error={errors.read?.message}
172
+ />
173
+ </Box>
174
+ );
175
+ }}
176
+ />
177
+ <Controller
178
+ name="write"
179
+ control={control}
180
+ rules={{ required: t("common.required") }}
181
+ render={({ field }) => {
182
+ return (
183
+ <Box style={{ flex: 4 }}>
184
+ <Select
185
+ value={getAccessLevel(field.value ?? [])}
186
+ onChange={(value) => field.onChange(getRoles(value))}
187
+ placeholder={t("placeholder.writeAccess")}
188
+ data={writeOptions}
189
+ clearable
190
+ error={errors.write?.message}
191
+ />
192
+ </Box>
193
+ );
194
+ }}
195
+ />
196
+ </Group>
197
+ )}
198
+
199
+ <Divider my="sm" />
200
+
201
+ <UnstyledButton onClick={() => setAttributesOpen((o) => !o)}>
202
+ <Group justify="space-between">
203
+ <Title order={5}>{t("placeholder.attributes")}</Title>
204
+ <ChevronDownIcon
205
+ style={{
206
+ width: 16,
207
+ transform: attributesOpen ? "rotate(180deg)" : "rotate(0deg)",
208
+ transition: "transform 200ms ease",
209
+ }}
210
+ />
211
+ </Group>
212
+ </UnstyledButton>
213
+
214
+ <Collapse in={attributesOpen}>
215
+ <EditableAttributes
216
+ value={website.attributes || {}}
217
+ isEditing={false}
218
+ onChange={() => {}}
219
+ />
220
+ </Collapse>
221
+ </Stack>
222
+ )}
223
+ </BaseForm>
224
+ );
225
+ };
@@ -0,0 +1 @@
1
+ export { WebsiteForm } from './EditableForm';
@@ -0,0 +1,48 @@
1
+ import { useTranslation } from "react-i18next";
2
+
3
+
4
+ export const useReadOptions = (isAdmin: boolean) => {
5
+ const { t } = useTranslation();
6
+ return [
7
+ { value: "private", label: t("access.private") },
8
+ ...(isAdmin
9
+ ? [
10
+ { value: "paid", label: t("access.paid") },
11
+ { value: "platform", label: t("access.platform") },
12
+ ]
13
+ : []),
14
+ { value: "public", label: t("access.public") },
15
+ ];
16
+ };
17
+
18
+ export const useWriteOptions = () => {
19
+ const { t } = useTranslation();
20
+ return [
21
+ { value: "private", label: t("access.private") },
22
+ { value: "paid", label: t("access.public") },
23
+ ];
24
+ };
25
+
26
+ export const getAccessLevel = (roles: string[]) => {
27
+ if (roles.includes("guest")) {
28
+ return "public";
29
+ } else if (roles.includes("user")) {
30
+ return "platform";
31
+ } else if (roles.includes("pro")) {
32
+ return "paid";
33
+ } else {
34
+ return "private";
35
+ }
36
+ }
37
+
38
+ export const getRoles = (accessLevel: string | null) => {
39
+ if (accessLevel === "public") {
40
+ return ["guest", "user", "pro", "admin"];
41
+ } else if (accessLevel === "platform") {
42
+ return ["user", "pro", "admin"];
43
+ } else if (accessLevel === "paid") {
44
+ return ["pro", "admin"];
45
+ } else {
46
+ return ["admin"];
47
+ }
48
+ }
@@ -0,0 +1,8 @@
1
+ export * from './EventForm';
2
+ export * from './OrganizationForm';
3
+ export * from './PersonForm';
4
+ export * from './RelationForm';
5
+ export * from './SourceForm';
6
+ export * from './WebsiteForm';
7
+ export * from './MonitoringSourceForm';
8
+ export * from './InsightForm';
@@ -27,8 +27,8 @@ export const EventIconSelector: React.FC<EventIconSelectorProps> = ({
27
27
  return (
28
28
  <Select
29
29
  leftSection={<EventIcon event={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 EventIconSelector: React.FC<EventIconSelectorProps> = ({
37
37
  };
38
38
 
39
39
  interface EventColorSelectorProps {
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 EventColorSelector: React.FC<EventColorSelectorProps> = ({
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 EventColorSelector: React.FC<EventColorSelectorProps> = ({
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 EventIconSelect: React.FC<EventIconSelectProps> = ({
96
97
  onChange={handleTypeChange}
97
98
  />
98
99
  <EventColorSelector
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>
@@ -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>