@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,209 @@
1
+ import {
2
+ Group,
3
+ Text,
4
+ Stack,
5
+ Divider,
6
+ UnstyledButton,
7
+ Collapse,
8
+ Title,
9
+ rem,
10
+ Tooltip,
11
+ Box,
12
+ Select,
13
+ } from "@mantine/core";
14
+ import { useTranslation } from "react-i18next";
15
+ import { type Organization, type Permissive } from "omni-osint-crud-client";
16
+ import { EditableAttributes } from "../EditableAttributes";
17
+ import { type CSSProperties, useState, type PropsWithChildren } from "react";
18
+ import { ChevronDownIcon, UserIcon } from "@heroicons/react/24/outline";
19
+ import {
20
+ getAccessLevel,
21
+ getRoles,
22
+ useReadOptions,
23
+ useWriteOptions,
24
+ } from "../accessLevel";
25
+ import { Controller } from "react-hook-form";
26
+ import { BaseForm } from "../BaseForm";
27
+ import { OrganizationIcon } from "@omnsight/osint-entity-components/icons";
28
+
29
+ interface Props extends PropsWithChildren {
30
+ organization: Organization;
31
+ isAdmin?: boolean;
32
+ onUpdate?: (data: Permissive) => void;
33
+ onClose?: () => void;
34
+ onDoubleClick: () => void;
35
+ exitButton?: React.ReactNode;
36
+ style?: CSSProperties;
37
+ editModeEnabled: boolean;
38
+ }
39
+
40
+ export const StaticForm: React.FC<Props> = ({
41
+ organization,
42
+ isAdmin = false,
43
+ onUpdate,
44
+ onClose,
45
+ onDoubleClick,
46
+ exitButton,
47
+ children,
48
+ style,
49
+ editModeEnabled,
50
+ }) => {
51
+ const { t } = useTranslation();
52
+ const [attributesOpen, setAttributesOpen] = useState(false);
53
+
54
+ const handlClose = () => {
55
+ onClose?.();
56
+ };
57
+
58
+ const readOptions = useReadOptions(isAdmin);
59
+ const writeOptions = useWriteOptions();
60
+
61
+ return (
62
+ <BaseForm<Organization>
63
+ style={style}
64
+ icon={<OrganizationIcon organization={organization} />}
65
+ title={organization.name || t("components.forms.OrganizationForm.title")}
66
+ onClose={handlClose}
67
+ defaultValues={organization}
68
+ onUpdate={onUpdate}
69
+ exitButton={exitButton}
70
+ onlyShowEditOnDirty={true}
71
+ >
72
+ {({ control, formState: { errors } }) => {
73
+ return (
74
+ <Stack
75
+ pos="relative"
76
+ gap="xs"
77
+ style={{ cursor: editModeEnabled ? "pointer" : "default" }}
78
+ onDoubleClick={onDoubleClick}
79
+ >
80
+ <Group gap={4}>
81
+ <Text size="sm" c="dimmed">
82
+ {t("placeholder.type")}:
83
+ </Text>
84
+ <Text size="sm">{organization.type}</Text>
85
+ </Group>
86
+
87
+ <Group gap={4}>
88
+ <Text size="sm" c="dimmed">
89
+ {t("placeholder.foundedDate")}:
90
+ </Text>
91
+ <Text size="sm">
92
+ {organization.founded_at
93
+ ? new Date(
94
+ organization.founded_at * 1000,
95
+ ).toLocaleDateString()
96
+ : t("placeholder.foundedDate")}
97
+ </Text>
98
+ </Group>
99
+
100
+ <Group gap={4}>
101
+ <Text size="sm" c="dimmed">
102
+ {t("placeholder.discoveredDate")}:
103
+ </Text>
104
+ <Text size="sm">
105
+ {organization.discovered_at
106
+ ? new Date(
107
+ organization.discovered_at * 1000,
108
+ ).toLocaleDateString()
109
+ : t("placeholder.discoveredDate")}
110
+ </Text>
111
+ </Group>
112
+
113
+ <Text size="sm">{(organization.tags || []).join(", ")}</Text>
114
+
115
+ {children}
116
+
117
+ {onUpdate && (
118
+ <Group gap="xs" w="100%">
119
+ <Box
120
+ style={{
121
+ flex: 1,
122
+ display: "flex",
123
+ alignItems: "center",
124
+ justifyContent: "center",
125
+ }}
126
+ >
127
+ <Tooltip label={organization.owner?.toUpperCase()[0] || ""}>
128
+ <UserIcon style={{ width: rem(18), height: rem(18) }} />
129
+ </Tooltip>
130
+ </Box>
131
+ <Box
132
+ style={{
133
+ flex: 3,
134
+ display: "flex",
135
+ }}
136
+ >
137
+ {t("placeholder.accessLabel")}:
138
+ </Box>
139
+ <Controller
140
+ name="read"
141
+ control={control}
142
+ rules={{ required: t("common.required") }}
143
+ render={({ field }) => {
144
+ return (
145
+ <Box style={{ flex: 4 }}>
146
+ <Select
147
+ value={getAccessLevel(field.value ?? [])}
148
+ onChange={(value) => field.onChange(getRoles(value))}
149
+ placeholder={t("placeholder.readAccess")}
150
+ data={readOptions}
151
+ clearable
152
+ error={errors.read?.message}
153
+ />
154
+ </Box>
155
+ );
156
+ }}
157
+ />
158
+ <Controller
159
+ name="write"
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.writeAccess")}
169
+ data={writeOptions}
170
+ clearable
171
+ error={errors.write?.message}
172
+ />
173
+ </Box>
174
+ );
175
+ }}
176
+ />
177
+ </Group>
178
+ )}
179
+
180
+ <Divider my="sm" />
181
+
182
+ <UnstyledButton onClick={() => setAttributesOpen((o) => !o)}>
183
+ <Group justify="space-between">
184
+ <Title order={5}>{t("placeholder.attributes")}</Title>
185
+ <ChevronDownIcon
186
+ style={{
187
+ width: 16,
188
+ transform: attributesOpen
189
+ ? "rotate(180deg)"
190
+ : "rotate(0deg)",
191
+ transition: "transform 200ms ease",
192
+ }}
193
+ />
194
+ </Group>
195
+ </UnstyledButton>
196
+
197
+ <Collapse in={attributesOpen}>
198
+ <EditableAttributes
199
+ value={organization.attributes || {}}
200
+ isEditing={false}
201
+ onChange={() => {}}
202
+ />
203
+ </Collapse>
204
+ </Stack>
205
+ );
206
+ }}
207
+ </BaseForm>
208
+ );
209
+ };
@@ -0,0 +1 @@
1
+ export { OrganizationForm } from './EditableForm';
@@ -0,0 +1,74 @@
1
+ import { useState, type PropsWithChildren, type CSSProperties } from "react";
2
+ import type { Person, Permissive } from "omni-osint-crud-client";
3
+ import { EditingForm } from "./EditingForm";
4
+ import { StaticForm } from "./StaticForm";
5
+
6
+ interface Props extends PropsWithChildren {
7
+ person: Person;
8
+ isAdmin?: boolean;
9
+ onSubmit?: (data: Person) => void;
10
+ onUpdate?: (data: Partial<Person>) => void;
11
+ onUpdatePermissive?: (data: Permissive) => void;
12
+ onClose?: () => void;
13
+ exitButton?: React.ReactNode;
14
+ style?: CSSProperties;
15
+ }
16
+
17
+ export const PersonForm: React.FC<Props> = ({
18
+ person,
19
+ isAdmin = false,
20
+ onSubmit,
21
+ onUpdate,
22
+ onUpdatePermissive,
23
+ onClose,
24
+ exitButton,
25
+ children,
26
+ style,
27
+ }) => {
28
+ const [isEditing, setIsEditing] = useState(onSubmit !== undefined || false);
29
+
30
+ if (
31
+ onSubmit !== undefined &&
32
+ (onUpdate !== undefined || onUpdatePermissive !== undefined)
33
+ ) {
34
+ throw new Error(
35
+ "onSubmit cannot be defined at the same time with onUpdate or onUpdatePermissive",
36
+ );
37
+ }
38
+
39
+ const handlClose = () => {
40
+ if (onUpdate !== undefined) {
41
+ setIsEditing(false);
42
+ }
43
+ onClose?.();
44
+ };
45
+
46
+ const handleDoubleClick = () => {
47
+ if (onUpdate !== undefined) {
48
+ setIsEditing(true);
49
+ }
50
+ };
51
+
52
+ return isEditing ? (
53
+ <EditingForm
54
+ person={person}
55
+ onSubmit={onSubmit}
56
+ onUpdate={onUpdate}
57
+ onClose={handlClose}
58
+ style={style}
59
+ />
60
+ ) : (
61
+ <StaticForm
62
+ person={person}
63
+ isAdmin={isAdmin}
64
+ onUpdate={onUpdatePermissive}
65
+ onClose={handlClose}
66
+ onDoubleClick={handleDoubleClick}
67
+ exitButton={exitButton || <></>}
68
+ style={style}
69
+ editModeEnabled={onUpdate !== undefined}
70
+ >
71
+ {children}
72
+ </StaticForm>
73
+ );
74
+ };
@@ -0,0 +1,187 @@
1
+ import {
2
+ Stack,
3
+ Text,
4
+ Group,
5
+ Divider,
6
+ UnstyledButton,
7
+ Collapse,
8
+ Title,
9
+ TextInput,
10
+ TagsInput,
11
+ } from "@mantine/core";
12
+ import { CustomDatePicker } from "../../inputs/CustomDatePicker";
13
+ import { useTranslation } from "react-i18next";
14
+ import { type Person } from "omni-osint-crud-client";
15
+ import { EditableAttributes } from "../EditableAttributes";
16
+ import { type CSSProperties, useState } from "react";
17
+ import { ChevronDownIcon } from "@heroicons/react/24/outline";
18
+ import { Controller } from "react-hook-form";
19
+ import { BaseForm } from "../BaseForm";
20
+ import { PersonIcon } from "@omnsight/osint-entity-components/icons";
21
+ import { PersonIconFormSection } from "./IconFormSection";
22
+
23
+ interface Props {
24
+ person: Person;
25
+ onSubmit?: (data: Person) => void;
26
+ onUpdate?: (data: Partial<Person>) => void;
27
+ onClose?: () => void;
28
+ children?: React.ReactNode;
29
+ style?: CSSProperties;
30
+ }
31
+
32
+ export const EditingForm: React.FC<Props> = ({
33
+ person,
34
+ onSubmit,
35
+ onUpdate,
36
+ onClose,
37
+ children,
38
+ style,
39
+ }) => {
40
+ const { t } = useTranslation();
41
+ const [attributesOpen, setAttributesOpen] = useState(false);
42
+
43
+ const handlClose = () => {
44
+ onClose?.();
45
+ };
46
+
47
+ return (
48
+ <BaseForm<Person>
49
+ style={style}
50
+ icon={<PersonIcon person={person} />}
51
+ title={t("components.forms.PersonForm.title")}
52
+ onClose={handlClose}
53
+ defaultValues={person}
54
+ onSubmit={onSubmit}
55
+ onUpdate={onUpdate}
56
+ onlyShowEditOnDirty={false}
57
+ >
58
+ {({ control, formState: { errors } }) => {
59
+ return (
60
+ <Stack pos="relative" gap="xs" style={{ cursor: "default" }}>
61
+ <Text size="sm" fw={500}>
62
+ {t("components.forms.PersonForm.name")}
63
+ </Text>
64
+ <Controller
65
+ name="name"
66
+ control={control}
67
+ rules={{ required: t("common.required") }}
68
+ render={({ field }) => (
69
+ <TextInput
70
+ {...field}
71
+ value={field.value || ""}
72
+ placeholder={t("components.forms.PersonForm.name")}
73
+ error={errors.name?.message}
74
+ />
75
+ )}
76
+ />
77
+
78
+ <Group gap={4}>
79
+ <Text size="sm" c="dimmed">
80
+ {t("placeholder.type")}:
81
+ </Text>
82
+ <PersonIconFormSection data={person} />
83
+ </Group>
84
+
85
+ <Group gap={4}>
86
+ <Text>{t("placeholder.role")}:</Text>
87
+ <Controller
88
+ name="role"
89
+ control={control}
90
+ render={({ field }) => (
91
+ <TextInput
92
+ {...field}
93
+ value={field.value || ""}
94
+ placeholder={t("placeholder.role")}
95
+ error={errors.role?.message}
96
+ />
97
+ )}
98
+ />
99
+ </Group>
100
+
101
+ <Group gap={4}>
102
+ <Text>{t("placeholder.nationality")}:</Text>
103
+ <Controller
104
+ name="nationality"
105
+ control={control}
106
+ render={({ field }) => (
107
+ <TextInput
108
+ {...field}
109
+ value={field.value || ""}
110
+ placeholder={t("placeholder.nationality")}
111
+ />
112
+ )}
113
+ />
114
+ </Group>
115
+
116
+ <Group gap={4}>
117
+ <Text size="sm" c="dimmed">
118
+ {t("placeholder.birthDate")}:
119
+ </Text>
120
+ <Controller
121
+ name="birth_date"
122
+ control={control}
123
+ render={({ field }) => (
124
+ <CustomDatePicker
125
+ value={field.value ? new Date(field.value * 1000) : null}
126
+ onChange={(date) =>
127
+ field.onChange(date ? date.getTime() / 1000 : 0)
128
+ }
129
+ placeholder={t("placeholder.birthDate")}
130
+ />
131
+ )}
132
+ />
133
+ </Group>
134
+
135
+ <Text size="sm" fw={500}>
136
+ {t("placeholder.tags")}
137
+ </Text>
138
+ <Controller
139
+ name="tags"
140
+ control={control}
141
+ render={({ field }) => (
142
+ <TagsInput
143
+ {...field}
144
+ value={field.value || []}
145
+ placeholder={t("placeholder.tags")}
146
+ />
147
+ )}
148
+ />
149
+
150
+ {children}
151
+
152
+ <Divider my="sm" />
153
+
154
+ <UnstyledButton onClick={() => setAttributesOpen((o) => !o)}>
155
+ <Group justify="space-between">
156
+ <Title order={5}>{t("placeholder.attributes")}</Title>
157
+ <ChevronDownIcon
158
+ style={{
159
+ width: 16,
160
+ transform: attributesOpen
161
+ ? "rotate(180deg)"
162
+ : "rotate(0deg)",
163
+ transition: "transform 200ms ease",
164
+ }}
165
+ />
166
+ </Group>
167
+ </UnstyledButton>
168
+
169
+ <Collapse in={attributesOpen}>
170
+ <Controller
171
+ name="attributes"
172
+ control={control}
173
+ render={({ field }) => (
174
+ <EditableAttributes
175
+ {...field}
176
+ value={field.value || {}}
177
+ isEditing={true}
178
+ />
179
+ )}
180
+ />
181
+ </Collapse>
182
+ </Stack>
183
+ );
184
+ }}
185
+ </BaseForm>
186
+ );
187
+ };
@@ -0,0 +1,54 @@
1
+ import { Group } from "@mantine/core";
2
+ import { useTranslation } from "react-i18next";
3
+ import { type Person } from "omni-osint-crud-client";
4
+ import { Controller, useFormContext, useWatch } from "react-hook-form";
5
+ import {
6
+ PersonIconSelector,
7
+ PersonColorSelector,
8
+ } from "@omnsight/osint-entity-components/icons";
9
+
10
+ export const PersonIconFormSection = ({ data }: { data: Person }) => {
11
+ const {
12
+ control,
13
+ formState: { errors },
14
+ } = useFormContext<Person>();
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
+ <PersonIconSelector
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
+ <PersonColorSelector
46
+ {...field}
47
+ value={field.value as string | undefined}
48
+ error={errors.attributes?.icon_color?.message}
49
+ />
50
+ )}
51
+ />
52
+ </Group>
53
+ );
54
+ };