@hed-hog/core 0.0.368 → 0.0.371
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.
- package/LICENSE.md +21 -0
- package/README.md +2 -0
- package/dist/ai/ai.service.d.ts.map +1 -1
- package/dist/ai/ai.service.js +10 -7
- package/dist/ai/ai.service.js.map +1 -1
- package/dist/app-command/app-command.module.d.ts +3 -0
- package/dist/app-command/app-command.module.d.ts.map +1 -0
- package/dist/app-command/app-command.module.js +22 -0
- package/dist/app-command/app-command.module.js.map +1 -0
- package/dist/app-command/app-command.registry.d.ts +22 -0
- package/dist/app-command/app-command.registry.d.ts.map +1 -0
- package/dist/{webhook-integration/webhook-command.registry.js → app-command/app-command.registry.js} +6 -6
- package/dist/app-command/app-command.registry.js.map +1 -0
- package/dist/auth/auth.controller.d.ts +2 -2
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +20 -7
- package/dist/auth/auth.controller.js.map +1 -1
- package/dist/auth/auth.service.d.ts +3 -3
- package/dist/auth/auth.service.d.ts.map +1 -1
- package/dist/auth/auth.service.js +10 -36
- package/dist/auth/auth.service.js.map +1 -1
- package/dist/auth/dto/forget.dto.d.ts +1 -0
- package/dist/auth/dto/forget.dto.d.ts.map +1 -1
- package/dist/auth/dto/forget.dto.js +5 -0
- package/dist/auth/dto/forget.dto.js.map +1 -1
- package/dist/auth/guards/auth.guard.d.ts.map +1 -1
- package/dist/auth/guards/auth.guard.js +4 -1
- package/dist/auth/guards/auth.guard.js.map +1 -1
- package/dist/challenge/challenge.service.d.ts +8 -7
- package/dist/challenge/challenge.service.d.ts.map +1 -1
- package/dist/challenge/challenge.service.js +27 -19
- package/dist/challenge/challenge.service.js.map +1 -1
- package/dist/file/dto/upload.dto.d.ts +3 -0
- package/dist/file/dto/upload.dto.d.ts.map +1 -1
- package/dist/file/dto/upload.dto.js +22 -0
- package/dist/file/dto/upload.dto.js.map +1 -1
- package/dist/file/file.controller.d.ts +2 -1
- package/dist/file/file.controller.d.ts.map +1 -1
- package/dist/file/file.controller.js +29 -16
- package/dist/file/file.controller.js.map +1 -1
- package/dist/file/file.service.d.ts +51 -1
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +250 -18
- package/dist/file/file.service.js.map +1 -1
- package/dist/file/image-optimization.service.d.ts +17 -4
- package/dist/file/image-optimization.service.d.ts.map +1 -1
- package/dist/file/image-optimization.service.js +101 -46
- package/dist/file/image-optimization.service.js.map +1 -1
- package/dist/file/provider/s3.provider.d.ts.map +1 -1
- package/dist/file/provider/s3.provider.js +15 -7
- package/dist/file/provider/s3.provider.js.map +1 -1
- package/dist/file/provider/spaces.provider.d.ts.map +1 -1
- package/dist/file/provider/spaces.provider.js +18 -8
- package/dist/file/provider/spaces.provider.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/integration/services/event-subscriber.registry.d.ts +5 -0
- package/dist/integration/services/event-subscriber.registry.d.ts.map +1 -1
- package/dist/integration/services/event-subscriber.registry.js +15 -0
- package/dist/integration/services/event-subscriber.registry.js.map +1 -1
- package/dist/integration/services/outbox.processor.d.ts.map +1 -1
- package/dist/integration/services/outbox.processor.js +2 -4
- package/dist/integration/services/outbox.processor.js.map +1 -1
- package/dist/integration/services/outbox.service.d.ts +4 -2
- package/dist/integration/services/outbox.service.d.ts.map +1 -1
- package/dist/integration/services/outbox.service.js +26 -15
- package/dist/integration/services/outbox.service.js.map +1 -1
- package/dist/integration-profile/integration-credential-crypto.service.d.ts +43 -0
- package/dist/integration-profile/integration-credential-crypto.service.d.ts.map +1 -0
- package/dist/integration-profile/integration-credential-crypto.service.js +142 -0
- package/dist/integration-profile/integration-credential-crypto.service.js.map +1 -0
- package/dist/integration-profile/integration-profile.controller.d.ts +6 -7
- package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.controller.js +4 -3
- package/dist/integration-profile/integration-profile.controller.js.map +1 -1
- package/dist/integration-profile/integration-profile.module.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.module.js +5 -2
- package/dist/integration-profile/integration-profile.module.js.map +1 -1
- package/dist/integration-profile/integration-profile.secrets.d.ts +16 -0
- package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -0
- package/dist/integration-profile/integration-profile.secrets.js +56 -0
- package/dist/integration-profile/integration-profile.secrets.js.map +1 -0
- package/dist/integration-profile/integration-profile.service.d.ts +125 -8
- package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.service.js +257 -68
- package/dist/integration-profile/integration-profile.service.js.map +1 -1
- package/dist/integration-profile/integration-profile.utils.d.ts +17 -0
- package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.utils.js +31 -0
- package/dist/integration-profile/integration-profile.utils.js.map +1 -1
- package/dist/language/en.json +178 -0
- package/dist/language/pt.json +178 -0
- package/dist/mail/mail.controller.d.ts.map +1 -1
- package/dist/mail/mail.controller.js +5 -28
- package/dist/mail/mail.controller.js.map +1 -1
- package/dist/mail/mail.module.js +18 -18
- package/dist/mail/mail.module.js.map +1 -1
- package/dist/mail/mail.service.d.ts +13 -2
- package/dist/mail/mail.service.d.ts.map +1 -1
- package/dist/mail/mail.service.js +25 -15
- package/dist/mail/mail.service.js.map +1 -1
- package/dist/mcp-chat/mcp-chat.service.js +6 -5
- package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
- package/dist/oauth/providers/facebook.provider.js +5 -5
- package/dist/oauth/providers/facebook.provider.js.map +1 -1
- package/dist/oauth/providers/github.provider.js +5 -5
- package/dist/oauth/providers/github.provider.js.map +1 -1
- package/dist/oauth/providers/google.provider.js +5 -5
- package/dist/oauth/providers/google.provider.js.map +1 -1
- package/dist/oauth/providers/microsoft-entra-id.provider.js +5 -5
- package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
- package/dist/oauth/providers/microsoft.provider.js +5 -5
- package/dist/oauth/providers/microsoft.provider.js.map +1 -1
- package/dist/pdf/pdf.service.d.ts +4 -0
- package/dist/pdf/pdf.service.d.ts.map +1 -1
- package/dist/pdf/pdf.service.js +16 -9
- package/dist/pdf/pdf.service.js.map +1 -1
- package/dist/profile/profile.controller.d.ts +7 -7
- package/dist/profile/profile.controller.d.ts.map +1 -1
- package/dist/profile/profile.controller.js +28 -21
- package/dist/profile/profile.controller.js.map +1 -1
- package/dist/profile/profile.service.d.ts +7 -7
- package/dist/profile/profile.service.d.ts.map +1 -1
- package/dist/profile/profile.service.js +21 -25
- package/dist/profile/profile.service.js.map +1 -1
- package/dist/setting/setting.service.d.ts +1 -0
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +15 -2
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/token/token.service.d.ts.map +1 -1
- package/dist/token/token.service.js +2 -1
- package/dist/token/token.service.js.map +1 -1
- package/dist/user/user.controller.d.ts +9 -0
- package/dist/user/user.controller.d.ts.map +1 -1
- package/dist/user/user.controller.js +13 -0
- package/dist/user/user.controller.js.map +1 -1
- package/dist/user/user.service.d.ts +7 -0
- package/dist/user/user.service.d.ts.map +1 -1
- package/dist/user/user.service.js +19 -0
- package/dist/user/user.service.js.map +1 -1
- package/dist/utils/webauthn-origin.d.ts +11 -0
- package/dist/utils/webauthn-origin.d.ts.map +1 -0
- package/dist/utils/webauthn-origin.js +37 -0
- package/dist/utils/webauthn-origin.js.map +1 -0
- package/dist/webhook-integration/webhook-integration.controller.d.ts +13 -0
- package/dist/webhook-integration/webhook-integration.controller.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.controller.js +29 -0
- package/dist/webhook-integration/webhook-integration.controller.js.map +1 -1
- package/dist/webhook-integration/webhook-integration.module.js +3 -3
- package/dist/webhook-integration/webhook-integration.module.js.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.d.ts +18 -2
- package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.js +110 -6
- package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
- package/hedhog/data/integration_event_catalog.yaml +118 -118
- package/hedhog/data/integration_provider.yaml +9 -0
- package/hedhog/data/integration_type.yaml +6 -0
- package/hedhog/data/menu.yaml +6 -0
- package/hedhog/data/role.yaml +7 -0
- package/hedhog/data/route.yaml +192 -207
- package/hedhog/data/setting_group.yaml +119 -13
- package/hedhog/frontend/app/access-log/page.tsx.ejs +35 -3
- package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +36 -3
- package/hedhog/frontend/app/account/components/mcp-tokens.tsx.ejs +39 -4
- package/hedhog/frontend/app/account/components/tokens.tsx.ejs +39 -4
- package/hedhog/frontend/app/ai_agent/page.tsx.ejs +35 -2
- package/hedhog/frontend/app/configurations/[slug]/components/byte-size-input.tsx.ejs +160 -0
- package/hedhog/frontend/app/configurations/[slug]/components/duration-input.tsx.ejs +157 -0
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +244 -63
- package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +33 -1
- package/hedhog/frontend/app/dashboard/management/tabs/component-roles-tab.tsx.ejs +35 -3
- package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +36 -4
- package/hedhog/frontend/app/dashboard/management/tabs/dashboard-roles-tab.tsx.ejs +35 -3
- package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +36 -4
- package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +35 -3
- package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +141 -29
- package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +118 -11
- package/hedhog/frontend/app/mail/log/page.tsx.ejs +37 -4
- package/hedhog/frontend/app/menu/page.tsx.ejs +34 -2
- package/hedhog/frontend/app/preferences/page.tsx.ejs +77 -0
- package/hedhog/frontend/app/roles/menus.tsx.ejs +35 -3
- package/hedhog/frontend/app/roles/page.tsx.ejs +34 -2
- package/hedhog/frontend/app/roles/routes.tsx.ejs +36 -4
- package/hedhog/frontend/app/roles/users.tsx.ejs +36 -4
- package/hedhog/frontend/app/routes/page.tsx.ejs +34 -2
- package/hedhog/frontend/app/setting-list/page.tsx.ejs +34 -2
- package/hedhog/frontend/app/users/page.tsx.ejs +51 -21
- package/hedhog/frontend/messages/en.json +69 -4
- package/hedhog/frontend/messages/pt.json +69 -4
- package/hedhog/table/event_webhook.yaml +55 -55
- package/hedhog/table/event_webhook_delivery_log.yaml +66 -66
- package/hedhog/table/event_webhook_event.yaml +26 -26
- package/hedhog/table/integration_event_catalog.yaml +41 -41
- package/hedhog/table/mcp_conversation.yaml +12 -12
- package/hedhog/table/mcp_conversation_message.yaml +23 -23
- package/hedhog/table/mcp_tool_call_log.yaml +22 -22
- package/hedhog/table/route.yaml +33 -33
- package/hedhog/table/setting.yaml +2 -0
- package/hedhog/table/webhook_call_log.yaml +3 -0
- package/hedhog/table/webhook_integration.yaml +43 -43
- package/package.json +9 -4
- package/src/ai/ai.service.ts +9 -6
- package/src/app-command/app-command.module.ts +9 -0
- package/src/{webhook-integration/webhook-command.registry.ts → app-command/app-command.registry.ts} +9 -9
- package/src/auth/auth.controller.ts +20 -4
- package/src/auth/auth.service.ts +10 -36
- package/src/auth/dto/forget.dto.ts +5 -1
- package/src/auth/guards/auth.guard.ts +4 -1
- package/src/challenge/challenge.service.ts +30 -19
- package/src/file/dto/upload.dto.ts +25 -1
- package/src/file/file.controller.ts +31 -18
- package/src/file/file.service.ts +341 -16
- package/src/file/image-optimization.service.ts +111 -46
- package/src/file/provider/s3.provider.ts +16 -7
- package/src/file/provider/spaces.provider.ts +15 -5
- package/src/index.ts +4 -1
- package/src/integration/services/event-subscriber.registry.ts +15 -0
- package/src/integration/services/outbox.processor.ts +2 -5
- package/src/integration/services/outbox.service.ts +35 -17
- package/src/integration/types/event.types.ts +4 -4
- package/src/integration-profile/integration-credential-crypto.service.ts +146 -0
- package/src/integration-profile/integration-profile.controller.ts +2 -0
- package/src/integration-profile/integration-profile.module.ts +5 -2
- package/src/integration-profile/integration-profile.secrets.ts +51 -0
- package/src/integration-profile/integration-profile.service.ts +335 -54
- package/src/integration-profile/integration-profile.utils.ts +56 -0
- package/src/language/en.json +5 -1
- package/src/language/pt.json +5 -1
- package/src/mail/mail.controller.ts +5 -29
- package/src/mail/mail.module.ts +5 -5
- package/src/mail/mail.service.ts +37 -13
- package/src/mcp/decorators/mcp-tool.decorator.ts +7 -7
- package/src/mcp/guards/mcp-permission.guard.ts +51 -51
- package/src/mcp/interfaces/mcp-tool-definition.interface.ts +6 -6
- package/src/mcp/mcp-server.service.ts +87 -87
- package/src/mcp/types/mcp-context.type.ts +6 -6
- package/src/mcp-chat/dto/create-conversation.dto.ts +7 -7
- package/src/mcp-chat/dto/send-message.dto.ts +7 -7
- package/src/mcp-chat/mcp-chat.service.ts +5 -5
- package/src/notification/events/notification-stale-progress-check.event.ts +5 -5
- package/src/oauth/oauth-callback-coordinator.service.spec.ts +159 -0
- package/src/oauth/providers/facebook.provider.ts +3 -3
- package/src/oauth/providers/github.provider.ts +3 -3
- package/src/oauth/providers/google.provider.ts +3 -3
- package/src/oauth/providers/microsoft-entra-id.provider.ts +3 -3
- package/src/oauth/providers/microsoft.provider.ts +3 -3
- package/src/pdf/pdf.service.ts +19 -6
- package/src/profile/profile.controller.ts +21 -14
- package/src/profile/profile.service.ts +23 -20
- package/src/route/dto/create.dto.ts +32 -32
- package/src/route/dto/update.dto.ts +31 -31
- package/src/security/security.service.spec.ts +222 -0
- package/src/setting/setting.service.ts +15 -2
- package/src/token/token.service.ts +2 -1
- package/src/user/user.controller.ts +26 -16
- package/src/user/user.service.ts +25 -0
- package/src/utils/webauthn-origin.ts +38 -0
- package/src/webhook-integration/integration-event-catalog.controller.ts +50 -50
- package/src/webhook-integration/integration-event-catalog.service.ts +117 -117
- package/src/webhook-integration/webhook-integration.controller.ts +23 -0
- package/src/webhook-integration/webhook-integration.module.ts +3 -3
- package/src/webhook-integration/webhook-integration.service.ts +115 -5
- package/dist/webhook-integration/webhook-command.registry.d.ts +0 -22
- package/dist/webhook-integration/webhook-command.registry.d.ts.map +0 -1
- package/dist/webhook-integration/webhook-command.registry.js.map +0 -1
- package/src/menu/menu.service.spec.ts +0 -247
- package/src/route/route.service.spec.ts +0 -300
- package/src/screen/screen.service.spec.ts +0 -298
- package/src/user/user.service.spec.ts +0 -294
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
InputGroup,
|
|
5
|
+
InputGroupAddon,
|
|
6
|
+
InputGroupInput,
|
|
7
|
+
} from '@/components/ui/input-group';
|
|
8
|
+
import {
|
|
9
|
+
Select,
|
|
10
|
+
SelectContent,
|
|
11
|
+
SelectItem,
|
|
12
|
+
SelectTrigger,
|
|
13
|
+
SelectValue,
|
|
14
|
+
} from '@/components/ui/select';
|
|
15
|
+
import { cn } from '@/lib/utils';
|
|
16
|
+
import { useTranslations } from 'next-intl';
|
|
17
|
+
import { useState } from 'react';
|
|
18
|
+
|
|
19
|
+
type ByteUnit = 'bytes' | 'kb' | 'mb' | 'gb' | 'tb';
|
|
20
|
+
|
|
21
|
+
// Base binária (1024) — consistente com os valores existentes (ex.: 10485760 = 10 MB).
|
|
22
|
+
const UNIT_BYTES: Record<ByteUnit, number> = {
|
|
23
|
+
bytes: 1,
|
|
24
|
+
kb: 1024,
|
|
25
|
+
mb: 1024 ** 2,
|
|
26
|
+
gb: 1024 ** 3,
|
|
27
|
+
tb: 1024 ** 4,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Da maior para a menor unidade — usado para escolher a melhor unidade de exibição.
|
|
31
|
+
const UNIT_ORDER: ByteUnit[] = ['tb', 'gb', 'mb', 'kb', 'bytes'];
|
|
32
|
+
|
|
33
|
+
const STORAGE_PREFIX = 'byte-unit:';
|
|
34
|
+
|
|
35
|
+
/** Maior unidade que divide o total exatamente (default: bytes). */
|
|
36
|
+
function pickBestUnit(totalBytes: number): ByteUnit {
|
|
37
|
+
if (totalBytes > 0) {
|
|
38
|
+
for (const unit of UNIT_ORDER) {
|
|
39
|
+
if (totalBytes % UNIT_BYTES[unit] === 0) {
|
|
40
|
+
return unit;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return 'bytes';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function readSavedUnit(persistKey?: string): ByteUnit | null {
|
|
48
|
+
if (!persistKey || typeof window === 'undefined') {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const saved = window.localStorage.getItem(STORAGE_PREFIX + persistKey);
|
|
52
|
+
return saved && saved in UNIT_BYTES ? (saved as ByteUnit) : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function toDisplay(totalBytes: number, unit: ByteUnit): string {
|
|
56
|
+
if (!totalBytes) {
|
|
57
|
+
return '0';
|
|
58
|
+
}
|
|
59
|
+
const value = totalBytes / UNIT_BYTES[unit];
|
|
60
|
+
// Evita ruído de ponto flutuante (ex.: 0.30000000000000004).
|
|
61
|
+
return String(Math.round(value * 10000) / 10000);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ByteSizeInputProps {
|
|
65
|
+
/** Valor total em bytes (unidade canônica armazenada). */
|
|
66
|
+
value: number;
|
|
67
|
+
/** Chamado com o novo total em bytes. */
|
|
68
|
+
onChange: (totalBytes: number) => void;
|
|
69
|
+
/** Slug do setting — usado para lembrar a unidade escolhida no localStorage. */
|
|
70
|
+
persistKey?: string;
|
|
71
|
+
disabled?: boolean;
|
|
72
|
+
className?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Input numérico com seletor de unidade (Bytes/KB/MB/GB/TB, base binária 1024). O valor é
|
|
77
|
+
* sempre persistido em **bytes**; o usuário escolhe apenas como visualizar/editar e a conversão
|
|
78
|
+
* de volta para bytes acontece a cada alteração. A unidade inicial é a maior que divide o valor
|
|
79
|
+
* exatamente (ou a última escolhida pelo usuário, se houver `persistKey`).
|
|
80
|
+
*
|
|
81
|
+
* Irmão do `DurationInput` (segundos → min/horas/dias).
|
|
82
|
+
*/
|
|
83
|
+
export function ByteSizeInput({
|
|
84
|
+
value,
|
|
85
|
+
onChange,
|
|
86
|
+
persistKey,
|
|
87
|
+
disabled,
|
|
88
|
+
className,
|
|
89
|
+
}: ByteSizeInputProps) {
|
|
90
|
+
const t = useTranslations('core.Configurations');
|
|
91
|
+
const total = Math.max(0, Math.floor(Number(value) || 0));
|
|
92
|
+
|
|
93
|
+
const [unit, setUnit] = useState<ByteUnit>(
|
|
94
|
+
() => readSavedUnit(persistKey) ?? pickBestUnit(total)
|
|
95
|
+
);
|
|
96
|
+
// Texto digitado em andamento. Quando `null`, a exibição é derivada de `value` + `unit`
|
|
97
|
+
// (mantém-se sincronizado com mudanças externas sem precisar de efeito/setState).
|
|
98
|
+
const [editValue, setEditValue] = useState<string | null>(null);
|
|
99
|
+
|
|
100
|
+
const display = editValue ?? toDisplay(total, unit);
|
|
101
|
+
|
|
102
|
+
const handleInputChange = (raw: string) => {
|
|
103
|
+
setEditValue(raw);
|
|
104
|
+
const parsed = parseFloat(raw);
|
|
105
|
+
const bytes = Number.isFinite(parsed)
|
|
106
|
+
? Math.max(0, Math.round(parsed * UNIT_BYTES[unit]))
|
|
107
|
+
: 0;
|
|
108
|
+
onChange(bytes);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const handleBlur = () => {
|
|
112
|
+
// Volta a exibir o valor canônico (reformatado) na unidade atual.
|
|
113
|
+
setEditValue(null);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const handleUnitChange = (nextUnit: ByteUnit) => {
|
|
117
|
+
// Troca de unidade é só exibição: mantém os bytes constantes, não dispara onChange.
|
|
118
|
+
setUnit(nextUnit);
|
|
119
|
+
setEditValue(null);
|
|
120
|
+
if (persistKey && typeof window !== 'undefined') {
|
|
121
|
+
window.localStorage.setItem(STORAGE_PREFIX + persistKey, nextUnit);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<InputGroup className={cn('bg-background', className)}>
|
|
127
|
+
<InputGroupInput
|
|
128
|
+
type="number"
|
|
129
|
+
inputMode="decimal"
|
|
130
|
+
min={0}
|
|
131
|
+
step="any"
|
|
132
|
+
disabled={disabled}
|
|
133
|
+
value={display}
|
|
134
|
+
onChange={(e) => handleInputChange(e.target.value)}
|
|
135
|
+
onBlur={handleBlur}
|
|
136
|
+
/>
|
|
137
|
+
<InputGroupAddon align="inline-end" className="pr-1">
|
|
138
|
+
<Select
|
|
139
|
+
value={unit}
|
|
140
|
+
onValueChange={(next) => handleUnitChange(next as ByteUnit)}
|
|
141
|
+
disabled={disabled}
|
|
142
|
+
>
|
|
143
|
+
<SelectTrigger
|
|
144
|
+
size="sm"
|
|
145
|
+
className="h-7 gap-1 border-0 bg-transparent px-2 shadow-none focus-visible:ring-0"
|
|
146
|
+
>
|
|
147
|
+
<SelectValue />
|
|
148
|
+
</SelectTrigger>
|
|
149
|
+
<SelectContent>
|
|
150
|
+
<SelectItem value="bytes">{t('byteUnitBytes')}</SelectItem>
|
|
151
|
+
<SelectItem value="kb">{t('byteUnitKB')}</SelectItem>
|
|
152
|
+
<SelectItem value="mb">{t('byteUnitMB')}</SelectItem>
|
|
153
|
+
<SelectItem value="gb">{t('byteUnitGB')}</SelectItem>
|
|
154
|
+
<SelectItem value="tb">{t('byteUnitTB')}</SelectItem>
|
|
155
|
+
</SelectContent>
|
|
156
|
+
</Select>
|
|
157
|
+
</InputGroupAddon>
|
|
158
|
+
</InputGroup>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
InputGroup,
|
|
5
|
+
InputGroupAddon,
|
|
6
|
+
InputGroupInput,
|
|
7
|
+
} from '@/components/ui/input-group';
|
|
8
|
+
import {
|
|
9
|
+
Select,
|
|
10
|
+
SelectContent,
|
|
11
|
+
SelectItem,
|
|
12
|
+
SelectTrigger,
|
|
13
|
+
SelectValue,
|
|
14
|
+
} from '@/components/ui/select';
|
|
15
|
+
import { cn } from '@/lib/utils';
|
|
16
|
+
import { useTranslations } from 'next-intl';
|
|
17
|
+
import { useState } from 'react';
|
|
18
|
+
|
|
19
|
+
type DurationUnit = 'seconds' | 'minutes' | 'hours' | 'days';
|
|
20
|
+
|
|
21
|
+
const UNIT_SECONDS: Record<DurationUnit, number> = {
|
|
22
|
+
seconds: 1,
|
|
23
|
+
minutes: 60,
|
|
24
|
+
hours: 3600,
|
|
25
|
+
days: 86400,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Da maior para a menor unidade — usado para escolher a melhor unidade de exibição.
|
|
29
|
+
const UNIT_ORDER: DurationUnit[] = ['days', 'hours', 'minutes', 'seconds'];
|
|
30
|
+
|
|
31
|
+
const STORAGE_PREFIX = 'duration-unit:';
|
|
32
|
+
|
|
33
|
+
/** Maior unidade que divide o total exatamente (default: segundos). */
|
|
34
|
+
function pickBestUnit(totalSeconds: number): DurationUnit {
|
|
35
|
+
if (totalSeconds > 0) {
|
|
36
|
+
for (const unit of UNIT_ORDER) {
|
|
37
|
+
if (totalSeconds % UNIT_SECONDS[unit] === 0) {
|
|
38
|
+
return unit;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return 'seconds';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function readSavedUnit(persistKey?: string): DurationUnit | null {
|
|
46
|
+
if (!persistKey || typeof window === 'undefined') {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const saved = window.localStorage.getItem(STORAGE_PREFIX + persistKey);
|
|
50
|
+
return saved && saved in UNIT_SECONDS ? (saved as DurationUnit) : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function toDisplay(totalSeconds: number, unit: DurationUnit): string {
|
|
54
|
+
if (!totalSeconds) {
|
|
55
|
+
return '0';
|
|
56
|
+
}
|
|
57
|
+
const value = totalSeconds / UNIT_SECONDS[unit];
|
|
58
|
+
// Evita ruído de ponto flutuante (ex.: 0.30000000000000004).
|
|
59
|
+
return String(Math.round(value * 10000) / 10000);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface DurationInputProps {
|
|
63
|
+
/** Valor total em segundos (unidade canônica armazenada). */
|
|
64
|
+
value: number;
|
|
65
|
+
/** Chamado com o novo total em segundos. */
|
|
66
|
+
onChange: (totalSeconds: number) => void;
|
|
67
|
+
/** Slug do setting — usado para lembrar a unidade escolhida no localStorage. */
|
|
68
|
+
persistKey?: string;
|
|
69
|
+
disabled?: boolean;
|
|
70
|
+
className?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Input numérico com seletor de unidade (segundos/minutos/horas/dias). O valor é sempre
|
|
75
|
+
* persistido em **segundos**; o usuário escolhe apenas como visualizar/editar e a conversão
|
|
76
|
+
* de volta para segundos acontece a cada alteração. A unidade inicial é a maior que divide
|
|
77
|
+
* o valor exatamente (ou a última escolhida pelo usuário, se houver `persistKey`).
|
|
78
|
+
*
|
|
79
|
+
* Não confundir com o `InputDuration` (campo mascarado mm:ss) usado na estrutura de cursos do LMS.
|
|
80
|
+
*/
|
|
81
|
+
export function DurationInput({
|
|
82
|
+
value,
|
|
83
|
+
onChange,
|
|
84
|
+
persistKey,
|
|
85
|
+
disabled,
|
|
86
|
+
className,
|
|
87
|
+
}: DurationInputProps) {
|
|
88
|
+
const t = useTranslations('core.Configurations');
|
|
89
|
+
const total = Math.max(0, Math.floor(Number(value) || 0));
|
|
90
|
+
|
|
91
|
+
const [unit, setUnit] = useState<DurationUnit>(
|
|
92
|
+
() => readSavedUnit(persistKey) ?? pickBestUnit(total)
|
|
93
|
+
);
|
|
94
|
+
// Texto digitado em andamento. Quando `null`, a exibição é derivada de `value` + `unit`
|
|
95
|
+
// (mantém-se sincronizado com mudanças externas sem precisar de efeito/setState).
|
|
96
|
+
const [editValue, setEditValue] = useState<string | null>(null);
|
|
97
|
+
|
|
98
|
+
const display = editValue ?? toDisplay(total, unit);
|
|
99
|
+
|
|
100
|
+
const handleInputChange = (raw: string) => {
|
|
101
|
+
setEditValue(raw);
|
|
102
|
+
const parsed = parseFloat(raw);
|
|
103
|
+
const seconds = Number.isFinite(parsed)
|
|
104
|
+
? Math.max(0, Math.round(parsed * UNIT_SECONDS[unit]))
|
|
105
|
+
: 0;
|
|
106
|
+
onChange(seconds);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const handleBlur = () => {
|
|
110
|
+
// Volta a exibir o valor canônico (reformatado) na unidade atual.
|
|
111
|
+
setEditValue(null);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const handleUnitChange = (nextUnit: DurationUnit) => {
|
|
115
|
+
// Troca de unidade é só exibição: mantém os segundos constantes, não dispara onChange.
|
|
116
|
+
setUnit(nextUnit);
|
|
117
|
+
setEditValue(null);
|
|
118
|
+
if (persistKey && typeof window !== 'undefined') {
|
|
119
|
+
window.localStorage.setItem(STORAGE_PREFIX + persistKey, nextUnit);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<InputGroup className={cn('bg-background', className)}>
|
|
125
|
+
<InputGroupInput
|
|
126
|
+
type="number"
|
|
127
|
+
inputMode="decimal"
|
|
128
|
+
min={0}
|
|
129
|
+
step="any"
|
|
130
|
+
disabled={disabled}
|
|
131
|
+
value={display}
|
|
132
|
+
onChange={(e) => handleInputChange(e.target.value)}
|
|
133
|
+
onBlur={handleBlur}
|
|
134
|
+
/>
|
|
135
|
+
<InputGroupAddon align="inline-end" className="pr-1">
|
|
136
|
+
<Select
|
|
137
|
+
value={unit}
|
|
138
|
+
onValueChange={(next) => handleUnitChange(next as DurationUnit)}
|
|
139
|
+
disabled={disabled}
|
|
140
|
+
>
|
|
141
|
+
<SelectTrigger
|
|
142
|
+
size="sm"
|
|
143
|
+
className="h-7 gap-1 border-0 bg-transparent px-2 shadow-none focus-visible:ring-0"
|
|
144
|
+
>
|
|
145
|
+
<SelectValue />
|
|
146
|
+
</SelectTrigger>
|
|
147
|
+
<SelectContent>
|
|
148
|
+
<SelectItem value="seconds">{t('durationUnitSeconds')}</SelectItem>
|
|
149
|
+
<SelectItem value="minutes">{t('durationUnitMinutes')}</SelectItem>
|
|
150
|
+
<SelectItem value="hours">{t('durationUnitHours')}</SelectItem>
|
|
151
|
+
<SelectItem value="days">{t('durationUnitDays')}</SelectItem>
|
|
152
|
+
</SelectContent>
|
|
153
|
+
</Select>
|
|
154
|
+
</InputGroupAddon>
|
|
155
|
+
</InputGroup>
|
|
156
|
+
);
|
|
157
|
+
}
|