@tuturuuu/ui 0.12.0 → 0.14.0

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 (51) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/package.json +4 -4
  3. package/src/components/ui/calendar-app/components/habits-panel.tsx +5 -2
  4. package/src/components/ui/calendar-app/components/priority-view.tsx +15 -7
  5. package/src/components/ui/calendar-app/components/scheduling-dialog.tsx +7 -3
  6. package/src/components/ui/calendar-app/components/task-scheduler-panel.tsx +7 -3
  7. package/src/components/ui/calendar-app/components/time-tracker/index.tsx +14 -9
  8. package/src/components/ui/custom/settings/task-settings.tsx +61 -2
  9. package/src/components/ui/custom/tables/data-table.tsx +58 -8
  10. package/src/components/ui/tu-do/boards/boardId/board-column.tsx +4 -0
  11. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/auto-scroll.test.ts +118 -0
  12. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/auto-scroll.ts +144 -70
  13. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.test.ts +63 -1
  14. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +21 -10
  15. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +6 -0
  16. package/src/components/ui/tu-do/boards/boardId/kanban.tsx +4 -2
  17. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +11 -1
  18. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +7 -3
  19. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-resource-context.test.ts +113 -0
  20. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-resource-context.ts +50 -0
  21. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +65 -18
  22. package/src/components/ui/tu-do/boards/boardId/timeline-board.tsx +1 -1
  23. package/src/components/ui/tu-do/cycles/task-cycles-client.tsx +23 -14
  24. package/src/components/ui/tu-do/estimates/use-task-estimates.ts +9 -4
  25. package/src/components/ui/tu-do/habits/client.tsx +4 -1
  26. package/src/components/ui/tu-do/my-tasks/__tests__/use-task-context-actions.test.ts +6 -3
  27. package/src/components/ui/tu-do/my-tasks/task-list-with-completion.tsx +19 -11
  28. package/src/components/ui/tu-do/my-tasks/use-my-tasks-query.ts +15 -6
  29. package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +18 -13
  30. package/src/components/ui/tu-do/my-tasks/use-task-context-actions.ts +17 -10
  31. package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +70 -0
  32. package/src/components/ui/tu-do/shared/board-views.tsx +33 -3
  33. package/src/components/ui/tu-do/shared/fade-setting-initializer.tsx +3 -1
  34. package/src/components/ui/tu-do/shared/list-view.tsx +1 -1
  35. package/src/components/ui/tu-do/shared/task-edit-dialog/components/quick-settings-popover.tsx +6 -2
  36. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/__tests__/use-update-shared-task.test.ts +9 -5
  37. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/task-api.ts +13 -8
  38. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-overrides.ts +21 -9
  39. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-update-shared-task.ts +10 -5
  40. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +4 -2
  41. package/src/components/ui/tu-do/shared/task-estimation-picker.tsx +5 -2
  42. package/src/components/ui/tu-do/shared/task-quick-create-target-list.ts +20 -0
  43. package/src/components/ui/tu-do/shared/task-row-actions-menu.tsx +1 -1
  44. package/src/hooks/__tests__/use-task-actions.test.tsx +51 -0
  45. package/src/hooks/task-actions-personal-external.ts +2 -3
  46. package/src/hooks/use-board-actions.ts +27 -17
  47. package/src/hooks/use-calendar.tsx +7 -3
  48. package/src/hooks/use-settings-dialog-shortcut.ts +51 -0
  49. package/src/hooks/use-user-config.ts +10 -2
  50. package/src/lib/task-personal-external.ts +49 -0
  51. package/src/lib/tasks-app-url.ts +85 -0
@@ -0,0 +1,51 @@
1
+ 'use client';
2
+
3
+ import { useEffect } from 'react';
4
+
5
+ interface UseSettingsDialogShortcutOptions {
6
+ enabled: boolean;
7
+ onOpen: () => void;
8
+ }
9
+
10
+ function isEditableShortcutTarget(target: EventTarget | null) {
11
+ if (!(target instanceof HTMLElement)) return false;
12
+
13
+ const tagName = target.tagName.toLowerCase();
14
+
15
+ return (
16
+ target.isContentEditable ||
17
+ tagName === 'input' ||
18
+ tagName === 'textarea' ||
19
+ tagName === 'select'
20
+ );
21
+ }
22
+
23
+ function isSettingsDialogShortcut(event: KeyboardEvent) {
24
+ return (event.metaKey || event.ctrlKey) && !event.altKey && event.key === ',';
25
+ }
26
+
27
+ /**
28
+ * Opens the app settings dialog on Cmd/Ctrl+, — the platform-wide convention.
29
+ * Ignores the shortcut while typing in editable fields and when another handler
30
+ * has already called preventDefault(). Shared by apps/web and every satellite
31
+ * app (wired once in the satellite user-nav shell).
32
+ */
33
+ export function useSettingsDialogShortcut({
34
+ enabled,
35
+ onOpen,
36
+ }: UseSettingsDialogShortcutOptions) {
37
+ useEffect(() => {
38
+ if (!enabled) return;
39
+
40
+ const handleKeyDown = (event: KeyboardEvent) => {
41
+ if (event.defaultPrevented || !isSettingsDialogShortcut(event)) return;
42
+ if (isEditableShortcutTarget(event.target)) return;
43
+
44
+ event.preventDefault();
45
+ onOpen();
46
+ };
47
+
48
+ window.addEventListener('keydown', handleKeyDown);
49
+ return () => window.removeEventListener('keydown', handleKeyDown);
50
+ }, [enabled, onOpen]);
51
+ }
@@ -11,14 +11,22 @@ import { getUserConfig, updateUserConfig } from '@tuturuuu/internal-api/users';
11
11
  * @param defaultValue - Default value if config doesn't exist
12
12
  * @returns Query result with the config value
13
13
  */
14
- export function useUserConfig(configId: string, defaultValue: string = '') {
14
+ export function useUserConfig(
15
+ configId: string,
16
+ defaultValue: string = '',
17
+ options?: {
18
+ enabled?: boolean;
19
+ staleTime?: number;
20
+ }
21
+ ) {
15
22
  return useQuery({
16
23
  queryKey: ['user-config', configId],
17
24
  queryFn: async () => {
18
25
  const data = await getUserConfig(configId);
19
26
  return (data.value as string) ?? defaultValue;
20
27
  },
21
- staleTime: 5 * 60 * 1000, // 5 minutes
28
+ enabled: options?.enabled !== false && Boolean(configId),
29
+ staleTime: options?.staleTime ?? 5 * 60 * 1000, // 5 minutes
22
30
  });
23
31
  }
24
32
 
@@ -0,0 +1,49 @@
1
+ import type { Task } from '@tuturuuu/types/primitives/Task';
2
+ import { isPersonalExternalStagingListId } from '@tuturuuu/utils/task-helper';
3
+
4
+ function hasValue(value: string | null | undefined): value is string {
5
+ return typeof value === 'string' && value.length > 0;
6
+ }
7
+
8
+ export function isPersonalExternalOverlayTask(task?: Task | null) {
9
+ if (!task) return false;
10
+
11
+ if (task.is_personal_external === true) {
12
+ return true;
13
+ }
14
+
15
+ if (task.is_personal_external === false) {
16
+ return false;
17
+ }
18
+
19
+ if (isPersonalExternalStagingListId(task.list_id)) {
20
+ return true;
21
+ }
22
+
23
+ const personalBoardId = hasValue(task.personal_board_id)
24
+ ? task.personal_board_id
25
+ : null;
26
+ const personalListId = hasValue(task.personal_list_id)
27
+ ? task.personal_list_id
28
+ : null;
29
+
30
+ if (!personalBoardId && !personalListId) {
31
+ return false;
32
+ }
33
+
34
+ if (
35
+ hasValue(task.source_board_id) &&
36
+ personalBoardId &&
37
+ task.source_board_id !== personalBoardId
38
+ ) {
39
+ return true;
40
+ }
41
+
42
+ return (
43
+ !hasValue(task.source_board_id) &&
44
+ hasValue(task.source_workspace_id) &&
45
+ hasValue(task.source_list_id) &&
46
+ personalListId !== null &&
47
+ task.source_list_id !== personalListId
48
+ );
49
+ }
@@ -0,0 +1,85 @@
1
+ function normalizeOrigin(value?: string) {
2
+ if (!value) {
3
+ return null;
4
+ }
5
+
6
+ const [firstValue] = value
7
+ .split(/[,\n]/u)
8
+ .map((entry) => entry.trim())
9
+ .filter(Boolean);
10
+
11
+ if (!firstValue) {
12
+ return null;
13
+ }
14
+
15
+ const normalized = /^[a-z]+:\/\//iu.test(firstValue)
16
+ ? firstValue
17
+ : `https://${firstValue}`;
18
+
19
+ try {
20
+ return new URL(normalized).origin;
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ function getTasksAppOriginForBrowser() {
27
+ const configured =
28
+ normalizeOrigin(process.env.NEXT_PUBLIC_TASKS_APP_URL) ??
29
+ normalizeOrigin(process.env.NEXT_PUBLIC_TUDO_APP_URL);
30
+
31
+ if (configured) {
32
+ return configured;
33
+ }
34
+
35
+ if (typeof window === 'undefined') {
36
+ return null;
37
+ }
38
+
39
+ const { hostname, port, protocol } = window.location;
40
+ const normalizedHostname = hostname.toLowerCase();
41
+
42
+ if (
43
+ normalizedHostname === 'tasks.tuturuuu.com' ||
44
+ normalizedHostname === 'tasks.tuturuuu.localhost' ||
45
+ port === '7809'
46
+ ) {
47
+ return null;
48
+ }
49
+
50
+ if (
51
+ normalizedHostname === 'localhost' ||
52
+ normalizedHostname === '127.0.0.1' ||
53
+ normalizedHostname === '::1' ||
54
+ normalizedHostname === '[::1]'
55
+ ) {
56
+ return port ? `${protocol}//${hostname}:7809` : null;
57
+ }
58
+
59
+ if (
60
+ normalizedHostname === 'tuturuuu.localhost' ||
61
+ normalizedHostname.endsWith('.tuturuuu.localhost')
62
+ ) {
63
+ return `${protocol}//tasks.tuturuuu.localhost`;
64
+ }
65
+
66
+ if (
67
+ normalizedHostname === 'tuturuuu.com' ||
68
+ normalizedHostname.endsWith('.tuturuuu.com')
69
+ ) {
70
+ return 'https://tasks.tuturuuu.com';
71
+ }
72
+
73
+ return null;
74
+ }
75
+
76
+ export function getTasksAppUrl(path: string) {
77
+ const normalizedPath = path.startsWith('/') ? path : `/${path}`;
78
+ const origin = getTasksAppOriginForBrowser();
79
+
80
+ return origin ? new URL(normalizedPath, origin).toString() : normalizedPath;
81
+ }
82
+
83
+ export function getTaskApiUrl(path: string) {
84
+ return getTasksAppUrl(path);
85
+ }