diff --git a/lint_output.txt b/lint_output.txt
new file mode 100644
index 0000000..44d07a7
--- /dev/null
+++ b/lint_output.txt
@@ -0,0 +1,884 @@
+
+> fe_admin_history_web@2.2.3 lint
+> eslint .
+
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/jsvectormap.d.ts
+ 2:24 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/app/(full-width-pages)/(auth)/layout.tsx
+ 1:8 warning 'GridShape' is defined but never used @typescript-eslint/no-unused-vars
+ 5:8 warning 'Image' is defined but never used @typescript-eslint/no-unused-vars
+ 6:8 warning 'Link' is defined but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/app/(full-width-pages)/(auth)/reset-password/page.tsx
+ 48:14 warning 'error' is defined but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/app/editor/[id]/page.tsx
+ 6:20 warning 'MapFeaturePayload' is defined but never used @typescript-eslint/no-unused-vars
+ 13:8 warning 'ReplayPreviewOverlay' is defined but never used @typescript-eslint/no-unused-vars
+ 14:8 warning 'ReplayPreviewLayerPanel' is defined but never used @typescript-eslint/no-unused-vars
+ 16:8 warning 'PublicWikiSidebar' is defined but never used @typescript-eslint/no-unused-vars
+ 22:8 warning 'PresentPlaceSearch' is defined but never used @typescript-eslint/no-unused-vars
+ 22:35 warning 'HistoricalGeometryFocusPayload' is defined but never used @typescript-eslint/no-unused-vars
+ 22:72 warning 'PresentPlaceSelection' is defined but never used @typescript-eslint/no-unused-vars
+ 26:10 warning 'fetchWikiById' is defined but never used @typescript-eslint/no-unused-vars
+ 53:5 warning 'persistBackgroundLayerVisibility' is defined but never used @typescript-eslint/no-unused-vars
+ 89:10 warning 'fitMapToFeatureCollection' is defined but never used @typescript-eslint/no-unused-vars
+ 119:6 warning 'PreviewLinkEntityPopupState' is defined but never used @typescript-eslint/no-unused-vars
+ 545:11 warning 'handleViewerPreviewTimelineYearChange' is assigned a value but never used @typescript-eslint/no-unused-vars
+ 556:11 warning 'handleViewerPreviewTimelineFilterChange' is assigned a value but never used @typescript-eslint/no-unused-vars
+ 583:37 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 616:23 warning 'replayPreviewActiveCursor' is assigned a value but never used @typescript-eslint/no-unused-vars
+ 618:22 warning 'replayPreviewSidebarOpen' is assigned a value but never used @typescript-eslint/no-unused-vars
+ 664:11 warning 'replayPreviewActiveEntity' is assigned a value but never used @typescript-eslint/no-unused-vars
+ 1199:57 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 1203:62 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 2420:8 warning React Hook useCallback has a missing dependency: 'setSelectedFeatureIds'. Either include it or remove the dependency array react-hooks/exhaustive-deps
+ 3047:10 warning 'extractWikiBlockquoteText' is defined but never used @typescript-eslint/no-unused-vars
+ 3083:10 warning 'computeFixedPopupPosition' is defined but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/app/user/account/applications/page.tsx
+ 42:9 warning 'config' is assigned a value but never used @typescript-eslint/no-unused-vars
+ 62:15 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 101:23 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 170:25 warning Using `
` could result in slower LCP and higher bandwidth. Consider using `` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/app/user/account/page.tsx
+ 15:10 warning 'loading' is assigned a value but never used @typescript-eslint/no-unused-vars
+ 31:6 warning React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array react-hooks/exhaustive-deps
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/app/user/layout.tsx
+ 4:8 warning 'AppHeader' is defined but never used @typescript-eslint/no-unused-vars
+ 30:6 warning React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array react-hooks/exhaustive-deps
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/app/user/projects/[id]/page.tsx
+ 10:8 warning 'PageBreadcrumb' is defined but never used @typescript-eslint/no-unused-vars
+ 31:26 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 36:23 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 51:14 warning 'error' is defined but never used @typescript-eslint/no-unused-vars
+ 60:6 warning React Hook useEffect has a missing dependency: 'fetchProject'. Either include it or remove the dependency array react-hooks/exhaustive-deps
+ 68:14 warning 'error' is defined but never used @typescript-eslint/no-unused-vars
+ 107:16 warning 'error' is defined but never used @typescript-eslint/no-unused-vars
+ 121:14 warning 'error' is defined but never used @typescript-eslint/no-unused-vars
+ 129:26 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 138:21 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 168:21 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 204:24 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 402:74 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 427:25 warning Using `
` could result in slower LCP and higher bandwidth. Consider using `` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
+ 529:51 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/app/user/role-upgrade/page.tsx
+ 3:8 warning 'PageBreadcrumb' is defined but never used @typescript-eslint/no-unused-vars
+ 180:9 warning 'handleItemClick' is assigned a value but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/auth/tokenStore.ts
+ 48:59 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 65:11 warning 'refresh' is assigned a value but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/auth/SignInForm.tsx
+ 82:14 warning 'error' is defined but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/auth/SignUpForm.tsx
+ 75:14 warning 'error' is defined but never used @typescript-eslint/no-unused-vars
+ 109:13 warning 'signupRes' is assigned a value but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/calendar/Calendar.tsx
+ 44:5 error Error: Calling setState synchronously within an effect can trigger cascading renders
+
+Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
+* Update external systems with the latest state from React.
+* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
+
+Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/calendar/Calendar.tsx:44:5
+ 42 | useEffect(() => {
+ 43 | // Initialize with some events
+> 44 | setEvents([
+ | ^^^^^^^^^ Avoid calling setState() directly within an effect
+ 45 | {
+ 46 | id: "1",
+ 47 | title: "Event Conf.", react-hooks/set-state-in-effect
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/ecommerce/StatisticsChart.tsx
+ 157:23 error `'` can be escaped with `'`, `‘`, `'`, `’` react/no-unescaped-entities
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/form/role-upgrade/Editor.tsx
+ 10:12 error Component definition is missing display name react/display-name
+ 10:67 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 10:88 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 36:27 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 82:6 warning React Hook useEffect has a missing dependency: 'onEditorReady'. Either include it or remove the dependency array. If 'onEditorReady' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/header/UserDropdown.tsx
+ 7:10 warning 'fullDataUser' is defined but never used @typescript-eslint/no-unused-vars
+ 14:9 warning 'router' is assigned a value but never used @typescript-eslint/no-unused-vars
+ 18:10 warning 'loading' is assigned a value but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/ApplicationDetailModal.tsx
+ 91:14 warning 'error' is defined but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/ApplicationTable.tsx
+ 182:33 error Error: Cannot create components during render
+
+Components created during render will reset their state each time they are created. Declare components outside of render.
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/ApplicationTable.tsx:182:33
+ 180 | onClick={() => onSort("status")}
+ 181 | >
+> 182 | Trạng thái
+ | ^^^^^^^^ This component is created during render
+ 183 |
+ 184 |
+ 185 | 48 | const SortIcon = ({ column }: { column: AppSortColumn }) => {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 49 | const isActive = sortBy === column;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 50 | return (
+ …
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 79 | );
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 80 | };
+ | ^^^^ The component is created during render here
+ 81 |
+ 82 | const getStatusBadge = (status: string | number) => {
+ 83 | const s = status?.toString(); react-hooks/static-components
+ 193:31 error Error: Cannot create components during render
+
+Components created during render will reset their state each time they are created. Declare components outside of render.
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/ApplicationTable.tsx:193:31
+ 191 | onClick={() => onSort("created_at")}
+ 192 | >
+> 193 | Ngày nộp
+ | ^^^^^^^^ This component is created during render
+ 194 |
+ 195 |
+ 196 | 48 | const SortIcon = ({ column }: { column: AppSortColumn }) => {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 49 | const isActive = sortBy === column;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 50 | return (
+ …
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 79 | );
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 80 | };
+ | ^^^^ The component is created during render here
+ 81 |
+ 82 | const getStatusBadge = (status: string | number) => {
+ 83 | const s = status?.toString(); react-hooks/static-components
+ 204:31 error Error: Cannot create components during render
+
+Components created during render will reset their state each time they are created. Declare components outside of render.
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/ApplicationTable.tsx:204:31
+ 202 | onClick={() => onSort("reviewed_at")}
+ 203 | >
+> 204 | Cập nhật
+ | ^^^^^^^^ This component is created during render
+ 205 |
+ 206 |
+ 207 | 48 | const SortIcon = ({ column }: { column: AppSortColumn }) => {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 49 | const isActive = sortBy === column;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 50 | return (
+ …
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 79 | );
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 80 | };
+ | ^^^^ The component is created during render here
+ 81 |
+ 82 | const getStatusBadge = (status: string | number) => {
+ 83 | const s = status?.toString(); react-hooks/static-components
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/BasicTableOne.tsx
+ 101:33 error Error: Cannot create components during render
+
+Components created during render will reset their state each time they are created. Declare components outside of render.
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/BasicTableOne.tsx:101:33
+ 99 | onClick={() => onSort("display_name")}
+ 100 | >
+> 101 | Người dùng
+ | ^^^^^^^^ This component is created during render
+ 102 |
+ 103 |
+ 104 |
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/BasicTableOne.tsx:52:20
+ 50 | };
+ 51 |
+> 52 | const SortIcon = ({ column }: { column: SortColumn }) => {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 53 | const isActive = sortBy === column;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 54 | return (
+ …
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 83 | );
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 84 | };
+ | ^^^^ The component is created during render here
+ 85 |
+ 86 | return (
+ 87 |
react-hooks/static-components
+ 113:28 error Error: Cannot create components during render
+
+Components created during render will reset their state each time they are created. Declare components outside of render.
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/BasicTableOne.tsx:113:28
+ 111 | onClick={() => onSort("email")}
+ 112 | >
+> 113 | Email
+ | ^^^^^^^^ This component is created during render
+ 114 |
+ 115 |
+ 116 |
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/BasicTableOne.tsx:52:20
+ 50 | };
+ 51 |
+> 52 | const SortIcon = ({ column }: { column: SortColumn }) => {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 53 | const isActive = sortBy === column;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 54 | return (
+ …
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 83 | );
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 84 | };
+ | ^^^^ The component is created during render here
+ 85 |
+ 86 | return (
+ 87 | react-hooks/static-components
+ 165:36 error Error: Cannot create components during render
+
+Components created during render will reset their state each time they are created. Declare components outside of render.
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/BasicTableOne.tsx:165:36
+ 163 | onClick={() => onSort("created_at")}
+ 164 | >
+> 165 | Ngày tham gia
+ | ^^^^^^^^ This component is created during render
+ 166 |
+ 167 |
+ 168 |
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/BasicTableOne.tsx:52:20
+ 50 | };
+ 51 |
+> 52 | const SortIcon = ({ column }: { column: SortColumn }) => {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 53 | const isActive = sortBy === column;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 54 | return (
+ …
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 83 | );
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 84 | };
+ | ^^^^ The component is created during render here
+ 85 |
+ 86 | return (
+ 87 | react-hooks/static-components
+ 177:31 error Error: Cannot create components during render
+
+Components created during render will reset their state each time they are created. Declare components outside of render.
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/BasicTableOne.tsx:177:31
+ 175 | onClick={() => onSort("updated_at")}
+ 176 | >
+> 177 | Cập nhật
+ | ^^^^^^^^ This component is created during render
+ 178 |
+ 179 |
+ 180 |
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/BasicTableOne.tsx:52:20
+ 50 | };
+ 51 |
+> 52 | const SortIcon = ({ column }: { column: SortColumn }) => {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 53 | const isActive = sortBy === column;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 54 | return (
+ …
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 83 | );
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 84 | };
+ | ^^^^ The component is created during render here
+ 85 |
+ 86 | return (
+ 87 | react-hooks/static-components
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx
+ 141:30 error Error: Cannot create components during render
+
+Components created during render will reset their state each time they are created. Declare components outside of render.
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx:141:30
+ 139 |
+ 140 | onSort("original_name")}>
+> 141 | Tên tệp
+ | ^^^^^^^^ This component is created during render
+ 142 |
+ 143 |
+ 144 |
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx:76:20
+ 74 | };
+ 75 |
+> 76 | const SortIcon = ({ column }: { column: MediaSortColumn }) => {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 77 | const isActive = sortBy === column;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 78 | return (
+ …
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 97 | );
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 98 | };
+ | ^^^^ The component is created during render here
+ 99 |
+ 100 | const getMimeTypeBadge = (mimeType: string) => {
+ 101 | if (mimeType.includes("image")) { react-hooks/static-components
+ 146:32 error Error: Cannot create components during render
+
+Components created during render will reset their state each time they are created. Declare components outside of render.
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx:146:32
+ 144 |
+ 145 | onSort("mime_type")}>
+> 146 | Định dạng
+ | ^^^^^^^^ This component is created during render
+ 147 |
+ 148 |
+ 149 |
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx:76:20
+ 74 | };
+ 75 |
+> 76 | const SortIcon = ({ column }: { column: MediaSortColumn }) => {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 77 | const isActive = sortBy === column;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 78 | return (
+ …
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 97 | );
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 98 | };
+ | ^^^^ The component is created during render here
+ 99 |
+ 100 | const getMimeTypeBadge = (mimeType: string) => {
+ 101 | if (mimeType.includes("image")) { react-hooks/static-components
+ 151:33 error Error: Cannot create components during render
+
+Components created during render will reset their state each time they are created. Declare components outside of render.
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx:151:33
+ 149 |
+ 150 | onSort("size")}>
+> 151 | Kích thước
+ | ^^^^^^^^ This component is created during render
+ 152 |
+ 153 |
+ 154 |
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx:76:20
+ 74 | };
+ 75 |
+> 76 | const SortIcon = ({ column }: { column: MediaSortColumn }) => {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 77 | const isActive = sortBy === column;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 78 | return (
+ …
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 97 | );
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 98 | };
+ | ^^^^ The component is created during render here
+ 99 |
+ 100 | const getMimeTypeBadge = (mimeType: string) => {
+ 101 | if (mimeType.includes("image")) { react-hooks/static-components
+ 156:35 error Error: Cannot create components during render
+
+Components created during render will reset their state each time they are created. Declare components outside of render.
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx:156:35
+ 154 |
+ 155 | onSort("created_at")}>
+> 156 | Ngày tải lên
+ | ^^^^^^^^ This component is created during render
+ 157 |
+ 158 |
+ 159 |
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx:76:20
+ 74 | };
+ 75 |
+> 76 | const SortIcon = ({ column }: { column: MediaSortColumn }) => {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 77 | const isActive = sortBy === column;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 78 | return (
+ …
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 97 | );
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 98 | };
+ | ^^^^ The component is created during render here
+ 99 |
+ 100 | const getMimeTypeBadge = (mimeType: string) => {
+ 101 | if (mimeType.includes("image")) { react-hooks/static-components
+ 161:31 error Error: Cannot create components during render
+
+Components created during render will reset their state each time they are created. Declare components outside of render.
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx:161:31
+ 159 |
+ 160 | onSort("updated_at")}>
+> 161 | Cập nhật
+ | ^^^^^^^^ This component is created during render
+ 162 |
+ 163 |
+ 164 |
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx:76:20
+ 74 | };
+ 75 |
+> 76 | const SortIcon = ({ column }: { column: MediaSortColumn }) => {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 77 | const isActive = sortBy === column;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 78 | return (
+ …
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 97 | );
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 98 | };
+ | ^^^^ The component is created during render here
+ 99 |
+ 100 | const getMimeTypeBadge = (mimeType: string) => {
+ 101 | if (mimeType.includes("image")) { react-hooks/static-components
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/UserDetailModal.tsx
+ 39:32 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/ui/table/index.tsx
+ 58:50 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/user-profile/AccountDetails.tsx
+ 31:7 error Error: Calling setState synchronously within an effect can trigger cascading renders
+
+Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
+* Update external systems with the latest state from React.
+* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
+
+Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/user-profile/AccountDetails.tsx:31:7
+ 29 | useEffect(() => {
+ 30 | if (!isOpen) {
+> 31 | setFormValues({
+ | ^^^^^^^^^^^^^ Avoid calling setState() directly within an effect
+ 32 | old_password: "",
+ 33 | new_password: "",
+ 34 | confirm_password: "", react-hooks/set-state-in-effect
+ 71:44 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 76:19 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/user-profile/ApplicationList.tsx
+ 142:19 warning Using `
` could result in slower LCP and higher bandwidth. Consider using `` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/user-profile/Media.tsx
+ 33:5 error Error: Calling setState synchronously within an effect can trigger cascading renders
+
+Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
+* Update external systems with the latest state from React.
+* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
+
+Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/user-profile/Media.tsx:33:5
+ 31 |
+ 32 | useEffect(() => {
+> 33 | setLocalMedia(data?.data || []);
+ | ^^^^^^^^^^^^^ Avoid calling setState() directly within an effect
+ 34 | }, [data]);
+ 35 |
+ 36 | const isImageFile = (file: MediaItem) => { react-hooks/set-state-in-effect
+ 117:16 warning 'error' is defined but never used @typescript-eslint/no-unused-vars
+ 159:16 warning 'error' is defined but never used @typescript-eslint/no-unused-vars
+ 210:13 warning Using `
` could result in slower LCP and higher bandwidth. Consider using `` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/user-profile/UserInfoCard.tsx
+ 28:7 error Error: Calling setState synchronously within an effect can trigger cascading renders
+
+Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
+* Update external systems with the latest state from React.
+* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
+
+Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/user-profile/UserInfoCard.tsx:28:7
+ 26 | useEffect(() => {
+ 27 | if (data?.data?.profile) {
+> 28 | setFormData({
+ | ^^^^^^^^^^^ Avoid calling setState() directly within an effect
+ 29 | display_name: data.data.profile.display_name || "",
+ 30 | phone: data.data.profile.phone || "",
+ 31 | bio: data.data.profile.bio || "", react-hooks/set-state-in-effect
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/user-profile/UserMetaCard.tsx
+ 19:9 warning 'router' is assigned a value but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/config/config.ts
+ 25:12 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 31:31 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 39:39 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 44:20 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 82:43 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 99:43 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 110:56 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 128:23 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 142:24 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/context/ThemeContext.tsx
+ 26:5 error Error: Calling setState synchronously within an effect can trigger cascading renders
+
+Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
+* Update external systems with the latest state from React.
+* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
+
+Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/context/ThemeContext.tsx:26:5
+ 24 | const initialTheme = savedTheme || "light"; // Default to light theme
+ 25 |
+> 26 | setTheme(initialTheme);
+ | ^^^^^^^^ Avoid calling setState() directly within an effect
+ 27 | setIsInitialized(true);
+ 28 | }, []);
+ 29 | react-hooks/set-state-in-effect
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/interface/common.ts
+ 1:37 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 5:12 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 18:12 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 28:12 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/layout/AppHeader.tsx
+ 2:10 warning 'ThemeToggleButton' is defined but never used @typescript-eslint/no-unused-vars
+ 3:8 warning 'NotificationDropdown' is defined but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/service/mediaService.ts
+ 54:9 warning 'res' is assigned a value but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/store/StoreProvider.tsx
+ 13:29 error Error: Cannot access refs during render
+
+React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/store/StoreProvider.tsx:13:29
+ 11 | }) {
+ 12 | const storeRef = useRef(store);
+> 13 | return {children};
+ | ^^^^^^^^^^^^^^^^ Cannot access ref value during render
+ 14 | } react-hooks/refs
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/store/features/userSlice.ts
+ 15:26 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 32:63 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/api/config.ts
+ 6:7 warning 'GOONG_PROXY_BASE_PATH' is assigned a value but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/api/http.ts
+ 47:39 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 52:14 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 67:19 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 104:29 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 104:78 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 106:52 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/editor/Panel.tsx
+ 12:5 warning 'badge' is defined but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/editor/ReplayEffectsSidebar.tsx
+ 22:34 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 111:7 warning 'uiInputOptionValues' is assigned a value but never used @typescript-eslint/no-unused-vars
+ 165:25 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 183:25 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 212:16 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 213:16 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 221:64 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 226:49 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 316:20 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 761:11 warning 'safeYear' is assigned a value but never used @typescript-eslint/no-unused-vars
+ 822:11 warning 'firstId' is assigned a value but never used @typescript-eslint/no-unused-vars
+ 1167:27 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 1180:41 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 1190:13 error Error: Calling setState synchronously within an effect can trigger cascading renders
+
+Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
+* Update external systems with the latest state from React.
+* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
+
+Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/editor/ReplayEffectsSidebar.tsx:1190:13
+ 1188 | if (actions.length > 0) {
+ 1189 | const first = actions[0];
+> 1190 | setComposerFunctionName(first.function_name);
+ | ^^^^^^^^^^^^^^^^^^^^^^^ Avoid calling setState() directly within an effect
+ 1191 | const def = definitions[first.function_name];
+ 1192 | if (def) {
+ 1193 | setComposerDraftValues(def.deserialize(first.params)); react-hooks/set-state-in-effect
+ 1354:27 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 1380:53 warning Compilation Skipped: `this` is not supported syntax
+
+React Compiler does not support compiling functions that use `this`.
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/editor/ReplayEffectsSidebar.tsx:1380:53
+ 1378 | ],
+ 1379 | handlers: {
+> 1380 | link: function (this: { quill?: any }) {
+ | ^^^^^^^^^^^^^^^^^^^^^ `this` was used here
+ 1381 | onLinkClick?.(this?.quill);
+ 1382 | },
+ 1383 | }, react-hooks/unsupported-syntax
+ 1380:69 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 1541:10 warning 'toInputNumber' is defined but never used @typescript-eslint/no-unused-vars
+ 1554:10 warning 'toNumberOr' is defined but never used @typescript-eslint/no-unused-vars
+ 1566:10 warning 'emptyToNull' is defined but never used @typescript-eslint/no-unused-vars
+ 1570:10 warning 'emptyToUndefined' is defined but never used @typescript-eslint/no-unused-vars
+ 1574:10 warning 'compactTrailingUndefined' is defined but never used @typescript-eslint/no-unused-vars
+ 1582:10 warning 'normalizeSelectValue' is defined but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/editor/ReplayPreviewLayerPanel.tsx
+ 5:10 warning 'GEO_TYPE_KEYS' is defined but never used @typescript-eslint/no-unused-vars
+ 168:7 warning 'buttonClassName' is assigned a value but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/editor/ReplayPreviewOverlay.tsx
+ 112:25 warning Using `
` could result in slower LCP and higher bandwidth. Consider using `` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/map/mapUtils.ts
+ 321:47 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 487:46 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 1065:44 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 1179:36 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/map/useMapInstance.ts
+ 65:34 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/map/useMapLayers.ts
+ 12:10 warning 'FeatureCollection' is defined but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/map/useMapSync.ts
+ 5:10 warning 'EMPTY_FEATURE_COLLECTION' is defined but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/preview/PinnedWikiPopup.tsx
+ 24:5 warning 'featureId' is defined but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/preview/PreviewLayout.tsx
+ 4:8 warning 'EditorMap' is defined but never used @typescript-eslint/no-unused-vars
+ 10:10 warning 'useReplayPreview' is defined but never used @typescript-eslint/no-unused-vars
+ 20:10 warning 'persistBackgroundLayerVisibility' is defined but never used @typescript-eslint/no-unused-vars
+ 28:10 warning 'deepClone' is defined but never used @typescript-eslint/no-unused-vars
+ 55:20 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 81:23 error Component definition is missing display name react/display-name
+ 81:34 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 90:5 warning 'entityWikiLinks' is defined but never used @typescript-eslint/no-unused-vars
+ 96:5 warning 'onViewModeChange' is defined but never used @typescript-eslint/no-unused-vars
+ 98:5 warning 'isGlobalLoading' is assigned a value but never used @typescript-eslint/no-unused-vars
+ 101:5 warning 'selectedStageId' is assigned a value but never used @typescript-eslint/no-unused-vars
+ 102:5 warning 'selectedStepIndex' is assigned a value but never used @typescript-eslint/no-unused-vars
+ 106:5 warning 'mapHandleRef' is defined but never used @typescript-eslint/no-unused-vars
+ 110:5 warning 'previewEntityFocusToken' is defined but never used @typescript-eslint/no-unused-vars
+ 114:5 warning 'isLargeScreen' is defined but never used @typescript-eslint/no-unused-vars
+ 115:5 warning 'setIsLargeScreen' is defined but never used @typescript-eslint/no-unused-vars
+ 143:9 error Error: Calling setState synchronously within an effect can trigger cascading renders
+
+Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
+* Update external systems with the latest state from React.
+* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
+
+Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/preview/PreviewLayout.tsx:143:9
+ 141 | useEffect(() => {
+ 142 | setPreviewWikiCache({});
+> 143 | setPreviewWikiError(null);
+ | ^^^^^^^^^^^^^^^^^^^ Avoid calling setState() directly within an effect
+ 144 | setIsPreviewWikiLoading(false);
+ 145 | setPreviewPinnedWikiPopupAnchor(null);
+ 146 | setPreviewActiveEntityId(null); react-hooks/set-state-in-effect
+ 209:13 error Error: Calling setState synchronously within an effect can trigger cascading renders
+
+Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
+* Update external systems with the latest state from React.
+* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
+
+Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/preview/PreviewLayout.tsx:209:13
+ 207 | useEffect(() => {
+ 208 | if (!mode || !replayPreviewSidebarOpen) {
+> 209 | setPreviewWikiError(null);
+ | ^^^^^^^^^^^^^^^^^^^ Avoid calling setState() directly within an effect
+ 210 | setIsPreviewWikiLoading(false);
+ 211 | return;
+ 212 | } react-hooks/set-state-in-effect
+ 261:8 warning React Hook useEffect has a missing dependency: 'setPreviewWikiCache'. Either include it or remove the dependency array. If 'setPreviewWikiCache' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps
+ 305:11 warning 'replayPreviewActiveEntityGeometries' is assigned a value but never used @typescript-eslint/no-unused-vars
+ 315:51 error Compilation Skipped: Existing memoization could not be preserved
+
+React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `setPreviewActiveEntityId`, but the source dependencies were [openReplayPreviewWikiPanelById, previewRelations.entitiesById, previewRelations.entityWikisById, setSelectedFeatureIds]. Inferred different dependency than source.
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/preview/PreviewLayout.tsx:315:51
+ 313 |
+ 314 | // Handle replay preview entity selection
+> 315 | const selectReplayPreviewEntity = useCallback((
+ | ^
+> 316 | entityId: string,
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+> 317 | options?: {
+ …
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+> 352 | }
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+> 353 | }, [
+ | ^^^^^^ Could not preserve existing manual memoization
+ 354 | openReplayPreviewWikiPanelById,
+ 355 | previewRelations.entitiesById,
+ 356 | previewRelations.entityWikisById, react-hooks/preserve-manual-memoization
+ 315:51 error Compilation Skipped: Existing memoization could not be preserved
+
+React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `setPreviewEntityFocusToken`, but the source dependencies were [openReplayPreviewWikiPanelById, previewRelations.entitiesById, previewRelations.entityWikisById, setSelectedFeatureIds]. Inferred different dependency than source.
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/preview/PreviewLayout.tsx:315:51
+ 313 |
+ 314 | // Handle replay preview entity selection
+> 315 | const selectReplayPreviewEntity = useCallback((
+ | ^
+> 316 | entityId: string,
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+> 317 | options?: {
+ …
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+> 352 | }
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+> 353 | }, [
+ | ^^^^^^ Could not preserve existing manual memoization
+ 354 | openReplayPreviewWikiPanelById,
+ 355 | previewRelations.entitiesById,
+ 356 | previewRelations.entityWikisById, react-hooks/preserve-manual-memoization
+ 353:8 warning React Hook useCallback has missing dependencies: 'setPreviewActiveEntityId' and 'setPreviewEntityFocusToken'. Either include them or remove the dependency array. If 'setPreviewActiveEntityId' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps
+ 361:51 error Compilation Skipped: Existing memoization could not be preserved
+
+React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `setPreviewActiveEntityId`, but the source dependencies were [closeReplayPreviewWikiPanel, setSelectedFeatureIds]. Inferred different dependency than source.
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/preview/PreviewLayout.tsx:361:51
+ 359 |
+ 360 | // Handle close sidebar
+> 361 | const closeReplayPreviewSidebar = useCallback(() => {
+ | ^^^^^^^
+> 362 | closeReplayPreviewWikiPanel();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 363 | setPreviewActiveEntityId(null);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 364 | setIsPreviewEntitySidebarOpen(false);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 365 | setPreviewWikiError(null);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 366 | setPreviewLinkEntityPopup(null);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 367 | setSelectedFeatureIds([]);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 368 | }, [closeReplayPreviewWikiPanel, setSelectedFeatureIds]);
+ | ^^^^^^ Could not preserve existing manual memoization
+ 369 |
+ 370 | // Play selected battle replay
+ 371 | const handlePlaySelectedReplay = useCallback((replay: BattleReplay) => { react-hooks/preserve-manual-memoization
+ 368:8 warning React Hook useCallback has a missing dependency: 'setPreviewActiveEntityId'. Either include it or remove the dependency array. If 'setPreviewActiveEntityId' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps
+ 517:11 warning 'mapRenderDraft' is assigned a value but never used @typescript-eslint/no-unused-vars
+ 575:11 warning 'mapLabelContextDraft' is assigned a value but never used @typescript-eslint/no-unused-vars
+ 580:11 warning 'viewerPreviewSelectedReplay' is assigned a value but never used @typescript-eslint/no-unused-vars
+ 600:55 error Compilation Skipped: Existing memoization could not be preserved
+
+React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `setPreviewEntityFocusToken`, but the source dependencies were [previewRelations.geometryEntityIds, selectReplayPreviewEntity]. Inferred different dependency than source.
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/preview/PreviewLayout.tsx:600:55
+ 598 | }, []);
+ 599 |
+> 600 | const handleFocusHistoricalGeometry = useCallback((payload: HistoricalGeometryFocusPayload) => {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 601 | setFocusedPresentPlace(null);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 602 | setSelectedFeatureIds([payload.geometry.id]);
+ …
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 612 | }
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> 613 | }, [previewRelations.geometryEntityIds, selectReplayPreviewEntity]);
+ | ^^^^^^ Could not preserve existing manual memoization
+ 614 |
+ 615 | const effectiveGeometryVisibility = useMemo(() => {
+ 616 | return geometryVisibility; react-hooks/preserve-manual-memoization
+ 613:8 warning React Hook useCallback has a missing dependency: 'setPreviewEntityFocusToken'. Either include it or remove the dependency array. If 'setPreviewEntityFocusToken' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps
+ 619:11 warning 'handleSetMode' is assigned a value but never used @typescript-eslint/no-unused-vars
+ 884:10 warning 'buildPreviewRelationIndex' is defined but never used @typescript-eslint/no-unused-vars
+ 909:11 warning 'wikiMap' is assigned a value but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/lib/editor/snapshot/editorSnapshot.ts
+ 1229:46 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 1230:44 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 1231:49 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 1232:53 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 1281:27 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/lib/map/styles/shared/pointStyle.ts
+ 26:6 warning 'PointIconVariant' is defined but never used @typescript-eslint/no-unused-vars
+ 45:7 warning 'DRAFT_FILL' is assigned a value but never used @typescript-eslint/no-unused-vars
+ 46:7 warning 'DRAFT_RIM' is assigned a value but never used @typescript-eslint/no-unused-vars
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/lib/replay/replayDispatcher.ts
+ 4:5 warning 'GeoFunctionName' is defined but never used @typescript-eslint/no-unused-vars
+ 5:5 warning 'MapFunctionName' is defined but never used @typescript-eslint/no-unused-vars
+ 6:5 warning 'NarrativeFunctionName' is defined but never used @typescript-eslint/no-unused-vars
+ 8:5 warning 'UIOptionName' is defined but never used @typescript-eslint/no-unused-vars
+ 22:14 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 50:29 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 196:40 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+ 196:59 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
+
+/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/lib/replay/useReplayPreview.ts
+ 258:8 warning React Hook useEffect has a missing dependency: 'setDialogWithRef'. Either include it or remove the dependency array react-hooks/exhaustive-deps
+
+✖ 204 problems (99 errors, 105 warnings)
+
diff --git a/src/app/editor/[id]/page.tsx b/src/app/editor/[id]/page.tsx
index a912915..08522e0 100644
--- a/src/app/editor/[id]/page.tsx
+++ b/src/app/editor/[id]/page.tsx
@@ -3,27 +3,23 @@
import { useCallback, useEffect, useMemo, useRef, useState, type SetStateAction } from "react";
import { useParams, useRouter } from "next/navigation";
import { useShallow } from "zustand/react/shallow";
-import Map, { type MapFeaturePayload, type MapHandle } from "@/uhm/components/Map";
+import Map, { type MapHandle } from "@/uhm/components/Map";
import Editor from "@/uhm/components/Editor";
import BackgroundLayersPanel from "@/uhm/components/editor/BackgroundLayersPanel";
import TimelineBar from "@/uhm/components/ui/TimelineBar";
import SelectedGeometryPanel from "@/uhm/components/editor/SelectedGeometryPanel";
import ReplayTimelineSidebar from "@/uhm/components/editor/ReplayTimelineSidebar";
import ReplayEffectsSidebar from "@/uhm/components/editor/ReplayEffectsSidebar";
-import ReplayPreviewOverlay from "@/uhm/components/editor/ReplayPreviewOverlay";
-import ReplayPreviewLayerPanel from "@/uhm/components/editor/ReplayPreviewLayerPanel";
import PreviewLayout from "@/uhm/components/preview/PreviewLayout";
-import PublicWikiSidebar from "@/uhm/components/wiki/PublicWikiSidebar";
import WikiSidebarPanel from "@/uhm/components/wiki/WikiSidebarPanel";
import ProjectEntityRefsPanel from "@/uhm/components/editor/ProjectEntityRefsPanel";
import EntityWikiBindingsPanel from "@/uhm/components/editor/EntityWikiBindingsPanel";
import GeometryBindingPanel from "@/uhm/components/editor/GeometryBindingPanel";
import ImageOverlayPanel from "@/uhm/components/editor/ImageOverlayPanel";
-import PresentPlaceSearch, { type HistoricalGeometryFocusPayload, type PresentPlaceSelection } from "@/uhm/components/editor/PresentPlaceSearch";
import { Entity, fetchEntities, searchEntitiesByName } from "@/uhm/api/entities";
import { ApiError } from "@/uhm/api/http";
import { fetchCurrentUser } from "@/uhm/api/auth";
-import { fetchWikiById, searchWikisByTitle, type Wiki } from "@/uhm/api/wikis";
+import { searchWikisByTitle, type Wiki } from "@/uhm/api/wikis";
import { searchGeometriesByEntityName, fetchGeometriesByBBox, type EntityGeometriesSearchItem, type EntityGeometrySearchGeo } from "@/uhm/api/geometries";
import { WORLD_BBOX } from "@/uhm/lib/map/geo/constants";
import {
@@ -50,7 +46,6 @@ import { buildFeatureEntityPatch } from "@/uhm/lib/editor/entity/entityBinding";
import { newId } from "@/uhm/lib/utils/id";
import {
loadBackgroundLayerVisibilityFromStorage,
- persistBackgroundLayerVisibility,
} from "@/uhm/lib/editor/background/backgroundVisibilityStorage";
import { deepClone } from "@/uhm/lib/editor/draft/draftDiff";
import { useProjectCommands } from "@/uhm/lib/editor/project/useProjectCommands";
@@ -86,7 +81,6 @@ import {
normalizeReplaysForCompare,
normalizeWikisForCompare,
} from "@/uhm/lib/editor/editorPageUtils";
-import { fitMapToFeatureCollection } from "@/uhm/components/map/mapUtils";
const CURRENT_YEAR = new Date().getUTCFullYear();
const DEFAULT_EDITOR_USER_ID = "local-editor";
@@ -116,12 +110,6 @@ type PreviewRelationIndex = {
wikiBySlug: Record;
};
-type PreviewLinkEntityPopupState = {
- slug: string;
- entities: Entity[];
- top: number;
- left: number;
-};
export default function Page() {
return (
@@ -542,27 +530,6 @@ function EditorPageContent() {
setTimelineDraftYear(clampYearToFixedRange(Math.trunc(nextYear)));
}, [setTimelineDraftYear]);
- const handleViewerPreviewTimelineYearChange = useCallback((nextYear: number) => {
- setPreviewSession((prev) =>
- prev
- ? {
- ...prev,
- timelineYear: clampYearToFixedRange(Math.trunc(nextYear)),
- }
- : prev
- );
- }, []);
-
- const handleViewerPreviewTimelineFilterChange = useCallback((enabled: boolean) => {
- setPreviewSession((prev) =>
- prev
- ? {
- ...prev,
- timelineFilterEnabled: enabled,
- }
- : prev
- );
- }, []);
// Preview specific UI states
const [previewActiveEntityId, setPreviewActiveEntityId] = useState(null);
@@ -613,9 +580,7 @@ function EditorPageContent() {
hiddenGeometryIds: replayPreviewHiddenGeometryIds,
timelineYear: replayPreviewTimelineYear,
timelineFilterEnabled: replayPreviewTimelineFilterEnabled,
- activeCursor: replayPreviewActiveCursor,
activeWikiId: replayPreviewActiveWikiId,
- sidebarOpen: replayPreviewSidebarOpen,
} = replayPreview;
// Draft hiển thị trong preview có thể ẩn bớt geometry theo action replay.
@@ -661,12 +626,6 @@ function EditorPageContent() {
return activeWikiEntityIds[0] || previewActiveEntityId;
}, [previewActiveEntityId, previewRelations.wikiEntityIdsById, replayPreviewActiveWikiId]);
- const replayPreviewActiveEntity = useMemo(() => {
- return replayPreviewActiveEntityId
- ? previewRelations.entitiesById[replayPreviewActiveEntityId] || null
- : null;
- }, [replayPreviewActiveEntityId, previewRelations.entitiesById]);
-
const replayPreviewActiveEntityGeometries = useMemo(() => {
return replayPreviewActiveEntityId
? previewRelations.entityGeometriesById[replayPreviewActiveEntityId] || EMPTY_FEATURE_COLLECTION
@@ -3044,25 +3003,6 @@ function snapshotWikiToWiki(snapshot: WikiSnapshot, wikiCache: Record]*>([\s\S]*?)<\/blockquote>/i);
- const rawText = blockquoteMatch?.[1]?.trim() || "";
- if (!rawText) return "";
-
- return rawText
- .replace(/<[^>]*>/g, "")
- .replace(/ /gi, " ")
- .replace(/\u00a0/g, " ")
- .replace(/&/gi, "&")
- .replace(/</gi, "<")
- .replace(/>/gi, ">")
- .replace(/"/gi, '"')
- .replace(/'/g, "'")
- .replace(/\s+/g, " ")
- .trim();
-}
function pushUniqueString(target: Record, key: string, value: string) {
if (!target[key]) {
@@ -3080,20 +3020,6 @@ function normalizeRelationArrays(target: Record) {
}
}
-function computeFixedPopupPosition(rect: DOMRect, width: number, height: number) {
- const margin = 12;
- const viewportWidth = typeof window !== "undefined" ? window.innerWidth : 1440;
- const viewportHeight = typeof window !== "undefined" ? window.innerHeight : 900;
- const preferredLeft = rect.right + margin;
- const maxLeft = Math.max(margin, viewportWidth - width - margin);
- const left = Math.min(preferredLeft, maxLeft);
-
- const preferredTop = rect.top;
- const maxTop = Math.max(margin, viewportHeight - height - margin);
- const top = Math.max(margin, Math.min(preferredTop, maxTop));
-
- return { top, left };
-}
function isTypingTarget(target: EventTarget | null): boolean {
if (!(target instanceof HTMLElement)) return false;
diff --git a/src/uhm/components/editor/Panel.tsx b/src/uhm/components/editor/Panel.tsx
index 3909789..8254d50 100644
--- a/src/uhm/components/editor/Panel.tsx
+++ b/src/uhm/components/editor/Panel.tsx
@@ -41,7 +41,23 @@ export function Panel({
gap: 8,
}}
>
- {title}
+
+ {title}
+ {badge && (
+
+ {badge}
+
+ )}
+
= [
{ label: "LngLat", value: "center" },
{ label: "Zoom", value: "zoom" },
@@ -758,8 +753,6 @@ function MapFunctionShortcutPanel({
currentTimelineYear: number;
onAppendActions: (actions: ReplayAction[], label: string) => void;
}) {
- const safeYear = Math.trunc(currentTimelineYear);
-
return (
@@ -819,7 +812,6 @@ function GeoFunctionShortcutPanel({
}) {
const selectedIds = selectedGeometries.map((item) => item.id);
const selectedCount = selectedIds.length;
- const firstId = selectedIds[0] || "";
const hasSelection = selectedCount > 0;
return (
@@ -1538,24 +1530,6 @@ function asString(value: unknown) {
return typeof value === "string" ? value : value == null ? "" : String(value);
}
-function toInputNumber(value: unknown, fallback: string) {
- if (typeof value === "number" && Number.isFinite(value)) return String(value);
- if (typeof value === "string" && value.trim().length) return value;
- return fallback;
-}
-
-function toOptionalNumber(value: unknown) {
- const raw = asString(value).trim();
- if (!raw.length) return undefined;
- const parsed = Number(raw);
- return Number.isFinite(parsed) ? parsed : undefined;
-}
-
-function toNumberOr(value: unknown, fallback: number) {
- const parsed = toOptionalNumber(value);
- return parsed == null ? fallback : parsed;
-}
-
function toStringArray(value: unknown): string[] {
if (!Array.isArray(value)) return [];
return value
@@ -1563,26 +1537,6 @@ function toStringArray(value: unknown): string[] {
.filter((item) => item.length > 0);
}
-function emptyToNull(value: string) {
- return value.trim().length ? value : null;
-}
-
-function emptyToUndefined(value: string) {
- return value.trim().length ? value : undefined;
-}
-
-function compactTrailingUndefined(values: unknown[]) {
- const next = [...values];
- while (next.length > 0 && next[next.length - 1] === undefined) {
- next.pop();
- }
- return next;
-}
-
-function normalizeSelectValue(value: string, fallback: string) {
- return value.trim().length ? value : fallback;
-}
-
function buildUiEffectsDraftState(actions: ReplayAction
[]): UiEffectsDraftState {
const selected = buildEmptyUiOptionSelection();
const visible = buildDefaultUiVisibilityState();
diff --git a/src/uhm/components/preview/PreviewLayout.tsx b/src/uhm/components/preview/PreviewLayout.tsx
index e942f15..4d9556a 100644
--- a/src/uhm/components/preview/PreviewLayout.tsx
+++ b/src/uhm/components/preview/PreviewLayout.tsx
@@ -1,32 +1,23 @@
"use client";
import { useCallback, useEffect, useMemo, useRef, useState, forwardRef, useImperativeHandle } from "react";
-import EditorMap, { type MapFeaturePayload, type MapHandle } from "@/uhm/components/Map";
+import { type MapFeaturePayload, type MapHandle } from "@/uhm/components/Map";
import PresentPlaceSearch, { type HistoricalGeometryFocusPayload, type PresentPlaceSelection } from "@/uhm/components/editor/PresentPlaceSearch";
import ReplayPreviewOverlay from "@/uhm/components/editor/ReplayPreviewOverlay";
import ReplayPreviewLayerPanel from "@/uhm/components/editor/ReplayPreviewLayerPanel";
import PublicWikiSidebar from "@/uhm/components/wiki/PublicWikiSidebar";
import TimelineBar from "@/uhm/components/ui/TimelineBar";
-import { useReplayPreview } from "@/uhm/lib/replay/useReplayPreview";
import RelatedEntityPopup from "./RelatedEntityPopup";
import PinnedWikiPopup from "./PinnedWikiPopup";
-import { fetchWikiById, type Wiki } from "@/uhm/api/wikis";
+import { type Wiki } from "@/uhm/api/wikis";
import type { Entity } from "@/uhm/api/entities";
-import type { Feature, FeatureCollection } from "@/uhm/types/geo";
-import type { BattleReplay, EntityWikiLinkSnapshot } from "@/uhm/types/projects";
+import type { FeatureCollection } from "@/uhm/types/geo";
+import type { BattleReplay } from "@/uhm/types/projects";
import type { WikiSnapshot } from "@/uhm/types/wiki";
import { type BackgroundLayerVisibility } from "@/uhm/lib/map/styles/backgroundLayers";
-import { persistBackgroundLayerVisibility } from "@/uhm/lib/editor/background/backgroundVisibilityStorage";
import { EMPTY_FEATURE_COLLECTION } from "@/uhm/lib/map/geo/constants";
-import {
- clampNumber,
- isFeatureVisibleAtYear,
-} from "@/uhm/lib/editor/editorPageUtils";
import { normalizeFeatureEntityIds } from "@/uhm/lib/editor/snapshot/editorSnapshot";
-import { normalizeTimelineYearValue } from "@/uhm/lib/utils/timeline";
-import { deepClone } from "@/uhm/lib/editor/draft/draftDiff";
-import type { EditorMode } from "@/uhm/lib/editor/session/sessionTypes";
type Props = {
projectId: string;
@@ -37,32 +28,20 @@ type Props = {
replays: BattleReplay[];
entities: Entity[];
wikis: WikiSnapshot[];
- entityWikiLinks: EntityWikiLinkSnapshot[];
backgroundVisibility: BackgroundLayerVisibility;
onBackgroundVisibilityChange: (vis: BackgroundLayerVisibility) => void;
geometryVisibility: Record;
onGeometryVisibilityChange: (vis: Record) => void;
- viewMode: "local" | "global";
- onViewModeChange?: (mode: "local" | "global") => void;
- globalGeometries?: FeatureCollection;
- isGlobalLoading?: boolean;
- baseline?: FeatureCollection;
activeReplay?: BattleReplay | null;
- selectedStageId?: number | null;
- selectedStepIndex?: number | null;
autoplayMode?: "start" | "selection" | null;
replayPreview: any;
- mapHandleRef: React.RefObject;
previewRelations: PreviewRelationIndex;
previewActiveEntityId: string | null;
setPreviewActiveEntityId: (id: string | null) => void;
- previewEntityFocusToken: number;
setPreviewEntityFocusToken: React.Dispatch>;
previewSidebarWidth: number;
setPreviewSidebarWidth: React.Dispatch>;
- isLargeScreen: boolean;
- setIsLargeScreen: (isLarge: boolean) => void;
previewWikiCache: Record;
setPreviewWikiCache: React.Dispatch>>;
};
@@ -83,36 +62,21 @@ const PreviewLayout = forwardRef(({
mode,
onModeChange,
onExitPreview,
- draft,
- replays,
- entities,
wikis,
- entityWikiLinks,
backgroundVisibility,
onBackgroundVisibilityChange,
geometryVisibility,
onGeometryVisibilityChange,
- viewMode,
- onViewModeChange,
- globalGeometries = EMPTY_FEATURE_COLLECTION,
- isGlobalLoading = false,
- baseline = EMPTY_FEATURE_COLLECTION,
activeReplay,
- selectedStageId = null,
- selectedStepIndex = null,
autoplayMode = null,
replayPreview,
- mapHandleRef,
previewRelations,
previewActiveEntityId,
setPreviewActiveEntityId,
- previewEntityFocusToken,
setPreviewEntityFocusToken,
previewSidebarWidth,
setPreviewSidebarWidth,
- isLargeScreen,
- setIsLargeScreen,
previewWikiCache,
setPreviewWikiCache,
}: Props, ref) => {
@@ -167,9 +131,7 @@ const PreviewLayout = forwardRef(({
}, [autoplayMode, isReplayPreviewMode, currentActiveReplay, replayPreview]);
const {
- hiddenGeometryIds: replayPreviewHiddenGeometryIds,
timelineYear: replayPreviewTimelineYear,
- timelineFilterEnabled: replayPreviewTimelineFilterEnabled,
resetPreview: resetReplayPreview,
playbackSpeed: replayPreviewPlaybackSpeed,
activeCursor: replayPreviewActiveCursor,
@@ -181,7 +143,6 @@ const PreviewLayout = forwardRef(({
// Timeline bar parameters
const activeTimelineYear = isReplayPreviewMode ? replayPreviewTimelineYear : replayPreviewTimelineYear;
- const activeTimelineFilterEnabled = isReplayPreviewMode ? replayPreviewTimelineFilterEnabled : true;
// Timeline bar visibility
const timelineBarVisible = !isReplayPreviewMode || replayPreview.timelineVisible;
@@ -302,15 +263,9 @@ const PreviewLayout = forwardRef(({
? previewRelations.entitiesById[replayPreviewActiveEntityId] || null
: null;
- const replayPreviewActiveEntityGeometries = replayPreviewActiveEntityId
- ? previewRelations.entityGeometriesById[replayPreviewActiveEntityId] || EMPTY_FEATURE_COLLECTION
- : EMPTY_FEATURE_COLLECTION;
const isReplayPreviewWikiSidebarOpen = mode && (replayPreviewSidebarOpen || isPreviewEntitySidebarOpen);
- // Selected feature ids
- const [selectedFeatureIds, setSelectedFeatureIds] = useState<(string | number)[]>([]);
-
// Handle replay preview entity selection
const selectReplayPreviewEntity = useCallback((
entityId: string,
@@ -344,9 +299,6 @@ const PreviewLayout = forwardRef(({
if (options?.focusMap === true) {
setPreviewEntityFocusToken((prev) => (prev ?? 0) + 1);
}
- if (options?.selectGeometry && options.sourceFeatureId != null) {
- setSelectedFeatureIds([options.sourceFeatureId]);
- }
if (nextWiki) {
openReplayPreviewWikiPanelById(nextWiki.id);
}
@@ -354,7 +306,8 @@ const PreviewLayout = forwardRef(({
openReplayPreviewWikiPanelById,
previewRelations.entitiesById,
previewRelations.entityWikisById,
- setSelectedFeatureIds,
+ setPreviewActiveEntityId,
+ setPreviewEntityFocusToken,
]);
// Handle close sidebar
@@ -364,8 +317,7 @@ const PreviewLayout = forwardRef(({
setIsPreviewEntitySidebarOpen(false);
setPreviewWikiError(null);
setPreviewLinkEntityPopup(null);
- setSelectedFeatureIds([]);
- }, [closeReplayPreviewWikiPanel, setSelectedFeatureIds]);
+ }, [closeReplayPreviewWikiPanel, setPreviewActiveEntityId]);
// Play selected battle replay
const handlePlaySelectedReplay = useCallback((replay: BattleReplay) => {
@@ -499,94 +451,7 @@ const PreviewLayout = forwardRef(({
wikis,
]);
- // Render Draft geometries builder
- const replayPreviewDraft = useMemo(() => {
- const sourceDraft = draft;
- if (!isReplayPreviewMode || replayPreviewHiddenGeometryIds.length === 0) {
- return sourceDraft;
- }
- const hiddenIds = new Set(replayPreviewHiddenGeometryIds);
- return {
- ...sourceDraft,
- features: sourceDraft.features.filter(
- (feature) => !hiddenIds.has(String(feature.properties.id))
- ),
- };
- }, [isReplayPreviewMode, draft, replayPreviewHiddenGeometryIds]);
- const mapRenderDraft = useMemo(() => {
- if (isReplayPreviewMode) {
- return replayPreviewDraft;
- }
-
- const sourceDraft = draft;
- if (!activeTimelineFilterEnabled) {
- return sourceDraft;
- }
-
- return {
- ...sourceDraft,
- features: sourceDraft.features.filter((feature) =>
- isFeatureVisibleAtYear(feature, activeTimelineYear)
- ),
- };
- }, [
- activeTimelineFilterEnabled,
- activeTimelineYear,
- draft,
- isReplayPreviewMode,
- replayPreviewDraft,
- ]);
-
- // Build label context
- const labelContextBaseDraft = useMemo(() => {
- if (viewMode === "local") {
- return draft;
- }
-
- const localFeatureIds = new Set();
- for (const f of draft.features) {
- if (f.properties?.id != null) {
- localFeatureIds.add(String(f.properties.id));
- }
- }
- if (baseline && baseline.features) {
- for (const f of baseline.features) {
- if (f.properties?.id != null) {
- localFeatureIds.add(String(f.properties.id));
- }
- }
- }
-
- const mergedFeatures = [...draft.features];
- for (const globalFeature of globalGeometries.features) {
- const globalId = globalFeature.properties?.id != null ? String(globalFeature.properties.id) : null;
- if (globalId === null || !localFeatureIds.has(globalId)) {
- mergedFeatures.push(globalFeature);
- }
- }
-
- return {
- ...draft,
- features: mergedFeatures,
- };
- }, [viewMode, draft, baseline, globalGeometries.features]);
-
- const mapLabelContextDraft = useMemo(() => {
- return buildEntityLabelContextDraft(labelContextBaseDraft, entities);
- }, [entities, labelContextBaseDraft]);
-
- // Replay matching the selected feature
- const viewerPreviewSelectedReplay = useMemo(() => {
- if (isReplayPreviewMode || !selectedFeatureIds.length) return null;
- const selectedGeometryId = String(selectedFeatureIds[0] ?? "").trim();
- if (!selectedGeometryId.length) return null;
- return replays.find(
- (r) =>
- String(r?.geometry_id || "").trim() === selectedGeometryId &&
- hasPlayableReplaySteps(r)
- ) || null;
- }, [isReplayPreviewMode, replays, selectedFeatureIds]);
// Search and focus place
const handleFocusPresentPlace = useCallback((place: PresentPlaceSelection) => {
@@ -599,7 +464,6 @@ const PreviewLayout = forwardRef(({
const handleFocusHistoricalGeometry = useCallback((payload: HistoricalGeometryFocusPayload) => {
setFocusedPresentPlace(null);
- setSelectedFeatureIds([payload.geometry.id]);
setPreviewEntityFocusToken((prev) => (prev ?? 0) + 1);
const linkedEntityIds = previewRelations.geometryEntityIds[String(payload.geometry.id)] || [];
@@ -610,17 +474,13 @@ const PreviewLayout = forwardRef(({
selectGeometry: false,
});
}
- }, [previewRelations.geometryEntityIds, selectReplayPreviewEntity]);
+ }, [previewRelations.geometryEntityIds, selectReplayPreviewEntity, setPreviewEntityFocusToken]);
const effectiveGeometryVisibility = useMemo(() => {
return geometryVisibility;
}, [geometryVisibility]);
- const handleSetMode = useCallback((m: EditorMode) => {
- if (m === "preview" || m === "replay_preview") {
- onModeChange(m);
- }
- }, [onModeChange]);
+
// Popup PinnedWikiPopup rows
const previewPinnedWikiPopupRows = useMemo(() => {
@@ -810,26 +670,6 @@ export default PreviewLayout;
// Helper functions
// ==========================================
-function snapshotWikiToWiki(snapshot: WikiSnapshot, wikiCache: Record, projectId: string): Wiki {
- if (typeof snapshot.doc === "string") {
- return {
- id: snapshot.id,
- project_id: projectId,
- title: snapshot.title,
- slug: snapshot.slug ?? null,
- content: snapshot.doc || "",
- };
- }
-
- return wikiCache[snapshot.id] || {
- id: snapshot.id,
- project_id: projectId,
- title: snapshot.title,
- slug: snapshot.slug ?? null,
- content: "",
- };
-}
-
function extractWikiBlockquoteText(content: string | null | undefined): string {
if (!content) return "";
@@ -850,22 +690,6 @@ function extractWikiBlockquoteText(content: string | null | undefined): string {
.trim();
}
-function pushUniqueString(target: Record, key: string, value: string) {
- if (!target[key]) {
- target[key] = [value];
- return;
- }
- if (!target[key].includes(value)) {
- target[key].push(value);
- }
-}
-
-function normalizeRelationArrays(target: Record) {
- for (const key of Object.keys(target)) {
- target[key] = Array.from(new Set(target[key]));
- }
-}
-
function computeFixedPopupPosition(rect: DOMRect, width: number, height: number) {
const margin = 12;
const viewportWidth = typeof window !== "undefined" ? window.innerWidth : 1440;
@@ -880,127 +704,3 @@ function computeFixedPopupPosition(rect: DOMRect, width: number, height: number)
return { top, left };
}
-
-function buildPreviewRelationIndex(options: {
- draft: FeatureCollection;
- entities: Entity[];
- wikis: WikiSnapshot[];
- entityWikiLinks: EntityWikiLinkSnapshot[];
- wikiCache: Record;
- projectId: string;
-}): PreviewRelationIndex {
- const next: PreviewRelationIndex = {
- entitiesById: {},
- entityGeometriesById: {},
- entityWikisById: {},
- geometryEntityIds: {},
- wikiEntityIdsById: {},
- wikiEntityIdsBySlug: {},
- wikiById: {},
- wikiBySlug: {},
- };
-
- for (const entity of options.entities || []) {
- const id = String(entity?.id || "").trim();
- if (!id) continue;
- next.entitiesById[id] = entity;
- }
-
- const wikiMap = new Map();
- for (const wikiSnapshot of options.wikis || []) {
- if (!wikiSnapshot || wikiSnapshot.operation === "delete") continue;
- const wiki = snapshotWikiToWiki(wikiSnapshot, options.wikiCache, options.projectId);
- if (!wiki?.id) continue;
- next.wikiById[wiki.id] = wiki;
- const slug = String(wiki.slug || "").trim();
- if (slug) next.wikiBySlug[slug] = wiki;
- }
-
- for (const feature of options.draft.features || []) {
- const geometryId = String(feature.properties.id);
- for (const entityId of normalizeFeatureEntityIds(feature)) {
- if (!next.entitiesById[entityId]) {
- next.entitiesById[entityId] = { id: entityId, name: entityId };
- }
- pushUniqueString(next.geometryEntityIds, geometryId, entityId);
- if (!next.entityGeometriesById[entityId]) {
- next.entityGeometriesById[entityId] = { type: "FeatureCollection", features: [] };
- }
- if (!next.entityGeometriesById[entityId].features.some((item) => String(item.properties.id) === geometryId)) {
- next.entityGeometriesById[entityId].features.push(feature);
- }
- }
- }
-
- for (const link of options.entityWikiLinks || []) {
- if (!link || link.operation === "delete") continue;
- const entityId = String(link.entity_id || "").trim();
- const wikiId = String(link.wiki_id || "").trim();
- const entity = next.entitiesById[entityId] || null;
- const wiki = next.wikiById[wikiId] || null;
- if (!entity || !wiki) continue;
-
- if (!next.entityWikisById[entityId]) next.entityWikisById[entityId] = [];
- if (!next.entityWikisById[entityId].some((item) => item.id === wiki.id)) {
- next.entityWikisById[entityId].push(wiki);
- }
-
- pushUniqueString(next.wikiEntityIdsById, wiki.id, entityId);
- const slug = String(wiki.slug || "").trim();
- if (slug) pushUniqueString(next.wikiEntityIdsBySlug, slug, entityId);
- }
-
- normalizeRelationArrays(next.geometryEntityIds);
- normalizeRelationArrays(next.wikiEntityIdsById);
- normalizeRelationArrays(next.wikiEntityIdsBySlug);
- return next;
-}
-
-function hasPlayableReplaySteps(replay: BattleReplay | null | undefined) {
- return Boolean(
- replay?.detail?.some((stage) => Array.isArray(stage?.steps) && stage.steps.length > 0)
- );
-}
-
-function buildEntityLabelContextDraft(draft: FeatureCollection, entities: Entity[]): FeatureCollection {
- if (!draft.features.length) return draft;
-
- const entityById = new globalThis.Map();
- for (const entity of entities || []) {
- const id = String(entity?.id || "").trim();
- if (!id) continue;
- entityById.set(id, entity);
- }
-
- return {
- ...draft,
- features: draft.features.map((feature) => {
- const entityIds = normalizeFeatureEntityIds(feature);
- if (!entityIds.length) return feature;
-
- const candidates = entityIds.map((id) => {
- const entity = entityById.get(id) || null;
- const name = String(entity?.name || id).trim();
- if (!name) return null;
- return {
- id,
- name,
- time_start: normalizeTimelineYearValue(entity?.time_start),
- time_end: normalizeTimelineYearValue(entity?.time_end),
- };
- }).filter((candidate) => candidate !== null);
-
- return {
- ...feature,
- properties: {
- ...feature.properties,
- entity_id: entityIds[0] || null,
- entity_ids: entityIds,
- entity_name: candidates[0]?.name || null,
- entity_names: candidates.map((candidate) => candidate.name),
- entity_label_candidates: candidates,
- },
- };
- }),
- };
-}
diff --git a/src/uhm/lib/map/styles/shared/pointStyle.ts b/src/uhm/lib/map/styles/shared/pointStyle.ts
index 1033de2..a4189bc 100644
--- a/src/uhm/lib/map/styles/shared/pointStyle.ts
+++ b/src/uhm/lib/map/styles/shared/pointStyle.ts
@@ -23,7 +23,6 @@ export const POINT_GEOTYPE_ICON_PATHS: Partial> =
port: "/images/mapIcon/point/port.png",
};
-type PointIconVariant = "default" | "draft";
type PointLayerOptions = {
iconScale?: number;
@@ -42,8 +41,6 @@ const TYPE_MATCH_EXPR: maplibregl.ExpressionSpecification = ["coalesce", ["get",
const SELECTED_EXPR: maplibregl.ExpressionSpecification = ["boolean", ["feature-state", "selected"], false];
const ICON_CANVAS_SIZE = 48;
-const DRAFT_FILL = "#ef4444";
-const DRAFT_RIM = "#7f1d1d";
const POINT_GEOMETRY_FILTER: maplibregl.ExpressionSpecification = [
"any",
["==", ["geometry-type"], "Point"],
diff --git a/src/uhm/lib/replay/actionCatalog.ts b/src/uhm/lib/replay/actionCatalog.ts
deleted file mode 100644
index 550f4a0..0000000
--- a/src/uhm/lib/replay/actionCatalog.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import type { UIOptionName } from "@/uhm/types/projects";
-
-export const REPLAY_UI_OPTIONS = [
- "timeline",
- "layer_panel",
- "zoom_panel",
- "wiki",
- "toast",
-] as const satisfies UIOptionName[];
-
-export function normalizeReplayUiOption(value: unknown): UIOptionName | null {
- return REPLAY_UI_OPTIONS.includes(value as UIOptionName)
- ? value as UIOptionName
- : null;
-}
diff --git a/src/uhm/lib/replay/replayDispatcher.ts b/src/uhm/lib/replay/replayDispatcher.ts
index cd9480a..b280d3b 100644
--- a/src/uhm/lib/replay/replayDispatcher.ts
+++ b/src/uhm/lib/replay/replayDispatcher.ts
@@ -1,11 +1,7 @@
import type maplibregl from "maplibre-gl";
import type { FeatureCollection } from "@/uhm/types/geo";
import type {
- GeoFunctionName,
- MapFunctionName,
- NarrativeFunctionName,
ReplayAction,
- UIOptionName,
DialogState,
} from "@/uhm/types/projects";
import { mapActions } from "./mapActions";
@@ -64,6 +60,9 @@ export const dispatchReplayAction = (
case "set_labels_visible":
mapActions.set_labels_visible(map, asBooleanValue(params[0], true));
return;
+ case "set_timeline_filter":
+ controllers.setTimelineFilterEnabled(asBooleanValue(params[0], true));
+ return;
case "fly_to_geometries":
mapActions.fly_to_geometries(
map,