From 0ff184fcdf1b13acbed2971fd25706d31562040c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Mon, 13 Jul 2026 11:39:13 +0300 Subject: [PATCH] Codex 5.6 Sol Genel Optimizasyon --- .../Seeds/LanguagesData.json | 6 + .../Seeds/TenantData.json | 10 +- ui/public/version.json | 2 +- ui/src/App.tsx | 16 +- .../devExtreme/DevExtremeBoundary.tsx | 56 +++ .../components/devExtreme/devExtremeConfig.ts | 31 ++ ui/src/components/layouts/ClassicLayout.tsx | 22 +- ui/src/components/layouts/DeckedLayout.tsx | 22 +- ui/src/components/layouts/Layouts.tsx | 61 +++- ui/src/components/layouts/ModernLayout.tsx | 22 +- ui/src/components/layouts/SimpleLayout.tsx | 22 +- .../components/layouts/StackedSideLayout.tsx | 22 +- .../template/DeferredShellActions.tsx | 72 ++++ ui/src/contexts/ComponentContext.tsx | 322 +++++++++--------- ui/src/main.tsx | 39 ++- ui/src/proxy/admin/list-form/options.ts | 4 +- ui/src/proxy/form/models.ts | 14 +- ui/src/routes/dynamicRouteLoader.tsx | 36 +- ui/src/services/notification.service.ts | 4 +- ui/src/utils/hooks/useLocale.ts | 10 - .../admin/listForm/edit/FormTabColumns.tsx | 2 +- .../views/admin/listForm/edit/FormTabEdit.tsx | 2 +- .../admin/listForm/edit/FormTabFilters.tsx | 2 +- .../admin/listForm/edit/FormTabGantt.tsx | 2 +- .../admin/listForm/edit/FormTabPager.tsx | 4 +- .../admin/listForm/edit/FormTabSelect.tsx | 4 +- .../admin/listForm/edit/FormTabState.tsx | 2 +- .../developerKit/ComponentCodeLayout.tsx | 1 + .../views/developerKit/ComponentManager.tsx | 23 +- ui/src/views/form/FormDevExpress.tsx | 4 +- ui/src/views/form/FormNew.tsx | 4 +- ui/src/views/form/types.ts | 2 +- ui/src/views/form/useFormData.tsx | 4 +- ui/src/views/list/GanttView.tsx | 4 +- ui/src/views/list/Grid.tsx | 6 +- ui/src/views/list/GridColumnData.ts | 2 +- ui/src/views/list/GridFilterDialogs.tsx | 2 +- ui/src/views/list/Pivot.tsx | 13 +- ui/src/views/list/SchedulerView.tsx | 4 +- ui/src/views/list/Tree.tsx | 6 +- ui/src/views/list/Utils.ts | 4 +- ui/src/views/list/toolbarOrder.ts | 2 +- ui/src/views/list/useFilters.tsx | 10 +- ui/src/views/list/useListFormColumns.ts | 6 +- .../views/list/useListFormCustomDataSource.ts | 12 +- ui/src/views/list/useToolbar.tsx | 4 +- ui/src/views/shared/SharedGridBoxEditor.tsx | 2 +- 47 files changed, 595 insertions(+), 331 deletions(-) create mode 100644 ui/src/components/devExtreme/DevExtremeBoundary.tsx create mode 100644 ui/src/components/devExtreme/devExtremeConfig.ts create mode 100644 ui/src/components/template/DeferredShellActions.tsx diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json index 0a0597f..45d2d9b 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json @@ -13938,6 +13938,12 @@ "en": "Api URL", "tr": "Api URL" }, + { + "resourceName": "Platform", + "key": "OpenUrl", + "en": "Open URL", + "tr": "URL'yi Aç" + }, { "resourceName": "Platform", "key": "App.Listform.ListformField.ApplicationName", diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json index 1a8f6c1..d20e247 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json @@ -350,7 +350,7 @@ { "name": "DynamicEntityComponent", "routePath": "/admin/dynamic-entity", - "code": "import React, { useEffect, useState } from \"react\";\nimport axios from \"axios\";\n\ninterface DynamicEntityComponentProps {\n title: string;\n}\n\nconst api = axios.create({\n baseURL: \"https://localhost:44344\",\n});\n\nconst DynamicEntityComponent: React.FC = ({ title }) => {\n const [data, setData] = useState>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState(null);\n\n useEffect(() => {\n const fetchData = async () => {\n setLoading(true);\n setError(null);\n\n try {\n const res = await api.get(`/api/app/crudendpoint/${title}`);\n const raw = Array.isArray(res.data) ? res.data : res.data?.items ?? [];\n\n const filtered = raw.map((item: any) => ({\n id: item.Id ?? item.id,\n name: item.Name ?? item.name,\n }));\n\n setData(filtered);\n } catch (err: any) {\n setError(err.message || \"Failed to fetch data\");\n } finally {\n setLoading(false);\n }\n };\n\n if (title) fetchData();\n }, [title]);\n\n if (loading) return
Loading...
;\n if (error) return
Error: {error}
;\n if (!data.length) return
No records found
;\n\n const headers = [\"id\", \"name\", \"actions\"];\n\n return (\n
\n \n \n \n {headers.map((key) => (\n \n {key === \"actions\" ? \"Actions\" : key}\n \n ))}\n \n \n \n {data.map((item, rowIndex) => (\n \n \n \n \n \n ))}\n \n
\n {item.id}\n \n {item.name}\n \n alert(item.name)}\n className=\"bg-blue-600 hover:bg-blue-700 dark:bg-blue-800 dark:hover:bg-blue-900 text-white text-sm px-3 py-1 rounded-lg shadow-sm transition\"\n >\n Show Name\n \n
\n
\n );\n};\n\nexport default DynamicEntityComponent;", + "code": "import React, { useEffect, useState } from \"react\";\nimport axios from \"axios\";\n\ninterface DynamicEntityComponentProps {\n title: string;\n}\n\nconst api = axios.create({\n baseURL: \"https://localhost:44344\",\n});\n\nconst DynamicEntityComponent: React.FC = ({ title }) => {\n const [data, setData] = useState>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState(null);\n\n useEffect(() => {\n const fetchData = async () => {\n setLoading(true);\n setError(null);\n\n try {\n const res = await api.get(`/api/app/crudendpoint/${title}`);\n const raw = Array.isArray(res.data) ? res.data : res.data?.items ?? [];\n\n const filtered = raw.map((item: any) => ({\n id: item.Id ?? item.id,\n name: item.Name ?? item.name,\n }));\n\n setData(filtered);\n } catch (err: any) {\n setError(err.message || \"Failed to fetch data\");\n } finally {\n setLoading(false);\n }\n };\n\n if (title) fetchData();\n }, [title]);\n\n if (loading) return
Loading...
;\n if (error) return
Error: {error}
;\n if (!data.length) return
No records found
;\n\n const headers = [\"id\", \"name\", \"actions\"];\n\n return (\n
\n \n \n \n {headers.map((key) => (\n \n {key === \"actions\" ? \"Actions\" : key}\n \n ))}\n \n \n \n {data.map((item, rowIndex) => (\n \n \n \n \n \n ))}\n \n
\n {item.id}\n \n {item.name}\n \n alert(item.name)}\n shape=\"round\"\n size=\"xs\"\n variant=\"solid\"\n id=\"c_mrix4c1a_qrybhk\"\n >\n Show Name\n \n
\n
\n );\n};\n\nexport default DynamicEntityComponent;", "props": null, "description": null, "isActive": true, @@ -363,9 +363,7 @@ "props": null, "description": null, "isActive": true, - "dependencies": [ - "DynamicEntityComponent" - ] + "dependencies": ["DynamicEntityComponent"] } ], "Abouts": [ @@ -1807,9 +1805,7 @@ { "postContent": "CI/CD pipeline güncellememiz tamamlandı! Deployment süremiz %40 azaldı. Otomasyonun gücü 💪", "type": "video", - "urls": [ - "https://www.w3schools.com/html/mov_bbb.mp4" - ] + "urls": ["https://www.w3schools.com/html/mov_bbb.mp4"] } ], "SocialPollOptions": [ diff --git a/ui/public/version.json b/ui/public/version.json index 373bea6..a823ebd 100644 --- a/ui/public/version.json +++ b/ui/public/version.json @@ -1,5 +1,5 @@ { - "commit": "351eddf", + "commit": "e7be51a", "releases": [ { "version": "1.1.06", diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 311af5c..729443d 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -6,8 +6,8 @@ import { BrowserRouter } from 'react-router-dom' import { store } from './store' import { DynamicRoutesProvider } from './routes/dynamicRoutesContext' import { ComponentProvider } from './contexts/ComponentContext' -import { registerServiceWorker } from './views/version/swRegistration' import { useEffect } from 'react' +import DevExtremeBoundary from './components/devExtreme/DevExtremeBoundary' const queryClient = new QueryClient({ defaultOptions: { @@ -20,7 +20,11 @@ const queryClient = new QueryClient({ function App() { useEffect(() => { - registerServiceWorker() + void import('./views/version/swRegistration') + .then(({ registerServiceWorker }) => registerServiceWorker()) + .catch((error: unknown) => { + console.warn('Service worker could not be registered.', error) + }) }, []) return ( @@ -29,9 +33,11 @@ function App() { - - - + + + + + diff --git a/ui/src/components/devExtreme/DevExtremeBoundary.tsx b/ui/src/components/devExtreme/DevExtremeBoundary.tsx new file mode 100644 index 0000000..32cd657 --- /dev/null +++ b/ui/src/components/devExtreme/DevExtremeBoundary.tsx @@ -0,0 +1,56 @@ +import Loading from '@/components/shared/Loading' +import { useStoreState } from '@/store' +import { useEffect, useState, type ReactNode } from 'react' +import { useLocation } from 'react-router-dom' +import { configureDevExtreme } from './devExtremeConfig' + +interface DevExtremeBoundaryProps { + children: ReactNode +} + +const DevExtremeBoundary = ({ children }: DevExtremeBoundaryProps) => { + const { pathname } = useLocation() + const cultureName = useStoreState((state) => state.locale.currentLang) || 'en' + const [configuredCulture, setConfiguredCulture] = useState() + const [error, setError] = useState() + const requiresDevExtreme = pathname.startsWith('/admin') + + useEffect(() => { + if (!requiresDevExtreme || configuredCulture === cultureName) return + + let cancelled = false + setError(undefined) + + void configureDevExtreme(cultureName) + .then(() => { + if (!cancelled) setConfiguredCulture(cultureName) + }) + .catch((reason: unknown) => { + if (!cancelled) { + setError(reason instanceof Error ? reason : new Error(String(reason))) + } + }) + + return () => { + cancelled = true + } + }, [configuredCulture, cultureName, requiresDevExtreme]) + + if (!requiresDevExtreme) return children + + if (error) { + return
DevExtreme yüklenemedi: {error.message}
+ } + + if (configuredCulture !== cultureName) { + return ( +
+ +
+ ) + } + + return children +} + +export default DevExtremeBoundary diff --git a/ui/src/components/devExtreme/devExtremeConfig.ts b/ui/src/components/devExtreme/devExtremeConfig.ts new file mode 100644 index 0000000..85e1b25 --- /dev/null +++ b/ui/src/components/devExtreme/devExtremeConfig.ts @@ -0,0 +1,31 @@ +let devExtremeModulesPromise: + | Promise<{ + config: typeof import('devextreme/core/config').default + locale: typeof import('devextreme/localization').locale + licenseKey: string + }> + | undefined + +const loadDevExtremeModules = () => { + devExtremeModulesPromise ??= Promise.all([ + import('devextreme/core/config'), + import('devextreme/localization'), + import('@/devextreme-license'), + ]).then(([configModule, localizationModule, licenseModule]) => ({ + config: configModule.default, + locale: localizationModule.locale, + licenseKey: licenseModule.licenseKey, + })) + + return devExtremeModulesPromise +} + +export const configureDevExtreme = async (cultureName: string) => { + const { config, locale, licenseKey } = await loadDevExtremeModules() + + config({ + licenseKey, + defaultCurrency: cultureName.startsWith('tr') ? 'TRY' : 'USD', + }) + locale(cultureName) +} diff --git a/ui/src/components/layouts/ClassicLayout.tsx b/ui/src/components/layouts/ClassicLayout.tsx index 7fcadb2..83e7444 100644 --- a/ui/src/components/layouts/ClassicLayout.tsx +++ b/ui/src/components/layouts/ClassicLayout.tsx @@ -1,16 +1,18 @@ import Header from '@/components/template/Header' import MobileNav from '@/components/template/MobileNav' -import Notification from '@/components/template/Notification' import SideNav from '@/components/template/SideNav' import SideNavToggle from '@/components/template/SideNavToggle' -import SidePanel from '@/components/template/SidePanel' import UserDropdown from '@/components/template/UserDropdown' import { useStoreState } from '@/store' import LanguageSelector from '../template/LanguageSelector' -import MessengerWidget from '../template/MessengerWidget' -import Search from '../template/Search' import StackedSideNav from '../template/StackedSideNav' -import AiAssistant from '../template/AiAssistant' +import { + DeferredAiAssistant, + DeferredMessengerWidget, + DeferredNotification, + DeferredSearch, + DeferredSidePanel, +} from '../template/DeferredShellActions' import { DynamicRouter } from '@/routes/dynamicRouter' const HeaderActionsStart = () => { @@ -18,7 +20,7 @@ const HeaderActionsStart = () => { <> - + ) } @@ -26,11 +28,11 @@ const HeaderActionsStart = () => { const HeaderActionsEnd = () => { return ( <> - - - + + + - + ) diff --git a/ui/src/components/layouts/DeckedLayout.tsx b/ui/src/components/layouts/DeckedLayout.tsx index e9845cb..1cb346e 100644 --- a/ui/src/components/layouts/DeckedLayout.tsx +++ b/ui/src/components/layouts/DeckedLayout.tsx @@ -1,15 +1,17 @@ import Header from '@/components/template/Header' -import SidePanel from '@/components/template/SidePanel' import UserDropdown from '@/components/template/UserDropdown' import HeaderLogo from '@/components/template/HeaderLogo' import SecondaryHeader from '@/components/template/SecondaryHeader' -import Notification from '@/components/template/Notification' import MobileNav from '@/components/template/MobileNav' import View from '@/views/Views' -import Search from '@/components/template/Search' import LanguageSelector from '../template/LanguageSelector' -import MessengerWidget from '../template/MessengerWidget' -import AiAssistant from '../template/AiAssistant' +import { + DeferredAiAssistant, + DeferredMessengerWidget, + DeferredNotification, + DeferredSearch, + DeferredSidePanel, +} from '../template/DeferredShellActions' const HeaderActionsStart = () => { return ( @@ -23,12 +25,12 @@ const HeaderActionsStart = () => { const HeaderActionsEnd = () => { return ( <> - - - - + + + + - + ) diff --git a/ui/src/components/layouts/Layouts.tsx b/ui/src/components/layouts/Layouts.tsx index 136a511..6fef18a 100644 --- a/ui/src/components/layouts/Layouts.tsx +++ b/ui/src/components/layouts/Layouts.tsx @@ -1,4 +1,4 @@ -import { useMemo, lazy, Suspense } from 'react' +import { Component, useMemo, lazy, Suspense, type ErrorInfo, type ReactNode } from 'react' import Loading from '@/components/shared/Loading' import { useStoreState } from '@/store' import { @@ -36,6 +36,45 @@ const layouts = { const AuthLayout = lazy(() => import('./AuthLayout')) const PublicLayout = lazy(() => import('./PublicLayout')) +class LayoutChunkErrorBoundary extends Component< + { children: ReactNode }, + { failed: boolean } +> { + state = { failed: false } + + static getDerivedStateFromError() { + return { failed: true } + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error('Application layout could not be loaded.', error, errorInfo) + } + + render() { + if (this.state.failed) { + return ( +
+
+

Uygulama kabuğu yüklenemedi

+

+ Bağlantınızı kontrol edip uygulamayı yeniden yükleyin. +

+ +
+
+ ) + } + + return this.props.children + } +} + const Layout = () => { const location = useLocation() const layoutType = useStoreState((s) => s.theme.layout.type) as LayoutType @@ -94,15 +133,17 @@ const Layout = () => { } return ( - - - - } - > - - + + + + + } + > + + + ) } diff --git a/ui/src/components/layouts/ModernLayout.tsx b/ui/src/components/layouts/ModernLayout.tsx index 2c49c87..bf422d6 100644 --- a/ui/src/components/layouts/ModernLayout.tsx +++ b/ui/src/components/layouts/ModernLayout.tsx @@ -1,24 +1,26 @@ import Header from '@/components/template/Header' import MobileNav from '@/components/template/MobileNav' -import Notification from '@/components/template/Notification' -import Search from '@/components/template/Search' import SideNav from '@/components/template/SideNav' import SideNavToggle from '@/components/template/SideNavToggle' -import SidePanel from '@/components/template/SidePanel' import UserDropdown from '@/components/template/UserDropdown' import View from '@/views/Views' import LanguageSelector from '../template/LanguageSelector' -import MessengerWidget from '../template/MessengerWidget' import { useStoreState } from '@/store' import StackedSideNav from '../template/StackedSideNav' -import AiAssistant from '../template/AiAssistant' +import { + DeferredAiAssistant, + DeferredMessengerWidget, + DeferredNotification, + DeferredSearch, + DeferredSidePanel, +} from '../template/DeferredShellActions' const HeaderActionsStart = () => { return ( <> - + ) } @@ -26,11 +28,11 @@ const HeaderActionsStart = () => { const HeaderActionsEnd = () => { return ( <> - - - + + + - + ) diff --git a/ui/src/components/layouts/SimpleLayout.tsx b/ui/src/components/layouts/SimpleLayout.tsx index f83b4e0..7cc30b3 100644 --- a/ui/src/components/layouts/SimpleLayout.tsx +++ b/ui/src/components/layouts/SimpleLayout.tsx @@ -1,15 +1,17 @@ import Header from '@/components/template/Header' -import SidePanel from '@/components/template/SidePanel' import UserDropdown from '@/components/template/UserDropdown' import HeaderLogo from '@/components/template/HeaderLogo' import MobileNav from '@/components/template/MobileNav' import HorizontalNav from '@/components/template/HorizontalNav' import View from '@/views/Views' -import Notification from '@/components/template/Notification' -import Search from '@/components/template/Search' import LanguageSelector from '../template/LanguageSelector' -import MessengerWidget from '../template/MessengerWidget' -import AiAssistant from '../template/AiAssistant' +import { + DeferredAiAssistant, + DeferredMessengerWidget, + DeferredNotification, + DeferredSearch, + DeferredSidePanel, +} from '../template/DeferredShellActions' const HeaderActionsStart = () => { return ( @@ -23,12 +25,12 @@ const HeaderActionsStart = () => { const HeaderActionsEnd = () => { return ( <> - - - - + + + + - + ) diff --git a/ui/src/components/layouts/StackedSideLayout.tsx b/ui/src/components/layouts/StackedSideLayout.tsx index a5dd5de..c617ec2 100644 --- a/ui/src/components/layouts/StackedSideLayout.tsx +++ b/ui/src/components/layouts/StackedSideLayout.tsx @@ -1,20 +1,22 @@ import Header from '@/components/template/Header' -import SidePanel from '@/components/template/SidePanel' import UserDropdown from '@/components/template/UserDropdown' import MobileNav from '@/components/template/MobileNav' import StackedSideNav from '@/components/template/StackedSideNav' import View from '@/views/Views' -import Search from '@/components/template/Search' -import Notification from '@/components/template/Notification' import LanguageSelector from '../template/LanguageSelector' -import MessengerWidget from '../template/MessengerWidget' -import AiAssistant from '../template/AiAssistant' +import { + DeferredAiAssistant, + DeferredMessengerWidget, + DeferredNotification, + DeferredSearch, + DeferredSidePanel, +} from '../template/DeferredShellActions' const HeaderActionsStart = () => { return ( <> - + ) } @@ -22,11 +24,11 @@ const HeaderActionsStart = () => { const HeaderActionsEnd = () => { return ( <> - - - + + + - + ) diff --git a/ui/src/components/template/DeferredShellActions.tsx b/ui/src/components/template/DeferredShellActions.tsx new file mode 100644 index 0000000..1b2f0ec --- /dev/null +++ b/ui/src/components/template/DeferredShellActions.tsx @@ -0,0 +1,72 @@ +import React, { Component, lazy, Suspense, type ErrorInfo, type ReactNode } from 'react' + +const Search = lazy(() => import('./Search')) +const AiAssistant = lazy(() => import('./AiAssistant')) +const Notification = lazy(() => import('./Notification')) +const MessengerWidget = lazy(() => import('./MessengerWidget')) +const SidePanel = lazy(() => import('./SidePanel')) + +interface OptionalShellActionBoundaryProps { + children: ReactNode +} + +interface OptionalShellActionBoundaryState { + failed: boolean +} + +class OptionalShellActionBoundary extends Component< + OptionalShellActionBoundaryProps, + OptionalShellActionBoundaryState +> { + state: OptionalShellActionBoundaryState = { failed: false } + + static getDerivedStateFromError(): OptionalShellActionBoundaryState { + return { failed: true } + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error('Optional shell action could not be loaded.', error, errorInfo) + } + + render() { + return this.state.failed ? null : this.props.children + } +} + +const ActionFallback = () => + +const DeferredAction = ({ children }: { children: ReactNode }) => ( + + }>{children} + +) + +export const DeferredSearch = () => ( + + + +) + +export const DeferredAiAssistant = () => ( + + + +) + +export const DeferredNotification = () => ( + + + +) + +export const DeferredMessengerWidget = () => ( + + + +) + +export const DeferredSidePanel = () => ( + + + +) diff --git a/ui/src/contexts/ComponentContext.tsx b/ui/src/contexts/ComponentContext.tsx index e511e08..dffaee9 100644 --- a/ui/src/contexts/ComponentContext.tsx +++ b/ui/src/contexts/ComponentContext.tsx @@ -1,52 +1,11 @@ -import { +import type { CreateUpdateCustomComponentDto, CustomComponent, CustomComponentDto, } from '@/proxy/developerKit/models' import { developerKitService } from '@/services/developerKit.service' import { useStoreState } from '@/store/store' -import React, { createContext, useContext, useState, useEffect, useCallback } from 'react' -import { - Alert, - Avatar, - Badge, - Button, - Calendar, - Card, - Checkbox, - ConfigProvider, - DatePicker, - Dialog, - Drawer, - Dropdown, - FormItem, - FormContainer, - Input, - InputGroup, - Menu, - MenuItem, - Notification, - Pagination, - Progress, - Radio, - RangeCalendar, - ScrollBar, - Segment, - Select, - Skeleton, - Spinner, - Steps, - Switcher, - Table, - Tabs, - Tag, - TimeInput, - Timeline, - toast, - Tooltip, - Upload, -} from '../components/ui' -import axios from 'axios' +import React, { createContext, useContext, useState, useEffect, useCallback, useMemo } from 'react' interface ComponentProps { [key: string]: unknown @@ -75,6 +34,39 @@ interface ComponentContextType { const ComponentContext = createContext(undefined) +const HelloWorldComponent: React.ComponentType = () => + React.createElement('div', { className: 'p-6 bg-blue-50 rounded-lg' }, [ + React.createElement( + 'h1', + { key: 'title', className: 'text-2xl font-bold text-blue-900 mb-4' }, + 'Hello World!', + ), + React.createElement( + 'p', + { key: 'description', className: 'text-blue-700' }, + 'Bu manuel kayıtlı bir komponent!', + ), + ]) + +const TestListComponent: React.ComponentType = () => + React.createElement('div', { className: 'p-6 bg-green-50 rounded-lg' }, [ + React.createElement( + 'h1', + { key: 'title', className: 'text-2xl font-bold text-green-900 mb-4' }, + 'Test List', + ), + React.createElement( + 'p', + { key: 'description', className: 'text-green-700' }, + 'Bu da test için kayıtlı komponent!', + ), + ]) + +const defaultRegisteredComponents: Record> = { + HelloWorld: HelloWorldComponent, + TestList: TestListComponent, +} + // eslint-disable-next-line react-refresh/only-export-components export const useComponents = () => { const context = useContext(ComponentContext) @@ -89,16 +81,13 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi const [components, setComponents] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) - const [registeredComponents, setRegisteredComponents] = useState< - Record> - >({}) + const [registeredComponents, setRegisteredComponents] = useState(defaultRegisteredComponents) const [compiledComponents, setCompiledComponents] = useState< Record> >({}) - const refreshComponents = async () => { + const refreshComponents = useCallback(async () => { try { - setLoading(true) setError(null) const customComponents = extraProperties?.customComponents as CustomComponentDto[] @@ -107,16 +96,14 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi setError(err instanceof Error ? err.message : 'Failed to fetch components') console.error('Failed to fetch components:', err) setComponents([]) - } finally { - setLoading(false) } - } + }, [extraProperties]) useEffect(() => { refreshComponents() - }, [extraProperties]) + }, [refreshComponents]) - const addComponent = async (componentData: CreateUpdateCustomComponentDto) => { + const addComponent = useCallback(async (componentData: CreateUpdateCustomComponentDto) => { try { setLoading(true) setError(null) @@ -128,9 +115,12 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi } finally { setLoading(false) } - } + }, []) - const updateComponent = async (id: string, componentData: CreateUpdateCustomComponentDto) => { + const updateComponent = useCallback(async ( + id: string, + componentData: CreateUpdateCustomComponentDto, + ) => { try { setLoading(true) setError(null) @@ -144,9 +134,9 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi } finally { setLoading(false) } - } + }, []) - const deleteComponent = async (id: string) => { + const deleteComponent = useCallback(async (id: string) => { try { setLoading(true) setError(null) @@ -158,78 +148,36 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi } finally { setLoading(false) } - } - const getComponent = (id: string) => { - return components?.find((comp) => comp.id === id) - } + }, []) - const getComponentByName = (name: string) => { - return components?.find((comp) => comp.name === name) - } + const componentsById = useMemo( + () => new Map(components.map((component) => [component.id, component])), + [components], + ) + const componentsByName = useMemo( + () => new Map(components.map((component) => [component.name, component])), + [components], + ) + const activeComponentsByName = useMemo( + () => new Map(components.filter((component) => component.isActive).map((component) => [component.name, component])), + [components], + ) - const registerComponent = (name: string, component: React.ComponentType) => { - setRegisteredComponents((prev) => ({ - ...prev, - [name]: component, - })) - } + const getComponent = useCallback((id: string) => componentsById.get(id), [componentsById]) - // Register some default components on mount - useEffect(() => { - // Example components for testing - const HelloWorldComponent: React.ComponentType = () => { - return React.createElement( - 'div', - { - className: 'p-6 bg-blue-50 rounded-lg', - }, - [ - React.createElement( - 'h1', - { - className: 'text-2xl font-bold text-blue-900 mb-4', - }, - 'Hello World!', - ), - React.createElement( - 'p', - { - className: 'text-blue-700', - }, - 'Bu manuel kayıtlı bir komponent!', - ), - ], + const getComponentByName = useCallback( + (name: string) => componentsByName.get(name), + [componentsByName], + ) + + const registerComponent = useCallback( + (name: string, component: React.ComponentType) => { + setRegisteredComponents((previous) => + previous[name] === component ? previous : { ...previous, [name]: component }, ) - } - - const TestListComponent: React.ComponentType = () => { - return React.createElement( - 'div', - { - className: 'p-6 bg-green-50 rounded-lg', - }, - [ - React.createElement( - 'h1', - { - className: 'text-2xl font-bold text-green-900 mb-4', - }, - 'Test List', - ), - React.createElement( - 'p', - { - className: 'text-green-700', - }, - 'Bu da test için kayıtlı komponent!', - ), - ], - ) - } - - registerComponent('HelloWorld', HelloWorldComponent) - registerComponent('TestList', TestListComponent) - }, []) // Empty dependency array - only run once + }, + [], + ) // Component compilation functions (moved from ComponentRegistryProvider) const extractComponentInfo = useCallback((code: string, defaultName = '') => { @@ -259,7 +207,10 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi // Compile components when they change useEffect(() => { - if (!components || !components?.length) return + if (!components.length) { + setCompiledComponents({}) + return + } let cancelled = false @@ -332,9 +283,54 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi // Babel is several megabytes and is only needed when an active runtime // component exists. Keep it out of the application startup bundle. - const Babel = await import('@babel/standalone') + const [Babel, ui, { default: axios }] = await Promise.all([ + import('@babel/standalone'), + import('../components/ui'), + import('axios'), + ]) if (cancelled) return + const { + Alert, + Avatar, + Badge, + Button, + Calendar, + Card, + Checkbox, + ConfigProvider, + DatePicker, + Dialog, + Drawer, + Dropdown, + FormItem, + FormContainer, + Input, + InputGroup, + Menu, + MenuItem, + Notification, + Pagination, + Progress, + Radio, + RangeCalendar, + ScrollBar, + Segment, + Select, + Skeleton, + Spinner, + Steps, + Switcher, + Table, + Tabs, + Tag, + TimeInput, + Timeline, + toast, + Tooltip, + Upload, + } = ui + const compiledBundle = Babel.transform(bundledCode, { presets: ['react', 'typescript'], filename: 'components-bundle.tsx', @@ -453,7 +449,7 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi return } - const component = components.find((c) => c.name === name && c.isActive) + const component = activeComponentsByName.get(name) if (!component) { console.error(`Component not found: ${name}`) return ( @@ -465,7 +461,7 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi return null }, - [components, compiledComponents], + [activeComponentsByName, compiledComponents], ) const compileAndRender = useCallback((code: string) => { @@ -476,43 +472,63 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi const isComponentRegistered = useCallback( (name: string) => { - return components.some((c) => c.name === name && c.isActive) || !!compiledComponents[name] + return activeComponentsByName.has(name) || !!compiledComponents[name] }, - [components, compiledComponents], + [activeComponentsByName, compiledComponents], ) const getRegisteredComponents = useCallback(() => { - const dbComponents = components?.filter((c) => c.isActive).map((c) => c.name) || [] + const dbComponents = [...activeComponentsByName.keys()] const compiledNames = Object.keys(compiledComponents) return [...new Set([...dbComponents, ...compiledNames])] - }, [components, compiledComponents]) + }, [activeComponentsByName, compiledComponents]) const getComponentCode = useCallback( (name: string) => { - const component = components.find((c) => c.name === name) + const component = componentsByName.get(name) return component ? component.code : null }, - [components], + [componentsByName], ) - const value = { - components, - loading, - error, - addComponent, - updateComponent, - deleteComponent, - getComponent, - getComponentByName, - refreshComponents, - registeredComponents, - registerComponent, - renderComponent, - compileAndRender, - isComponentRegistered, - getRegisteredComponents, - getComponentCode, - } + const value = useMemo( + () => ({ + components, + loading, + error, + addComponent, + updateComponent, + deleteComponent, + getComponent, + getComponentByName, + refreshComponents, + registeredComponents, + registerComponent, + renderComponent, + compileAndRender, + isComponentRegistered, + getRegisteredComponents, + getComponentCode, + }), + [ + components, + loading, + error, + addComponent, + updateComponent, + deleteComponent, + getComponent, + getComponentByName, + refreshComponents, + registeredComponents, + registerComponent, + renderComponent, + compileAndRender, + isComponentRegistered, + getRegisteredComponents, + getComponentCode, + ], + ) return {children} } diff --git a/ui/src/main.tsx b/ui/src/main.tsx index b710be2..91d53e4 100644 --- a/ui/src/main.tsx +++ b/ui/src/main.tsx @@ -1,15 +1,38 @@ -/* eslint-disable import/default */ import ReactDOM from 'react-dom/client' import App from './App' import './index.css' -import { UiEvalService } from './services/UiEvalService' -import config from 'devextreme/core/config' -import { licenseKey } from './devextreme-license' +type UiEvalServiceModule = typeof import('./services/UiEvalService') -// Lisansı uygulama başlamadan önce kaydediyoruz -config({ licenseKey }) +let uiEvalServicePromise: Promise | undefined +const loadUiEvalService = () => + (uiEvalServicePromise ??= import('./services/UiEvalService')) + +const lazyUiEvalService = new Proxy void>>( + {}, + { + get: (_, methodName) => { + if (typeof methodName !== 'string') return undefined + + return (...args: unknown[]) => { + void loadUiEvalService() + .then(({ UiEvalService }) => { + const serviceMethod = Reflect.get(UiEvalService, methodName) + if (typeof serviceMethod === 'function') { + Reflect.apply(serviceMethod, UiEvalService, args) + } + }) + .catch((error: unknown) => { + console.error(`UiEvalService.${methodName} could not be loaded.`, error) + }) + } + }, + }, +) + +const globalWithUiEval = globalThis as typeof globalThis & { + UiEvalService?: typeof lazyUiEvalService +} +globalWithUiEval.UiEvalService ??= lazyUiEvalService ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render() - -UiEvalService.Init() diff --git a/ui/src/proxy/admin/list-form/options.ts b/ui/src/proxy/admin/list-form/options.ts index ab7e3e4..9bb0869 100644 --- a/ui/src/proxy/admin/list-form/options.ts +++ b/ui/src/proxy/admin/list-form/options.ts @@ -1,5 +1,3 @@ -import { Gantt } from 'devextreme-react' - export const extraFilterControlTypeOptions = [ { value: 'Select', label: 'Select' }, { value: 'TextBox', label: 'TextBox' }, @@ -175,4 +173,4 @@ export const tooltipFormatListOptions = [ { value: 'thousands', label: 'Thousands' }, { value: 'trillions', label: 'Trillions' }, { value: 'year', label: 'Year' }, -] \ No newline at end of file +] diff --git a/ui/src/proxy/form/models.ts b/ui/src/proxy/form/models.ts index 709806b..fa03677 100644 --- a/ui/src/proxy/form/models.ts +++ b/ui/src/proxy/form/models.ts @@ -1,10 +1,10 @@ -import { +import type { HorizontalAlignment, ScrollMode, SelectAllMode, - type SingleMultipleOrNone, + SingleMultipleOrNone, } from 'devextreme/common' -import { +import type { ApplyFilterMode, ColumnChooserMode, ColumnResizeMode, @@ -15,7 +15,7 @@ import { StartEditAction, StateStoreType, } from 'devextreme/ui/data_grid_types' -import { FormItemComponent } from 'devextreme/ui/form' +import type { FormItemComponent } from 'devextreme/ui/form' import { AuditedEntityDto } from '../abp' import { EditorType2, RowMode } from '../../views/form/types' import { @@ -42,9 +42,9 @@ import { ChartZoomAndPanDto, } from '../admin/charts/models' import { ListViewLayoutType } from '@/views/admin/listForm/edit/types' -import { SeriesType } from 'devextreme/common/charts' -import { PagerDisplayMode } from 'devextreme/common/grids' -import { GanttScaleType } from 'devextreme/ui/gantt' +import type { SeriesType } from 'devextreme/common/charts' +import type { PagerDisplayMode } from 'devextreme/common/grids' +import type { GanttScaleType } from 'devextreme/ui/gantt' //1 export interface SelectListItem { diff --git a/ui/src/routes/dynamicRouteLoader.tsx b/ui/src/routes/dynamicRouteLoader.tsx index e06877e..c3c6a20 100644 --- a/ui/src/routes/dynamicRouteLoader.tsx +++ b/ui/src/routes/dynamicRouteLoader.tsx @@ -1,20 +1,25 @@ import React, { lazy } from 'react' -import { RouteDto } from '@/proxy/routes/models' -import { CustomComponent } from '@/proxy/developerKit/models' +import type { RouteDto } from '@/proxy/routes/models' +import type { CustomComponent } from '@/proxy/developerKit/models' // Tüm view bileşenlerini import et (vite özel) // shared klasörü hariç, çünkü bu bileşenler genellikle başka yerlerde statik import ediliyor const modules = import.meta.glob(['../views/**/*.tsx', '!../views/shared/**/*.tsx']) -const lazyComponentCache = new Map>>() +// Only physical modules are safe to cache. Runtime components depend on the +// latest ComponentContext render function and must not retain an old closure. +const physicalComponentCache = new Map< + string, + React.LazyExoticComponent> +>() // Fiziksel komponent yükleme (mevcut mantık) function loadPhysicalComponent(componentPath: string) { const cleanedPath = componentPath.replace(/^@\//, '') const fullPath = `../${cleanedPath}.tsx` - if (lazyComponentCache.has(fullPath)) { - return lazyComponentCache.get(fullPath)! + if (physicalComponentCache.has(fullPath)) { + return physicalComponentCache.get(fullPath)! } const loader = modules[fullPath] @@ -24,7 +29,7 @@ function loadPhysicalComponent(componentPath: string) { } const LazyComponent = lazy(loader as () => Promise<{ default: React.ComponentType }>) - lazyComponentCache.set(fullPath, LazyComponent) + physicalComponentCache.set(fullPath, LazyComponent) return LazyComponent } @@ -35,12 +40,6 @@ function loadDynamicComponent( renderComponent?: (name: string, props?: any) => React.ReactNode, isComponentRegistered?: (name: string) => boolean, ) { - - if (lazyComponentCache.has(componentPath)) { - // console.log(`Dynamic component loaded from cache: ${componentName}`) - return lazyComponentCache.get(componentPath)! - } - // Önce manuel registered komponentleri kontrol et let DynamicComponent = registeredComponents[componentPath] @@ -61,13 +60,10 @@ function loadDynamicComponent( throw new Error(`Dynamic component not found: ${componentPath}`) } - // console.log(`Dynamic component loaded: ${componentPath}`) - // Dinamik komponent için lazy wrapper oluştur - const LazyComponent = lazy(() => - Promise.resolve({ default: DynamicComponent as React.ComponentType }), - ) - lazyComponentCache.set(componentPath, LazyComponent) - return LazyComponent + // Do not put this wrapper in a module-level cache. renderComponent changes + // after the asynchronous database bundle compilation finishes. Caching this + // function would keep the pre-compilation closure and render an empty page. + return DynamicComponent as React.ComponentType } export function loadComponent( @@ -103,7 +99,7 @@ export interface DynamicReactRoute { registeredComponents?: Record>, renderComponent?: (name: string, props?: any) => React.ReactNode, isComponentRegistered?: (name: string) => boolean, - ) => React.LazyExoticComponent> + ) => React.ComponentType routeType: string authority?: string[] componentType: string diff --git a/ui/src/services/notification.service.ts b/ui/src/services/notification.service.ts index 147bf8e..5a5ab61 100644 --- a/ui/src/services/notification.service.ts +++ b/ui/src/services/notification.service.ts @@ -37,7 +37,7 @@ export const updateSent = (notificationId: string, isSent: boolean) => }) export const updateReadMany = (notificationIds: string[], isRead: boolean) => - apiService.fetchData({ + apiService.fetchData({ method: 'PUT', url: `/api/app/notification/read-many`, params: { isRead }, @@ -45,7 +45,7 @@ export const updateReadMany = (notificationIds: string[], isRead: boolean) => }) export const updateSentMany = (notificationIds: string[], isSent: boolean) => - apiService.fetchData({ + apiService.fetchData({ method: 'PUT', url: `/api/app/notification/sent-many`, params: { isSent }, diff --git a/ui/src/utils/hooks/useLocale.ts b/ui/src/utils/hooks/useLocale.ts index a6a1f0c..4f56e33 100644 --- a/ui/src/utils/hooks/useLocale.ts +++ b/ui/src/utils/hooks/useLocale.ts @@ -2,8 +2,6 @@ import { useEffect } from 'react' import dayjs from 'dayjs' import utc from 'dayjs/plugin/utc' import timezone from 'dayjs/plugin/timezone' -import { locale as dxLocale } from 'devextreme/localization' -import config from 'devextreme/core/config' import { useStoreState } from '@/store' import { dateLocales } from '@/constants/dateLocales.constant' @@ -26,14 +24,6 @@ function useLocale() { dayjs.locale(cultureName) dayjs.tz.setDefault(timeZone) - // ✅ DevExtreme locale ayarı - dxLocale(cultureName) - - // ✅ Para birimi - config({ - defaultCurrency: cultureName.startsWith('tr') ? 'TRY' : 'USD', - }) - // console.info(`🌍 Locale: ${cultureName}, TZ: ${timeZone}`) }) } diff --git a/ui/src/views/admin/listForm/edit/FormTabColumns.tsx b/ui/src/views/admin/listForm/edit/FormTabColumns.tsx index 5c4761d..7464604 100644 --- a/ui/src/views/admin/listForm/edit/FormTabColumns.tsx +++ b/ui/src/views/admin/listForm/edit/FormTabColumns.tsx @@ -9,7 +9,7 @@ import { import { ListFormEditTabs } from '@/proxy/admin/list-form/options' import { useStoreState } from '@/store' import { useLocalization } from '@/utils/hooks/useLocalization' -import { ColumnChooserMode, ColumnResizeMode } from 'devextreme/common/grids' +import type { ColumnChooserMode, ColumnResizeMode } from 'devextreme/common/grids' import { Field, FieldProps, Form, Formik } from 'formik' import * as Yup from 'yup' import { FormEditProps } from './FormEdit' diff --git a/ui/src/views/admin/listForm/edit/FormTabEdit.tsx b/ui/src/views/admin/listForm/edit/FormTabEdit.tsx index 3590d82..5ec92a4 100644 --- a/ui/src/views/admin/listForm/edit/FormTabEdit.tsx +++ b/ui/src/views/admin/listForm/edit/FormTabEdit.tsx @@ -3,7 +3,7 @@ import { Button, Card, Checkbox, FormContainer, FormItem, Input, Select } from ' import { ListFormEditTabs } from '@/proxy/admin/list-form/options' import { useStoreState } from '@/store' import { useLocalization } from '@/utils/hooks/useLocalization' -import { +import type { GridsEditMode, GridsEditRefreshMode, NewRowPosition, diff --git a/ui/src/views/admin/listForm/edit/FormTabFilters.tsx b/ui/src/views/admin/listForm/edit/FormTabFilters.tsx index fabcf92..cb6bfda 100644 --- a/ui/src/views/admin/listForm/edit/FormTabFilters.tsx +++ b/ui/src/views/admin/listForm/edit/FormTabFilters.tsx @@ -11,7 +11,7 @@ import { import { ListFormEditTabs } from '@/proxy/admin/list-form/options' import { useStoreState } from '@/store' import { useLocalization } from '@/utils/hooks/useLocalization' -import { ApplyFilterMode } from 'devextreme/common/grids' +import type { ApplyFilterMode } from 'devextreme/common/grids' import { Field, FieldProps, Form, Formik } from 'formik' import * as Yup from 'yup' import { FormEditProps } from './FormEdit' diff --git a/ui/src/views/admin/listForm/edit/FormTabGantt.tsx b/ui/src/views/admin/listForm/edit/FormTabGantt.tsx index e4e83b7..9b56031 100644 --- a/ui/src/views/admin/listForm/edit/FormTabGantt.tsx +++ b/ui/src/views/admin/listForm/edit/FormTabGantt.tsx @@ -20,7 +20,7 @@ import { useCallback, useEffect, useState } from 'react' import { getListFormFields } from '@/services/admin/list-form-field.service' import groupBy from 'lodash/groupBy' import { useParams } from 'react-router-dom' -import { GanttScaleType } from 'devextreme/ui/gantt' +import type { GanttScaleType } from 'devextreme/ui/gantt' import { gantScaleOptions } from './options' const validationSchema = Yup.object().shape({}) diff --git a/ui/src/views/admin/listForm/edit/FormTabPager.tsx b/ui/src/views/admin/listForm/edit/FormTabPager.tsx index db04c92..a12312c 100644 --- a/ui/src/views/admin/listForm/edit/FormTabPager.tsx +++ b/ui/src/views/admin/listForm/edit/FormTabPager.tsx @@ -3,8 +3,8 @@ import { Button, Checkbox, FormContainer, FormItem, Input, Select } from '@/comp import { ListFormEditTabs } from '@/proxy/admin/list-form/options' import { useStoreState } from '@/store' import { useLocalization } from '@/utils/hooks/useLocalization' -import { ScrollMode } from 'devextreme/common' -import { PagerDisplayMode } from 'devextreme/common/grids' +import type { ScrollMode } from 'devextreme/common' +import type { PagerDisplayMode } from 'devextreme/common/grids' import { Field, FieldProps, Form, Formik } from 'formik' import * as Yup from 'yup' import { FormEditProps } from './FormEdit' diff --git a/ui/src/views/admin/listForm/edit/FormTabSelect.tsx b/ui/src/views/admin/listForm/edit/FormTabSelect.tsx index b84ffab..57c82f6 100644 --- a/ui/src/views/admin/listForm/edit/FormTabSelect.tsx +++ b/ui/src/views/admin/listForm/edit/FormTabSelect.tsx @@ -3,8 +3,8 @@ import { Button, Checkbox, FormContainer, FormItem, Select } from '@/components/ import { ListFormEditTabs } from '@/proxy/admin/list-form/options' import { useStoreState } from '@/store' import { useLocalization } from '@/utils/hooks/useLocalization' -import { SelectAllMode, SingleMultipleOrNone } from 'devextreme/common' -import { SelectionColumnDisplayMode } from 'devextreme/common/grids' +import type { SelectAllMode, SingleMultipleOrNone } from 'devextreme/common' +import type { SelectionColumnDisplayMode } from 'devextreme/common/grids' import { Field, FieldProps, Form, Formik } from 'formik' import * as Yup from 'yup' import { FormEditProps } from './FormEdit' diff --git a/ui/src/views/admin/listForm/edit/FormTabState.tsx b/ui/src/views/admin/listForm/edit/FormTabState.tsx index 7170ae5..0a29653 100644 --- a/ui/src/views/admin/listForm/edit/FormTabState.tsx +++ b/ui/src/views/admin/listForm/edit/FormTabState.tsx @@ -3,7 +3,7 @@ import { Button, Checkbox, FormContainer, FormItem, Input, Select } from '@/comp import { ListFormEditTabs } from '@/proxy/admin/list-form/options' import { useStoreState } from '@/store' import { useLocalization } from '@/utils/hooks/useLocalization' -import { StateStoreType } from 'devextreme/common/grids' +import type { StateStoreType } from 'devextreme/common/grids' import { Field, FieldProps, Form, Formik } from 'formik' import * as Yup from 'yup' import { FormEditProps } from './FormEdit' diff --git a/ui/src/views/developerKit/ComponentCodeLayout.tsx b/ui/src/views/developerKit/ComponentCodeLayout.tsx index e11f78f..db7b74c 100644 --- a/ui/src/views/developerKit/ComponentCodeLayout.tsx +++ b/ui/src/views/developerKit/ComponentCodeLayout.tsx @@ -58,6 +58,7 @@ function ComponentCodeLayout() { try { const componentData = { name: name.trim(), + routePath: id ? (getComponent(id)?.routePath ?? '') : '', dependencies: JSON.stringify(dependencies), // Serialize dependencies to JSON string code: code.trim(), isActive, diff --git a/ui/src/views/developerKit/ComponentManager.tsx b/ui/src/views/developerKit/ComponentManager.tsx index 0784214..d15d3b1 100644 --- a/ui/src/views/developerKit/ComponentManager.tsx +++ b/ui/src/views/developerKit/ComponentManager.tsx @@ -10,6 +10,7 @@ import { FaEyeSlash, FaFilter, FaCalendarAlt, + FaExternalLinkAlt, } from 'react-icons/fa' import Widget from '@/components/common/Widget' import { ROUTES_ENUM } from '@/routes/route.constant' @@ -65,6 +66,11 @@ const ComponentManager: React.FC = () => { } } + const handleOpenRoute = (routePath: string) => { + const normalizedPath = routePath.startsWith('/') ? routePath : `/${routePath}` + navigate(normalizedPath) + } + return (
{
+ @@ -272,7 +289,7 @@ const ComponentManager: React.FC = () => { ), ) } - title={translate('::App.DeveloperKit.Component.View')} + title={translate('::App.Platform.View')} > @@ -282,7 +299,7 @@ const ComponentManager: React.FC = () => { shape="circle" color="red-600" onClick={() => handleDelete(component.id)} - title={translate('::App.DeveloperKit.Component.Delete')} + title={translate('::Delete')} > diff --git a/ui/src/views/form/FormDevExpress.tsx b/ui/src/views/form/FormDevExpress.tsx index 22142a3..98b4bbc 100644 --- a/ui/src/views/form/FormDevExpress.tsx +++ b/ui/src/views/form/FormDevExpress.tsx @@ -3,11 +3,11 @@ import '@/utils/registerDevExtremeEditors' import { executeEditorScript } from '@/utils/editorScriptRuntime' import { Form as FormDx, - FormRef, + type FormRef, GroupItem as GroupItemDx, SimpleItem as SimpleItemDx, } from 'devextreme-react/form' -import { FieldDataChangedEvent, GroupItem } from 'devextreme/ui/form' +import type { FieldDataChangedEvent, GroupItem } from 'devextreme/ui/form' import { Dispatch, RefObject, useEffect, useRef, useState } from 'react' import { GridBoxEditorComponent } from './editors/GridBoxEditorComponent' import { ImageUploadEditorComponent } from './editors/ImageUploadEditorComponent' diff --git a/ui/src/views/form/FormNew.tsx b/ui/src/views/form/FormNew.tsx index af6d0fc..d17c89a 100644 --- a/ui/src/views/form/FormNew.tsx +++ b/ui/src/views/form/FormNew.tsx @@ -1,7 +1,7 @@ import { Container, Loading } from '@/components/shared' import { useLocalization } from '@/utils/hooks/useLocalization' -import { DataType } from 'devextreme/common' -import { SimpleItem } from 'devextreme/ui/form' +import type { DataType } from 'devextreme/common' +import type { SimpleItem } from 'devextreme/ui/form' import { useEffect, useRef, useState } from 'react' import { Helmet } from 'react-helmet' import { useParams, useSearchParams } from 'react-router-dom' diff --git a/ui/src/views/form/types.ts b/ui/src/views/form/types.ts index 6638f07..659e0b7 100644 --- a/ui/src/views/form/types.ts +++ b/ui/src/views/form/types.ts @@ -1,4 +1,4 @@ -import { FormItemComponent, SimpleItem } from 'devextreme/ui/form' +import type { FormItemComponent, SimpleItem } from 'devextreme/ui/form' import { Overwrite } from '../../utils/types' import { ColumnFormatDto, diff --git a/ui/src/views/form/useFormData.tsx b/ui/src/views/form/useFormData.tsx index 43bd330..2a2ecf2 100644 --- a/ui/src/views/form/useFormData.tsx +++ b/ui/src/views/form/useFormData.tsx @@ -2,10 +2,10 @@ import { Notification, toast } from '@/components/ui' import { getList } from '@/services/form.service' import { useLocalization } from '@/utils/hooks/useLocalization' import { usePermission } from '@/utils/hooks/usePermission' -import { FormRef } from 'devextreme-react/form' +import type { FormRef } from 'devextreme-react/form' import { captionize } from 'devextreme/core/utils/inflector' import CustomStore from 'devextreme/data/custom_store' -import { GroupItem } from 'devextreme/ui/form' +import type { GroupItem } from 'devextreme/ui/form' import { useEffect, useRef, useState } from 'react' import { useNavigate, useSearchParams } from 'react-router-dom' import { GridColumnData } from '../list/GridColumnData' diff --git a/ui/src/views/list/GanttView.tsx b/ui/src/views/list/GanttView.tsx index 56bbb4d..8040118 100644 --- a/ui/src/views/list/GanttView.tsx +++ b/ui/src/views/list/GanttView.tsx @@ -7,7 +7,7 @@ import Gantt, { ContextMenu, Editing, FilterRow, - GanttRef, + type GanttRef, HeaderFilter, Item, Sorting, @@ -24,7 +24,7 @@ import { layoutTypes } from '../admin/listForm/edit/types' import WidgetGroup from '@/components/ui/Widget/WidgetGroup' import { ROUTES_ENUM } from '@/routes/route.constant' import { usePWA } from '@/utils/hooks/usePWA' -import { GanttScaleType } from 'devextreme/ui/gantt' +import type { GanttScaleType } from 'devextreme/ui/gantt' import { useListFormColumns } from './useListFormColumns' import CustomStore from 'devextreme/data/custom_store' import { GridColumnData } from './GridColumnData' diff --git a/ui/src/views/list/Grid.tsx b/ui/src/views/list/Grid.tsx index 460d00f..dd10a30 100644 --- a/ui/src/views/list/Grid.tsx +++ b/ui/src/views/list/Grid.tsx @@ -16,8 +16,8 @@ import { Template } from 'devextreme-react/core/template' import DataGrid, { ColumnChooser, ColumnFixing, - DataGridRef, - DataGridTypes, + type DataGridRef, + type DataGridTypes, Editing, Export, FilterPanel, @@ -38,7 +38,7 @@ import DataGrid, { TotalItem, } from 'devextreme-react/data-grid' import { Item } from 'devextreme-react/toolbar' -import { DataType } from 'devextreme/common' +import type { DataType } from 'devextreme/common' import { captionize } from 'devextreme/core/utils/inflector' import CustomStore from 'devextreme/data/custom_store' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' diff --git a/ui/src/views/list/GridColumnData.ts b/ui/src/views/list/GridColumnData.ts index 49fe489..657f61e 100644 --- a/ui/src/views/list/GridColumnData.ts +++ b/ui/src/views/list/GridColumnData.ts @@ -1,4 +1,4 @@ -import { DataGridTypes } from 'devextreme-react/data-grid' +import type { DataGridTypes } from 'devextreme-react/data-grid' import { ColumnFormatDto, GridBoxOptionsDto, diff --git a/ui/src/views/list/GridFilterDialogs.tsx b/ui/src/views/list/GridFilterDialogs.tsx index b966d36..ac66d95 100644 --- a/ui/src/views/list/GridFilterDialogs.tsx +++ b/ui/src/views/list/GridFilterDialogs.tsx @@ -3,7 +3,7 @@ import { deleteListFormCustomization, postListFormCustomization, } from '@/services/list-form-customization.service' -import { DataGridRef } from 'devextreme-react/data-grid' +import type { DataGridRef } from 'devextreme-react/data-grid' import { Dispatch, MutableRefObject, SetStateAction, useState } from 'react' import CreatableSelect from 'react-select/creatable' import { ISelectBoxData } from './useFilters' diff --git a/ui/src/views/list/Pivot.tsx b/ui/src/views/list/Pivot.tsx index ca65e8f..1b92014 100644 --- a/ui/src/views/list/Pivot.tsx +++ b/ui/src/views/list/Pivot.tsx @@ -3,20 +3,25 @@ import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant' import { GridDto, ListFormCustomizationTypeEnum } from '@/proxy/form/models' import { postListFormCustomization } from '@/services/list-form-customization.service' import { useLocalization } from '@/utils/hooks/useLocalization' -import Chart, { ChartRef, CommonSeriesSettings, Size, Tooltip } from 'devextreme-react/chart' +import Chart, { + type ChartRef, + CommonSeriesSettings, + Size, + Tooltip, +} from 'devextreme-react/chart' import PivotGrid, { Export, FieldChooser, FieldPanel, HeaderFilter, LoadPanel, - PivotGridRef, - PivotGridTypes, + type PivotGridRef, + type PivotGridTypes, Scrolling, Search, } from 'devextreme-react/pivot-grid' import CustomStore from 'devextreme/data/custom_store' -import { Field } from 'devextreme/ui/pivot_grid/data_source' +import type { Field } from 'devextreme/ui/pivot_grid/data_source' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Helmet } from 'react-helmet' import { GridColumnData } from './GridColumnData' diff --git a/ui/src/views/list/SchedulerView.tsx b/ui/src/views/list/SchedulerView.tsx index 83b9d8d..6120344 100644 --- a/ui/src/views/list/SchedulerView.tsx +++ b/ui/src/views/list/SchedulerView.tsx @@ -13,7 +13,7 @@ import Scheduler, { Editing, Item, Resource, - SchedulerRef, + type SchedulerRef, Toolbar, View, } from 'devextreme-react/scheduler' @@ -31,7 +31,7 @@ import { Loading } from '@/components/shared' import { usePermission } from '@/utils/hooks/usePermission' import { RowMode, SimpleItemWithColData } from '../form/types' import { captionize } from 'devextreme/core/utils/inflector' -import { AppointmentFormOpeningEvent } from 'devextreme/ui/scheduler' +import type { AppointmentFormOpeningEvent } from 'devextreme/ui/scheduler' interface SchedulerViewProps { listFormCode: string diff --git a/ui/src/views/list/Tree.tsx b/ui/src/views/list/Tree.tsx index 86ae448..39112c9 100644 --- a/ui/src/views/list/Tree.tsx +++ b/ui/src/views/list/Tree.tsx @@ -29,8 +29,8 @@ import TreeListDx, { Selection, Sorting, Toolbar, - TreeListRef, - TreeListTypes, + type TreeListRef, + type TreeListTypes, } from 'devextreme-react/tree-list' import { Item } from 'devextreme-react/toolbar' import CustomStore from 'devextreme/data/custom_store' @@ -60,7 +60,7 @@ import { layoutTypes } from '../admin/listForm/edit/types' import { useListFormCustomDataSource } from './useListFormCustomDataSource' import { useListFormColumns } from './useListFormColumns' import { orderListToolbarItems } from './toolbarOrder' -import { DataType } from 'devextreme/common' +import type { DataType } from 'devextreme/common' import { useStoreState } from '@/store/store' import SubForms from '../form/SubForms' import { ImportDashboard } from '@/components/importManager/ImportDashboard' diff --git a/ui/src/views/list/Utils.ts b/ui/src/views/list/Utils.ts index c8cbad2..71a2c88 100644 --- a/ui/src/views/list/Utils.ts +++ b/ui/src/views/list/Utils.ts @@ -1,5 +1,5 @@ -import { DataType } from 'devextreme/common' -import { PivotGridDataType } from 'devextreme/ui/pivot_grid/data_source' +import type { DataType } from 'devextreme/common' +import type { PivotGridDataType } from 'devextreme/ui/pivot_grid/data_source' import { MULTIVALUE_DELIMITER } from '../../constants/app.constant' import { ChartSeriesDto } from '@/proxy/admin/charts/models' diff --git a/ui/src/views/list/toolbarOrder.ts b/ui/src/views/list/toolbarOrder.ts index 546ec1d..af8ba56 100644 --- a/ui/src/views/list/toolbarOrder.ts +++ b/ui/src/views/list/toolbarOrder.ts @@ -1,4 +1,4 @@ -import { ToolbarItem } from 'devextreme/ui/data_grid_types' +import type { ToolbarItem } from 'devextreme/ui/data_grid_types' const orderedToolbarItemNames = [ 'searchPanel', diff --git a/ui/src/views/list/useFilters.tsx b/ui/src/views/list/useFilters.tsx index f3cd5be..b6ebf1b 100644 --- a/ui/src/views/list/useFilters.tsx +++ b/ui/src/views/list/useFilters.tsx @@ -6,10 +6,10 @@ import { } from '@/proxy/form/models' import { getListFormCustomization } from '@/services/list-form-customization.service' import { useLocalization } from '@/utils/hooks/useLocalization' -import { DataGridRef } from 'devextreme-react/data-grid' -import { PivotGridRef } from 'devextreme-react/pivot-grid' -import { TreeListRef } from 'devextreme-react/tree-list' -import { ToolbarItem } from 'devextreme/ui/data_grid_types' +import type { DataGridRef } from 'devextreme-react/data-grid' +import type { PivotGridRef } from 'devextreme-react/pivot-grid' +import type { TreeListRef } from 'devextreme-react/tree-list' +import type { ToolbarItem } from 'devextreme/ui/data_grid_types' import dxDataGrid from 'devextreme/ui/data_grid' import dxPivotGrid from 'devextreme/ui/pivot_grid' import dxTreeList from 'devextreme/ui/tree_list' @@ -19,7 +19,7 @@ import { setGridPanelColor } from './Utils' import { usePermission } from '@/utils/hooks/usePermission' import { usePWA } from '@/utils/hooks/usePWA' import { ROUTES_ENUM } from '@/routes/route.constant' -import { GanttRef } from 'devextreme-react/cjs/gantt' +import type { GanttRef } from 'devextreme-react/cjs/gantt' import { useStoreState } from '@/store' import { MULTIVALUE_DELIMITER } from '@/constants/app.constant' import { createReportQueryString } from '../report/reportRouteParams' diff --git a/ui/src/views/list/useListFormColumns.ts b/ui/src/views/list/useListFormColumns.ts index fa55386..da30ad2 100644 --- a/ui/src/views/list/useListFormColumns.ts +++ b/ui/src/views/list/useListFormColumns.ts @@ -1,7 +1,7 @@ -import { DataGridTypes } from 'devextreme-react/data-grid' -import { DataType, HorizontalEdge, SortOrder, ValidationRule } from 'devextreme/common' +import type { DataGridTypes } from 'devextreme-react/data-grid' +import type { DataType, HorizontalEdge, SortOrder, ValidationRule } from 'devextreme/common' import CustomStore from 'devextreme/data/custom_store' -import { SelectedFilterOperation } from 'devextreme/ui/data_grid' +import type { SelectedFilterOperation } from 'devextreme/ui/data_grid' import { useCallback, useEffect } from 'react' import { useLocalization } from '@/utils/hooks/useLocalization' import { usePermission } from '@/utils/hooks/usePermission' diff --git a/ui/src/views/list/useListFormCustomDataSource.ts b/ui/src/views/list/useListFormCustomDataSource.ts index 12a6466..1e8af30 100644 --- a/ui/src/views/list/useListFormCustomDataSource.ts +++ b/ui/src/views/list/useListFormCustomDataSource.ts @@ -1,5 +1,5 @@ -import { DataGridRef } from 'devextreme-react/data-grid' -import { PivotGridRef } from 'devextreme-react/pivot-grid' +import type { DataGridRef } from 'devextreme-react/data-grid' +import type { PivotGridRef } from 'devextreme-react/pivot-grid' import CustomStore from 'devextreme/data/custom_store' import { MutableRefObject, useCallback } from 'react' import { layoutTypes, ListViewLayoutType } from '@/views/admin/listForm/edit/types' @@ -8,10 +8,10 @@ import { GridOptionsDto } from '@/proxy/form/models' import { GridColumnData } from './GridColumnData' import { dynamicFetch } from '@/services/form.service' import { MULTIVALUE_DELIMITER } from '@/constants/app.constant' -import { TreeListRef } from 'devextreme-react/cjs/tree-list' -import { GanttRef } from 'devextreme-react/cjs/gantt' -import { SchedulerRef } from 'devextreme-react/cjs/scheduler' -import { CardViewRef, CardViewTypes } from 'devextreme-react/cjs/card-view' +import type { TreeListRef } from 'devextreme-react/cjs/tree-list' +import type { GanttRef } from 'devextreme-react/cjs/gantt' +import type { SchedulerRef } from 'devextreme-react/cjs/scheduler' +import type { CardViewRef, CardViewTypes } from 'devextreme-react/cjs/card-view' const filteredGridPanelColor = 'rgba(10, 200, 10, 0.5)' // kullanici tanimli filtre ile filtrelenmis gridin paneline ait renk diff --git a/ui/src/views/list/useToolbar.tsx b/ui/src/views/list/useToolbar.tsx index 5876c66..5f4828f 100644 --- a/ui/src/views/list/useToolbar.tsx +++ b/ui/src/views/list/useToolbar.tsx @@ -3,8 +3,8 @@ import { GridDto, UiCommandButtonPositionTypeEnum, WorkflowDto } from '@/proxy/f import { dynamicFetch } from '@/services/form.service' import { useLocalization } from '@/utils/hooks/useLocalization' import { usePermission } from '@/utils/hooks/usePermission' -import { DataGridTypes } from 'devextreme-react/data-grid' -import { ToolbarItem } from 'devextreme/ui/data_grid_types' +import type { DataGridTypes } from 'devextreme-react/data-grid' +import type { ToolbarItem } from 'devextreme/ui/data_grid_types' import { useEffect, useState } from 'react' import type { ReactNode } from 'react' import { useDialogContext } from '../shared/DialogContext' diff --git a/ui/src/views/shared/SharedGridBoxEditor.tsx b/ui/src/views/shared/SharedGridBoxEditor.tsx index e1ba2c8..cacc13f 100644 --- a/ui/src/views/shared/SharedGridBoxEditor.tsx +++ b/ui/src/views/shared/SharedGridBoxEditor.tsx @@ -1,5 +1,5 @@ import Button from 'devextreme-react/button' -import DataGrid, { DataGridRef } from 'devextreme-react/data-grid' +import DataGrid, { type DataGridRef } from 'devextreme-react/data-grid' import DropDownBox from 'devextreme-react/drop-down-box' import { ReactElement, useRef } from 'react' import { GridBoxOptionsDto } from '@/proxy/form/models'