@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,192 @@
1
+ import React, { useState } from 'react';
2
+ import {
3
+ Stack,
4
+ Group,
5
+ Text,
6
+ Divider,
7
+ Title,
8
+ Collapse,
9
+ UnstyledButton,
10
+ TextInput,
11
+ Textarea,
12
+ Select,
13
+ Slider,
14
+ } from '@mantine/core';
15
+ import { CustomDatePicker } from '../../inputs/CustomDatePicker';
16
+ import { type MonitoringSource } from 'omni-monitoring-client';
17
+ import { useTranslation } from 'react-i18next';
18
+ import { EditableAttributes } from '../EditableAttributes';
19
+ import { ChevronDownIcon } from '@heroicons/react/24/solid';
20
+ import { Controller } from 'react-hook-form';
21
+ import { BaseForm } from '../BaseForm';
22
+
23
+ interface Props {
24
+ source: MonitoringSource;
25
+ onSubmit?: (data: MonitoringSource) => void;
26
+ onUpdate?: (data: Partial<MonitoringSource>) => void;
27
+ onClose?: () => void;
28
+ }
29
+
30
+ export const EditingForm: React.FC<Props> = ({
31
+ source,
32
+ onSubmit,
33
+ onUpdate,
34
+ onClose,
35
+ }) => {
36
+ const { t } = useTranslation();
37
+ const [attributesOpen, setAttributesOpen] = useState(false);
38
+
39
+ const handlClose = () => {
40
+ onClose?.();
41
+ };
42
+
43
+ return (
44
+ <BaseForm<MonitoringSource>
45
+ title={t('components.forms.MonitoringSourceForm.title')}
46
+ onClose={handlClose}
47
+ defaultValues={source}
48
+ onSubmit={onSubmit}
49
+ onUpdate={onUpdate}
50
+ onlyShowEditOnDirty={false}
51
+ >
52
+ {({ control, formState: { errors } }) => (
53
+ <Stack
54
+ pos="relative"
55
+ gap="xs"
56
+ style={{ cursor: 'default' }}
57
+ >
58
+ <Text size="sm" fw={500}>
59
+ {t('components.forms.MonitoringSourceForm.name')}
60
+ </Text>
61
+ <Controller
62
+ name="name"
63
+ control={control}
64
+ rules={{ required: t('common.required') }}
65
+ render={({ field }) => (
66
+ <TextInput
67
+ {...field}
68
+ value={field.value || ''}
69
+ placeholder={t('components.forms.MonitoringSourceForm.name')}
70
+ error={errors.name?.message}
71
+ />
72
+ )}
73
+ />
74
+
75
+ <Text size="sm" fw={500}>
76
+ {t('placeholder.description')}
77
+ </Text>
78
+ <Controller
79
+ name="description"
80
+ control={control}
81
+ rules={{ required: t('common.required') }}
82
+ render={({ field }) => (
83
+ <Textarea
84
+ {...field}
85
+ value={field.value || ''}
86
+ placeholder={t('placeholder.description')}
87
+ error={errors.description?.message}
88
+ />
89
+ )}
90
+ />
91
+
92
+ <Group>
93
+ <Text>{t('placeholder.source.type')}:</Text>
94
+ <Controller
95
+ name="type"
96
+ control={control}
97
+ rules={{ required: t('common.required') }}
98
+ render={({ field }) => (
99
+ <Select
100
+ {...field}
101
+ value={field.value || ''}
102
+ data={['website', 'twitter', 'telegram'].map((type) => ({
103
+ value: type,
104
+ label: type,
105
+ }))}
106
+ placeholder={t('placeholder.source.type')}
107
+ error={errors.type?.message}
108
+ />
109
+ )}
110
+ />
111
+ </Group>
112
+ <Group>
113
+ <Text>{t('placeholder.url')}:</Text>
114
+ <Controller
115
+ name="url"
116
+ control={control}
117
+ rules={{ required: t('common.required') }}
118
+ render={({ field }) => (
119
+ <TextInput
120
+ {...field}
121
+ value={field.value || ''}
122
+ placeholder={t('placeholder.url')}
123
+ error={errors.url?.message}
124
+ />
125
+ )}
126
+ />
127
+ </Group>
128
+
129
+ <Group>
130
+ <Text>{t('placeholder.reliability')}:</Text>
131
+ <Controller
132
+ name="reliability"
133
+ control={control}
134
+ rules={{ required: t('common.required') }}
135
+ render={({ field }) => (
136
+ <Slider
137
+ {...field}
138
+ value={field.value || 0}
139
+ min={0}
140
+ max={100}
141
+ style={{ flex: 1 }}
142
+ label={(value) => `${value}%`}
143
+ />
144
+ )}
145
+ />
146
+ </Group>
147
+ <Group>
148
+ <Text>{t('placeholder.lastReviewed')}:</Text>
149
+ <Controller
150
+ name="last_reviewed"
151
+ control={control}
152
+ rules={{ required: t('common.required') }}
153
+ render={({ field }) => (
154
+ <CustomDatePicker
155
+ value={field.value ? new Date(field.value * 1000) : null}
156
+ onChange={(date) => field.onChange(date ? date.getTime() / 1000 : 0)}
157
+ placeholder={t('placeholder.lastReviewed')}
158
+ error={errors.last_reviewed?.message}
159
+ />
160
+ )}
161
+ />
162
+ </Group>
163
+
164
+ <Divider my="sm" />
165
+
166
+ <UnstyledButton onClick={() => setAttributesOpen((o) => !o)}>
167
+ <Group justify="space-between">
168
+ <Title order={5}>{t('placeholder.attributes')}</Title>
169
+ <ChevronDownIcon
170
+ style={{
171
+ width: 16,
172
+ transform: attributesOpen ? 'rotate(180deg)' : 'rotate(0deg)',
173
+ transition: 'transform 200ms ease',
174
+ }}
175
+ />
176
+ </Group>
177
+ </UnstyledButton>
178
+
179
+ <Collapse in={attributesOpen}>
180
+ <Controller
181
+ name="attributes"
182
+ control={control}
183
+ render={({ field }) => (
184
+ <EditableAttributes {...field} value={field.value || {}} isEditing={true} />
185
+ )}
186
+ />
187
+ </Collapse>
188
+ </Stack>
189
+ )}
190
+ </BaseForm>
191
+ );
192
+ };
@@ -0,0 +1,107 @@
1
+ import React, { useState, type PropsWithChildren } from 'react';
2
+ import {
3
+ Stack,
4
+ Group,
5
+ Text,
6
+ Divider,
7
+ Title,
8
+ Collapse,
9
+ UnstyledButton,
10
+ } from '@mantine/core';
11
+ import { type MonitoringSource } from 'omni-monitoring-client';
12
+ import { useTranslation } from 'react-i18next';
13
+ import { EditableAttributes } from '../EditableAttributes';
14
+ import { ChevronDownIcon } from '@heroicons/react/24/solid';
15
+ import { BaseForm } from '../BaseForm';
16
+
17
+ interface Props extends PropsWithChildren {
18
+ source: MonitoringSource;
19
+ onClose?: () => void;
20
+ onDoubleClick: () => void;
21
+ editModeEnabled: boolean;
22
+ exitButton?: React.ReactNode;
23
+ }
24
+
25
+ export const StaticForm: React.FC<Props> = ({
26
+ source,
27
+ onClose,
28
+ onDoubleClick,
29
+ editModeEnabled,
30
+ exitButton,
31
+ children,
32
+ }) => {
33
+ const { t } = useTranslation();
34
+ const [attributesOpen, setAttributesOpen] = useState(false);
35
+
36
+ const handlClose = () => {
37
+ onClose?.();
38
+ };
39
+
40
+ return (
41
+ <BaseForm<MonitoringSource>
42
+ title={source.name || t('components.forms.MonitoringSourceForm.title')}
43
+ onClose={handlClose}
44
+ defaultValues={source}
45
+ exitButton={exitButton}
46
+ onlyShowEditOnDirty={true}
47
+ >
48
+ {() => (
49
+ <Stack
50
+ pos="relative"
51
+ gap="xs"
52
+ style={{ cursor: editModeEnabled ? 'pointer' : 'default' }}
53
+ onDoubleClick={onDoubleClick}
54
+ >
55
+ <Text>{source.description || t('placeholder.description')}</Text>
56
+
57
+ <Group>
58
+ <Text>{t('placeholder.source.type')}:</Text>
59
+ <Text>{source.type}</Text>
60
+ </Group>
61
+ <Group>
62
+ <Text>{t('placeholder.url')}:</Text>
63
+ <Text>{source.url}</Text>
64
+ </Group>
65
+
66
+ <Group>
67
+ <Text>{t('placeholder.reliability')}:</Text>
68
+ <Text>{source.reliability}</Text>
69
+ </Group>
70
+ <Group>
71
+ <Text>{t('placeholder.lastReviewed')}:</Text>
72
+ <Text>
73
+ {source.last_reviewed
74
+ ? new Date(source.last_reviewed * 1000).toLocaleDateString()
75
+ : ''}
76
+ </Text>
77
+ </Group>
78
+
79
+ {children}
80
+
81
+ <Divider my="sm" />
82
+
83
+ <UnstyledButton onClick={() => setAttributesOpen((o) => !o)}>
84
+ <Group justify="space-between">
85
+ <Title order={5}>{t('placeholder.attributes')}</Title>
86
+ <ChevronDownIcon
87
+ style={{
88
+ width: 16,
89
+ transform: attributesOpen ? 'rotate(180deg)' : 'rotate(0deg)',
90
+ transition: 'transform 200ms ease',
91
+ }}
92
+ />
93
+ </Group>
94
+ </UnstyledButton>
95
+
96
+ <Collapse in={attributesOpen}>
97
+ <EditableAttributes
98
+ value={source.attributes || {}}
99
+ isEditing={false}
100
+ onChange={() => {}}
101
+ />
102
+ </Collapse>
103
+ </Stack>
104
+ )}
105
+ </BaseForm>
106
+ );
107
+ };
@@ -0,0 +1 @@
1
+ export { MonitoringSourceForm } from './EditableForm';
@@ -0,0 +1,74 @@
1
+ import { useState, type PropsWithChildren, type CSSProperties } from "react";
2
+ import type { Organization, Permissive } from "omni-osint-crud-client";
3
+ import { EditingForm } from "./EditingForm";
4
+ import { StaticForm } from "./StaticForm";
5
+
6
+ interface Props extends PropsWithChildren {
7
+ organization: Organization;
8
+ isAdmin?: boolean;
9
+ onSubmit?: (data: Organization) => void;
10
+ onUpdate?: (data: Partial<Organization>) => void;
11
+ onUpdatePermissive?: (data: Permissive) => void;
12
+ onClose?: () => void;
13
+ exitButton?: React.ReactNode;
14
+ style?: CSSProperties;
15
+ }
16
+
17
+ export const OrganizationForm: React.FC<Props> = ({
18
+ organization,
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);
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
+ organization={organization}
55
+ onSubmit={onSubmit}
56
+ onUpdate={onUpdate}
57
+ onClose={handlClose}
58
+ style={style}
59
+ />
60
+ ) : (
61
+ <StaticForm
62
+ organization={organization}
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,177 @@
1
+ import {
2
+ Group,
3
+ Text,
4
+ Stack,
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 Organization } 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 { OrganizationIcon } from "@omnsight/osint-entity-components/icons";
21
+ import { OrganizationIconFormSection } from "./IconFormSection";
22
+
23
+ interface Props {
24
+ organization: Organization;
25
+ onSubmit?: (data: Organization) => void;
26
+ onUpdate?: (data: Partial<Organization>) => void;
27
+ onClose?: () => void;
28
+ children?: React.ReactNode;
29
+ style?: CSSProperties;
30
+ }
31
+
32
+ export const EditingForm: React.FC<Props> = ({
33
+ organization,
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<Organization>
49
+ style={style}
50
+ icon={<OrganizationIcon organization={organization} />}
51
+ title={t("components.forms.OrganizationForm.title")}
52
+ onClose={handlClose}
53
+ defaultValues={organization}
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
+ <Group gap={4}>
62
+ <Text size="sm" fw={500}>
63
+ {t("placeholder.name")}
64
+ </Text>
65
+ <Controller
66
+ name="name"
67
+ control={control}
68
+ rules={{ required: t("common.required") }}
69
+ render={({ field }) => (
70
+ <TextInput
71
+ {...field}
72
+ value={field.value || ""}
73
+ placeholder={t("components.forms.OrganizationForm.name")}
74
+ error={errors.name?.message}
75
+ />
76
+ )}
77
+ />
78
+ </Group>
79
+
80
+ <Group gap={4}>
81
+ <Text size="sm" c="dimmed">
82
+ {t("placeholder.type")}:
83
+ </Text>
84
+ <OrganizationIconFormSection data={organization} />
85
+ </Group>
86
+
87
+ <Group gap={4}>
88
+ <Text size="sm" c="dimmed">
89
+ {t("placeholder.foundedDate")}:
90
+ </Text>
91
+ <Controller
92
+ name="founded_at"
93
+ control={control}
94
+ render={({ field }) => (
95
+ <CustomDatePicker
96
+ value={field.value ? new Date(field.value * 1000) : null}
97
+ onChange={(date) =>
98
+ field.onChange(date ? date.getTime() / 1000 : 0)
99
+ }
100
+ placeholder={t("placeholder.foundedDate")}
101
+ />
102
+ )}
103
+ />
104
+ </Group>
105
+
106
+ <Group gap={4}>
107
+ <Text size="sm" c="dimmed">
108
+ {t("placeholder.discoveredDate")}:
109
+ </Text>
110
+ <Controller
111
+ name="discovered_at"
112
+ control={control}
113
+ render={({ field }) => (
114
+ <CustomDatePicker
115
+ value={field.value ? new Date(field.value * 1000) : null}
116
+ onChange={(date) =>
117
+ field.onChange(date ? date.getTime() / 1000 : 0)
118
+ }
119
+ placeholder={t("placeholder.discoveredDate")}
120
+ />
121
+ )}
122
+ />
123
+ </Group>
124
+
125
+ <Text size="sm" fw={500}>
126
+ {t("placeholder.tags")}
127
+ </Text>
128
+ <Controller
129
+ name="tags"
130
+ control={control}
131
+ render={({ field }) => (
132
+ <TagsInput
133
+ {...field}
134
+ value={field.value || []}
135
+ placeholder={t("placeholder.tags")}
136
+ />
137
+ )}
138
+ />
139
+
140
+ {children}
141
+
142
+ <Divider my="sm" />
143
+
144
+ <UnstyledButton onClick={() => setAttributesOpen((o) => !o)}>
145
+ <Group justify="space-between">
146
+ <Title order={5}>{t("placeholder.attributes")}</Title>
147
+ <ChevronDownIcon
148
+ style={{
149
+ width: 16,
150
+ transform: attributesOpen
151
+ ? "rotate(180deg)"
152
+ : "rotate(0deg)",
153
+ transition: "transform 200ms ease",
154
+ }}
155
+ />
156
+ </Group>
157
+ </UnstyledButton>
158
+
159
+ <Collapse in={attributesOpen}>
160
+ <Controller
161
+ name="attributes"
162
+ control={control}
163
+ render={({ field }) => (
164
+ <EditableAttributes
165
+ {...field}
166
+ value={field.value || {}}
167
+ isEditing={true}
168
+ />
169
+ )}
170
+ />
171
+ </Collapse>
172
+ </Stack>
173
+ );
174
+ }}
175
+ </BaseForm>
176
+ );
177
+ };
@@ -0,0 +1,60 @@
1
+ import { Group, Stack } from "@mantine/core";
2
+ import { useTranslation } from "react-i18next";
3
+ import { type Organization } from "omni-osint-crud-client";
4
+ import { Controller, useFormContext, useWatch } from "react-hook-form";
5
+ import {
6
+ OrganizationIconSelector,
7
+ OrganizationColorSelector,
8
+ } from "@omnsight/osint-entity-components/icons";
9
+
10
+ export const OrganizationIconFormSection = ({
11
+ data,
12
+ }: {
13
+ data: Organization;
14
+ }) => {
15
+ const {
16
+ control,
17
+ formState: { errors },
18
+ } = useFormContext<Organization>();
19
+ const { t } = useTranslation();
20
+ const type = useWatch({ control, name: "type" });
21
+ const iconColor = useWatch({ control, name: "attributes.icon_color" });
22
+
23
+ const modifiedData = {
24
+ ...data,
25
+ type: type,
26
+ attributes: { ...data.attributes, icon_color: iconColor },
27
+ };
28
+
29
+ return (
30
+ <Group grow>
31
+ <Controller
32
+ name="type"
33
+ control={control}
34
+ rules={{ required: t("common.required") }}
35
+ render={({ field }) => (
36
+ <Stack gap={0}>
37
+ <OrganizationIconSelector
38
+ {...field}
39
+ data={modifiedData}
40
+ value={field.value}
41
+ error={errors.type?.message}
42
+ />
43
+ </Stack>
44
+ )}
45
+ />
46
+ <Controller
47
+ name="attributes.icon_color"
48
+ control={control}
49
+ rules={{ required: t("common.required") }}
50
+ render={({ field }) => (
51
+ <OrganizationColorSelector
52
+ {...field}
53
+ value={field.value as string | undefined}
54
+ error={errors.attributes?.icon_color?.message}
55
+ />
56
+ )}
57
+ />
58
+ </Group>
59
+ );
60
+ };