Ölü Paketler kaldırıldı
This commit is contained in:
parent
b517b7346e
commit
39ad5cab12
76 changed files with 1097 additions and 579 deletions
|
|
@ -12,7 +12,7 @@
|
|||
"code": "App.SiteManagement.General.NewMemberNotificationEmails",
|
||||
"nameKey": "App.SiteManagement.General.NewMemberNotificationEmails",
|
||||
"descriptionKey": "App.GeneralNewMemberNotificationEmails.NewMemberNotificationEmailsDescription",
|
||||
"defaultValue": "system@sozsoft.com",
|
||||
"defaultValue": "destek@sozsoft.com",
|
||||
"isVisibleToClients": false,
|
||||
"providers": "T|G|D",
|
||||
"isInherited": false,
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
"code": "App.SiteManagement.General.TimedLoginEmails",
|
||||
"nameKey": "App.SiteManagement.General.TimedLoginEmails",
|
||||
"descriptionKey": "App.GeneralTimedLoginEmails.TimedLoginEmailsDescription",
|
||||
"defaultValue": "system@sozsoft.com",
|
||||
"defaultValue": "destek@sozsoft.com",
|
||||
"isVisibleToClients": false,
|
||||
"providers": "T|G|D",
|
||||
"isInherited": false,
|
||||
|
|
|
|||
|
|
@ -6846,6 +6846,12 @@
|
|||
"tr": "Bültene Abone Ol",
|
||||
"en": "Subscribe to Newsletter"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.PublicBlog.SearchPlaceholder",
|
||||
"en": "Search blog posts...",
|
||||
"tr": "Blog yazıları ara..."
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.BlogSubscribe.SubscribeDesc",
|
||||
|
|
|
|||
180
ui/package-lock.json
generated
180
ui/package-lock.json
generated
|
|
@ -11,14 +11,9 @@
|
|||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@floating-ui/react": "^0.27.20",
|
||||
"@fullcalendar/daygrid": "^6.1.21",
|
||||
"@fullcalendar/interaction": "^6.1.21",
|
||||
"@fullcalendar/react": "^6.1.21",
|
||||
"@fullcalendar/timegrid": "^6.1.21",
|
||||
"@marsidev/react-turnstile": "^1.6.0",
|
||||
"@microsoft/signalr": "^10.0.11",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@tanstack/react-query": "^5.101.4",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"axios": "^1.19.0",
|
||||
"classnames": "^2.5.1",
|
||||
|
|
@ -33,30 +28,23 @@
|
|||
"formik": "^2.4.9",
|
||||
"framer-motion": "^13.1.0",
|
||||
"jspdf": "^4.2.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lodash": "^4.18.1",
|
||||
"react": "^19.2.8",
|
||||
"react-advanced-cropper": "^0.20.1",
|
||||
"react-arborist": "^3.16.0",
|
||||
"react-dom": "^19.2.8",
|
||||
"react-error-boundary": "^6.1.2",
|
||||
"react-highlight-words": "^0.21.0",
|
||||
"react-icons": "^5.7.0",
|
||||
"react-modal": "^3.16.3",
|
||||
"react-router-dom": "^7.18.2",
|
||||
"react-select": "^5.10.2",
|
||||
"yet-another-react-lightbox": "^3.32.2",
|
||||
"yup": "^1.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.5",
|
||||
"@tailwindcss/typography": "^0.5.20",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/lodash": "^4.17.25",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/react": "^19.2.18",
|
||||
"@types/react-dom": "^19.2.4",
|
||||
"@types/react-highlight-words": "^0.20.1",
|
||||
"@types/react-modal": "^3.16.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.67.0",
|
||||
"@typescript-eslint/parser": "^8.67.0",
|
||||
|
|
@ -2182,57 +2170,6 @@
|
|||
"integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@fullcalendar/core": {
|
||||
"version": "6.1.21",
|
||||
"resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.21.tgz",
|
||||
"integrity": "sha512-t3u/+sqh3Iq7TWtUnVLcGDUE6OWZh0UD3c04bI/l7lSLAgAKr3kngBmhHiQD1QXpwC8ZN5iNqG7a7gOVixhSKQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"preact": "~10.12.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@fullcalendar/daygrid": {
|
||||
"version": "6.1.21",
|
||||
"resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.21.tgz",
|
||||
"integrity": "sha512-QYb1y40RGYLlOxKpYWg8O+7njEnKnFG8Tt7qjnubJGR35s1phQg67E+81y2TyAbbm59p2JFOCXGDk9t6KDujIA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@fullcalendar/core": "~6.1.21"
|
||||
}
|
||||
},
|
||||
"node_modules/@fullcalendar/interaction": {
|
||||
"version": "6.1.21",
|
||||
"resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.21.tgz",
|
||||
"integrity": "sha512-WPYpqtljDWmU0Xm2cOtFrLlocgxv7cgkOppj34Q6OUUat8a6Cnd6kYo2JR+irP223PE5lBYHFNp1qh7SIpJc0w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@fullcalendar/core": "~6.1.21"
|
||||
}
|
||||
},
|
||||
"node_modules/@fullcalendar/react": {
|
||||
"version": "6.1.21",
|
||||
"resolved": "https://registry.npmjs.org/@fullcalendar/react/-/react-6.1.21.tgz",
|
||||
"integrity": "sha512-TLpmGUd5k/PMdCh8XbeFC9PW9wuGvMms1oCxWgXyjK3EFPXAAd0PLfcvwKdyxoAS5eK1E4RJFkjMHvsYHpimcg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@fullcalendar/core": "~6.1.21",
|
||||
"react": "^16.7.0 || ^17 || ^18 || ^19",
|
||||
"react-dom": "^16.7.0 || ^17 || ^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/@fullcalendar/timegrid": {
|
||||
"version": "6.1.21",
|
||||
"resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.21.tgz",
|
||||
"integrity": "sha512-2DnShx/jallGmb8QCkr6pAOu/zuPhJrP7+uTrAtSnbqsX7GF3lTxqSeNGkTQwsgF5g/ia8udhQ+JNYaE+TN1cQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fullcalendar/daygrid": "~6.1.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@fullcalendar/core": "~6.1.21"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
||||
|
|
@ -3266,32 +3203,6 @@
|
|||
"tailwindcss": ">=3.0.0 || >=4.0.0 || insiders"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "5.101.4",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.4.tgz",
|
||||
"integrity": "sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-query": {
|
||||
"version": "5.101.4",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.4.tgz",
|
||||
"integrity": "sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "5.101.4"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-table": {
|
||||
"version": "8.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz",
|
||||
|
|
@ -3402,13 +3313,6 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.17.25",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.25.tgz",
|
||||
"integrity": "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
|
||||
|
|
@ -3463,16 +3367,6 @@
|
|||
"@types/react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-highlight-words": {
|
||||
"version": "0.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-highlight-words/-/react-highlight-words-0.20.1.tgz",
|
||||
"integrity": "sha512-EgF6RaoibBurIhxk3X3d5xL0uMqjD7KtShvZ8S+d7/o6xZ+mQ4rct1a2E49vWA9r63OzrHCIyghqvQHASb/ERw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-modal": {
|
||||
"version": "3.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-modal/-/react-modal-3.16.3.tgz",
|
||||
|
|
@ -7669,12 +7563,6 @@
|
|||
"hermes-estree": "0.25.1"
|
||||
}
|
||||
},
|
||||
"node_modules/highlight-words-core": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/highlight-words-core/-/highlight-words-core-1.2.3.tgz",
|
||||
"integrity": "sha512-m1O9HW3/GNHxzSIXWw1wCNXXsgLlxrP0OI6+ycGUhiUHkikqW3OrwVHz+lxeNBe5yqLESdIcj8PowHQ2zLvUvQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/hoist-non-react-statics": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||
|
|
@ -8590,15 +8478,6 @@
|
|||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jwt-decode": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
|
||||
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
|
|
@ -10383,17 +10262,6 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/preact": {
|
||||
"version": "10.12.1",
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz",
|
||||
"integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
|
|
@ -10647,34 +10515,12 @@
|
|||
"react": "^19.2.8"
|
||||
}
|
||||
},
|
||||
"node_modules/react-error-boundary": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-6.1.2.tgz",
|
||||
"integrity": "sha512-3DpCr5HVdZ0caUjYE/kIHBEJN0mNP3ZCgf16c48uJ5TbWjorKVp+YG8W3XqlJ7vJAVNw6wNIImyPXmFydwmyng==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-fast-compare": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
|
||||
"integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-highlight-words": {
|
||||
"version": "0.21.0",
|
||||
"resolved": "https://registry.npmjs.org/react-highlight-words/-/react-highlight-words-0.21.0.tgz",
|
||||
"integrity": "sha512-SdWEeU9fIINArEPO1rO5OxPyuhdEKZQhHzZZP1ie6UeXQf+CjycT1kWaB+9bwGcVbR0NowuHK3RqgqNg6bgBDQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"highlight-words-core": "^1.2.0",
|
||||
"memoize-one": "^4.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^0.14.0 || ^15.0.0 || ^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-icons": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.7.0.tgz",
|
||||
|
|
@ -13240,32 +13086,6 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yet-another-react-lightbox": {
|
||||
"version": "3.32.2",
|
||||
"resolved": "https://registry.npmjs.org/yet-another-react-lightbox/-/yet-another-react-lightbox-3.32.2.tgz",
|
||||
"integrity": "sha512-F4HtHQfUNpvkj+AmECgWM4XRdCqMY5gXpKgOUx39+T+FyxLe8II4SK/pwMyYj2X54KH9lFSQeHYY1/GfYf3SdA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/igordanchenko"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16 || ^17 || ^18 || ^19",
|
||||
"@types/react-dom": "^16 || ^17 || ^18 || ^19",
|
||||
"react": "^16.8.0 || ^17 || ^18 || ^19",
|
||||
"react-dom": "^16.8.0 || ^17 || ^18 || ^19"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
|
|
|||
|
|
@ -29,14 +29,9 @@
|
|||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@floating-ui/react": "^0.27.20",
|
||||
"@fullcalendar/daygrid": "^6.1.21",
|
||||
"@fullcalendar/interaction": "^6.1.21",
|
||||
"@fullcalendar/react": "^6.1.21",
|
||||
"@fullcalendar/timegrid": "^6.1.21",
|
||||
"@marsidev/react-turnstile": "^1.6.0",
|
||||
"@microsoft/signalr": "^10.0.11",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@tanstack/react-query": "^5.101.4",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"axios": "^1.19.0",
|
||||
"classnames": "^2.5.1",
|
||||
|
|
@ -51,30 +46,23 @@
|
|||
"formik": "^2.4.9",
|
||||
"framer-motion": "^13.1.0",
|
||||
"jspdf": "^4.2.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lodash": "^4.18.1",
|
||||
"react": "^19.2.8",
|
||||
"react-advanced-cropper": "^0.20.1",
|
||||
"react-arborist": "^3.16.0",
|
||||
"react-dom": "^19.2.8",
|
||||
"react-error-boundary": "^6.1.2",
|
||||
"react-highlight-words": "^0.21.0",
|
||||
"react-icons": "^5.7.0",
|
||||
"react-modal": "^3.16.3",
|
||||
"react-router-dom": "^7.18.2",
|
||||
"react-select": "^5.10.2",
|
||||
"yet-another-react-lightbox": "^3.32.2",
|
||||
"yup": "^1.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.5",
|
||||
"@tailwindcss/typography": "^0.5.20",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/lodash": "^4.17.25",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/react": "^19.2.18",
|
||||
"@types/react-dom": "^19.2.4",
|
||||
"@types/react-highlight-words": "^0.20.1",
|
||||
"@types/react-modal": "^3.16.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.67.0",
|
||||
"@typescript-eslint/parser": "^8.67.0",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import Layout from '@/components/layouts'
|
||||
import Theme from '@/components/template/Theme'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { StoreProvider } from 'easy-peasy'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import { store } from './store'
|
||||
|
|
@ -10,15 +9,6 @@ import { registerServiceWorker } from './views/version/swRegistration'
|
|||
import { useEffect } from 'react'
|
||||
import DevExtremeBoundary from './components/devExtreme/DevExtremeBoundary'
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 30_000,
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
function App() {
|
||||
useEffect(() => {
|
||||
registerServiceWorker()
|
||||
|
|
@ -30,13 +20,11 @@ function App() {
|
|||
<BrowserRouter>
|
||||
<DynamicRoutesProvider>
|
||||
<ComponentProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<DevExtremeBoundary>
|
||||
<Theme>
|
||||
<Layout />
|
||||
</Theme>
|
||||
</DevExtremeBoundary>
|
||||
</QueryClientProvider>
|
||||
<DevExtremeBoundary>
|
||||
<Theme>
|
||||
<Layout />
|
||||
</Theme>
|
||||
</DevExtremeBoundary>
|
||||
</ComponentProvider>
|
||||
</DynamicRoutesProvider>
|
||||
</BrowserRouter>
|
||||
|
|
|
|||
|
|
@ -1,185 +0,0 @@
|
|||
import classNames from 'classnames'
|
||||
import Badge from '@/components/ui/Badge'
|
||||
import FullCalendar from '@fullcalendar/react'
|
||||
import dayGridPlugin from '@fullcalendar/daygrid'
|
||||
import timeGridPlugin from '@fullcalendar/timegrid'
|
||||
import interactionPlugin from '@fullcalendar/interaction'
|
||||
import { CalendarOptions } from '@fullcalendar/core'
|
||||
|
||||
type EventColors = Record<
|
||||
string,
|
||||
{
|
||||
bg: string
|
||||
text: string
|
||||
dot: string
|
||||
}
|
||||
>
|
||||
|
||||
interface CalendarViewProps extends CalendarOptions {
|
||||
wrapperClass?: string
|
||||
eventColors?: (colors: EventColors) => EventColors
|
||||
}
|
||||
|
||||
const defaultColorList: Record<
|
||||
string,
|
||||
{
|
||||
bg: string
|
||||
text: string
|
||||
dot: string
|
||||
}
|
||||
> = {
|
||||
red: {
|
||||
bg: 'bg-red-50 dark:bg-red-500/10',
|
||||
text: 'text-red-500 dark:text-red-100',
|
||||
dot: 'bg-red-500',
|
||||
},
|
||||
orange: {
|
||||
bg: 'bg-orange-50 dark:bg-orange-500/10',
|
||||
text: 'text-orange-500 dark:text-orange-100',
|
||||
dot: 'bg-orange-500',
|
||||
},
|
||||
amber: {
|
||||
bg: 'bg-amber-50 dark:bg-amber-500/10',
|
||||
text: 'text-amber-500 dark:text-amber-100',
|
||||
dot: 'bg-amber-500',
|
||||
},
|
||||
yellow: {
|
||||
bg: 'bg-yellow-50 dark:bg-yellow-500/10',
|
||||
text: 'text-yellow-500 dark:text-yellow-100',
|
||||
dot: 'bg-yellow-500',
|
||||
},
|
||||
lime: {
|
||||
bg: 'bg-lime-50 dark:bg-lime-500/10',
|
||||
text: 'text-lime-500 dark:text-lime-100',
|
||||
dot: 'bg-lime-500',
|
||||
},
|
||||
green: {
|
||||
bg: 'bg-green-50 dark:bg-green-500/10',
|
||||
text: 'text-green-500 dark:text-green-100',
|
||||
dot: 'bg-green-500',
|
||||
},
|
||||
emerald: {
|
||||
bg: 'bg-emerald-50 dark:bg-emerald-500/10',
|
||||
text: 'text-emerald-500 dark:text-emerald-100',
|
||||
dot: 'bg-emerald-500',
|
||||
},
|
||||
teal: {
|
||||
bg: 'bg-teal-50 dark:bg-teal-500/10',
|
||||
text: 'text-teal-500 dark:text-teal-100',
|
||||
dot: 'bg-teal-500',
|
||||
},
|
||||
cyan: {
|
||||
bg: 'bg-cyan-50 dark:bg-cyan-500/10',
|
||||
text: 'text-cyan-500 dark:text-cyan-100',
|
||||
dot: 'bg-cyan-500',
|
||||
},
|
||||
sky: {
|
||||
bg: 'bg-sky-50 dark:bg-sky-500/10',
|
||||
text: 'text-sky-500 dark:text-sky-100',
|
||||
dot: 'bg-sky-500',
|
||||
},
|
||||
blue: {
|
||||
bg: 'bg-blue-50 dark:bg-blue-500/10',
|
||||
text: 'text-blue-500 dark:text-blue-100',
|
||||
dot: 'bg-blue-500',
|
||||
},
|
||||
indigo: {
|
||||
bg: 'bg-indigo-50 dark:bg-indigo-500/10',
|
||||
text: 'text-indigo-500 dark:text-indigo-100',
|
||||
dot: 'bg-indigo-500',
|
||||
},
|
||||
purple: {
|
||||
bg: 'bg-purple-50 dark:bg-purple-500/10',
|
||||
text: 'text-purple-500 dark:text-purple-100',
|
||||
dot: 'bg-purple-500',
|
||||
},
|
||||
fuchsia: {
|
||||
bg: 'bg-fuchsia-50 dark:bg-fuchsia-500/10',
|
||||
text: 'text-fuchsia-500 dark:text-fuchsia-100',
|
||||
dot: 'bg-fuchsia-500',
|
||||
},
|
||||
pink: {
|
||||
bg: 'bg-pink-50 dark:bg-pink-500/10',
|
||||
text: 'text-pink-500 dark:text-pink-100',
|
||||
dot: 'bg-pink-500',
|
||||
},
|
||||
rose: {
|
||||
bg: 'bg-rose-50 dark:bg-rose-500/10',
|
||||
text: 'text-rose-500 dark:text-rose-100',
|
||||
dot: 'bg-rose-500',
|
||||
},
|
||||
}
|
||||
|
||||
const CalendarView = (props: CalendarViewProps) => {
|
||||
const {
|
||||
wrapperClass,
|
||||
eventColors = () => defaultColorList,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
return (
|
||||
<div className={classNames('calendar', wrapperClass)}>
|
||||
<FullCalendar
|
||||
initialView="dayGridMonth"
|
||||
headerToolbar={{
|
||||
left: 'title',
|
||||
center: '',
|
||||
right: 'dayGridMonth,timeGridWeek,timeGridDay prev,next',
|
||||
}}
|
||||
eventContent={(arg) => {
|
||||
const { extendedProps } = arg.event
|
||||
const { isEnd, isStart } = arg
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'custom-calendar-event',
|
||||
extendedProps.eventColor
|
||||
? (eventColors(defaultColorList) ||
|
||||
defaultColorList)[
|
||||
extendedProps.eventColor
|
||||
]?.bg
|
||||
: '',
|
||||
extendedProps.eventColor
|
||||
? (eventColors(defaultColorList) ||
|
||||
defaultColorList)[
|
||||
extendedProps.eventColor
|
||||
]?.text
|
||||
: '',
|
||||
isEnd &&
|
||||
!isStart &&
|
||||
'!rounded-tl-none !rounded-bl-none !rtl:rounded-tr-none !rtl:rounded-br-none',
|
||||
!isEnd &&
|
||||
isStart &&
|
||||
'!rounded-tr-none !rounded-br-none !rtl:rounded-tl-none !rtl:rounded-bl-none'
|
||||
)}
|
||||
>
|
||||
{!(isEnd && !isStart) && (
|
||||
<Badge
|
||||
className={classNames(
|
||||
'mr-1 rtl:ml-1',
|
||||
extendedProps.eventColor
|
||||
? (eventColors(defaultColorList) ||
|
||||
defaultColorList)[
|
||||
extendedProps.eventColor
|
||||
].dot
|
||||
: ''
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{!(isEnd && !isStart) && (
|
||||
<span>{arg.timeText}</span>
|
||||
)}
|
||||
<span className="font-semibold ml-1 rtl:mr-1">
|
||||
{arg.event.title}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
||||
{...rest}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CalendarView
|
||||
62
ui/src/components/shared/ErrorBoundary.tsx
Normal file
62
ui/src/components/shared/ErrorBoundary.tsx
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { Component } from 'react'
|
||||
import type { ComponentType, ErrorInfo, ReactNode } from 'react'
|
||||
|
||||
export interface FallbackProps {
|
||||
error: unknown
|
||||
resetErrorBoundary: () => void
|
||||
}
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
FallbackComponent: ComponentType<FallbackProps>
|
||||
children: ReactNode
|
||||
/** Bu değerler değiştiğinde sınır kendini sıfırlar (ör. route değişimi). */
|
||||
resetKeys?: unknown[]
|
||||
onError?: (error: unknown, info: ErrorInfo) => void
|
||||
}
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
error: unknown
|
||||
hasError: boolean
|
||||
}
|
||||
|
||||
const areKeysEqual = (a: unknown[] = [], b: unknown[] = []) =>
|
||||
a.length === b.length && a.every((item, index) => Object.is(item, b[index]))
|
||||
|
||||
/**
|
||||
* react-error-boundary yerine kullanılan yerel sınır.
|
||||
* Uygulamanın ihtiyaç duyduğu yüzey kadarı var: FallbackComponent,
|
||||
* resetErrorBoundary ve resetKeys.
|
||||
*/
|
||||
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
state: ErrorBoundaryState = { error: null, hasError: false }
|
||||
|
||||
static getDerivedStateFromError(error: unknown): ErrorBoundaryState {
|
||||
return { error, hasError: true }
|
||||
}
|
||||
|
||||
componentDidCatch(error: unknown, info: ErrorInfo) {
|
||||
this.props.onError?.(error, info)
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: ErrorBoundaryProps) {
|
||||
if (this.state.hasError && !areKeysEqual(prevProps.resetKeys, this.props.resetKeys)) {
|
||||
this.reset()
|
||||
}
|
||||
}
|
||||
|
||||
reset = () => {
|
||||
this.setState({ error: null, hasError: false })
|
||||
}
|
||||
|
||||
render() {
|
||||
const { FallbackComponent, children } = this.props
|
||||
|
||||
if (this.state.hasError) {
|
||||
return <FallbackComponent error={this.state.error} resetErrorBoundary={this.reset} />
|
||||
}
|
||||
|
||||
return children
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary
|
||||
44
ui/src/components/shared/Highlighter.tsx
Normal file
44
ui/src/components/shared/Highlighter.tsx
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { useMemo, Fragment } from 'react'
|
||||
|
||||
interface HighlighterProps {
|
||||
/** Vurgulanacak kelimeler; boş/whitespace olanlar yok sayılır. */
|
||||
searchWords: string[]
|
||||
textToHighlight: string
|
||||
highlightClassName?: string
|
||||
}
|
||||
|
||||
const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
|
||||
/**
|
||||
* react-highlight-words yerine kullanılan yerel bileşen.
|
||||
* Kullanılan tek senaryo: arama kelimelerini büyük/küçük harf duyarsız
|
||||
* eşleyip <mark> ile işaretlemek (paketteki `autoEscape` davranışı varsayılan).
|
||||
*/
|
||||
const Highlighter = ({ searchWords, textToHighlight, highlightClassName }: HighlighterProps) => {
|
||||
const parts = useMemo(() => {
|
||||
const words = searchWords.map((word) => word?.trim()).filter(Boolean)
|
||||
if (words.length === 0 || !textToHighlight) return null
|
||||
|
||||
const pattern = new RegExp(`(${words.map(escapeRegExp).join('|')})`, 'gi')
|
||||
return textToHighlight.split(pattern)
|
||||
}, [searchWords, textToHighlight])
|
||||
|
||||
if (!parts) return <>{textToHighlight}</>
|
||||
|
||||
// split içindeki tek sayılı indeksler yakalanan gruplardır.
|
||||
return (
|
||||
<>
|
||||
{parts.map((part, index) =>
|
||||
index % 2 === 1 ? (
|
||||
<mark key={index} className={highlightClassName}>
|
||||
{part}
|
||||
</mark>
|
||||
) : (
|
||||
<Fragment key={index}>{part}</Fragment>
|
||||
),
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Highlighter
|
||||
|
|
@ -3,7 +3,6 @@ export { default as AdaptableCard } from './AdaptableCard'
|
|||
export { default as Affix } from './Affix'
|
||||
export { default as AuthorityCheck } from './AuthorityCheck'
|
||||
export { default as PermissionCheck } from './PermissionCheck'
|
||||
export { default as CalendarView } from './CalendarView'
|
||||
export { default as ConfirmDialog } from './ConfirmDialog'
|
||||
export { default as Container } from './Container'
|
||||
export { default as DataTable } from './DataTable'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import navigationIcon from '@/proxy/menus/navigation-icon.config'
|
||||
import { useNavigationIcons } from '@/proxy/menus/navigation-icon.config'
|
||||
import type { ElementType, ComponentPropsWithRef } from 'react'
|
||||
import React from 'react'
|
||||
import { FaQuestionCircle } from 'react-icons/fa'
|
||||
|
|
@ -18,6 +18,8 @@ export const Icon = <T extends ElementType>({
|
|||
}
|
||||
|
||||
const HorizontalMenuIcon = ({ icon }: HorizontalMenuIconProps) => {
|
||||
const navigationIcon = useNavigationIcons()
|
||||
|
||||
if (typeof icon !== 'string' || !icon) {
|
||||
return <></>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import navigationIcon from '@/proxy/menus/navigation-icon.config'
|
||||
import { useNavigationIcons } from '@/proxy/menus/navigation-icon.config'
|
||||
import MenuItem from '@/components/ui/MenuItem'
|
||||
import HorizontalMenuNavLink from './HorizontalMenuNavLink'
|
||||
import type { NavMode } from '@/proxy/theme/models'
|
||||
|
|
@ -20,6 +20,7 @@ export type HorizontalMenuItemProps = {
|
|||
|
||||
const HorizontalMenuItem = ({ nav, isLink, manuVariant }: HorizontalMenuItemProps) => {
|
||||
const { title, icon, path, isExternalLink } = nav
|
||||
const navigationIcon = useNavigationIcons()
|
||||
|
||||
// Dinamik ikon render etmek için createElement kullanıyoruz
|
||||
const renderIcon =
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import withHeaderItem from '@/utils/hoc/withHeaderItem'
|
|||
import useThemeClass from '@/utils/hooks/useThemeClass'
|
||||
import classNames from 'classnames'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import Highlighter from 'react-highlight-words'
|
||||
import Highlighter from '@/components/shared/Highlighter'
|
||||
import { FaChevronRight, FaSearch } from 'react-icons/fa'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { PermissionCheck } from '../shared'
|
||||
|
|
@ -61,7 +61,6 @@ const ListItem = (props: {
|
|||
</div> */}
|
||||
<div className="text-gray-900 dark:text-gray-300">
|
||||
<Highlighter
|
||||
autoEscape
|
||||
highlightClassName={classNames(
|
||||
textTheme,
|
||||
'underline bg-transparent font-semibold dark:text-white',
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
import StackedSideNavMini, { SelectedMenuItem } from './StackedSideNavMini'
|
||||
import StackedSideNavSecondary from './StackedSideNavSecondary'
|
||||
import useResponsive from '@/utils/hooks/useResponsive'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import { isEmpty } from '@/utils/collection'
|
||||
import { useStoreState } from '@/store'
|
||||
|
||||
const stackedSideNavDefaultStyle = {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { PermissionCheck } from '@/components/shared'
|
|||
import Logo from '@/components/template/Logo'
|
||||
import Menu from '@/components/ui/Menu'
|
||||
import ScrollBar from '@/components/ui/ScrollBar'
|
||||
import navigationIcon from '@/proxy/menus/navigation-icon.config'
|
||||
import { useNavigationIcons } from '@/proxy/menus/navigation-icon.config'
|
||||
import { NAV_ITEM_TYPE_ITEM } from '@/constants/navigation.constant'
|
||||
import {
|
||||
NAV_MODE_DARK,
|
||||
|
|
@ -15,7 +15,7 @@ import {
|
|||
} from '@/constants/theme.constant'
|
||||
import { useStoreState } from '@/store'
|
||||
import useMenuActive from '@/utils/hooks/useMenuActive'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import { isEmpty } from '@/utils/collection'
|
||||
import React, { useEffect } from 'react'
|
||||
import { FaQuestionCircle } from 'react-icons/fa'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
|
|
@ -41,6 +41,7 @@ interface StackedSideNavMiniProps extends CommonProps {
|
|||
|
||||
const StackedSideNavMini = (props: StackedSideNavMiniProps) => {
|
||||
const { pathname } = useLocation()
|
||||
const navigationIcon = useNavigationIcons()
|
||||
|
||||
const {
|
||||
navMode,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import navigationIcon from '@/proxy/menus/navigation-icon.config'
|
||||
import { useNavigationIcons } from '@/proxy/menus/navigation-icon.config'
|
||||
import type { ElementType, ComponentPropsWithRef } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
|
|
@ -17,6 +17,8 @@ export const Icon = <T extends ElementType>({
|
|||
}
|
||||
|
||||
const VerticalMenuIcon = ({ icon }: VerticalMenuIconProps) => {
|
||||
const navigationIcon = useNavigationIcons()
|
||||
|
||||
// Eğer icon boş veya string değilse, boş bir öğe döndür
|
||||
if (typeof icon !== 'string' || !icon) {
|
||||
return <></>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useState, forwardRef } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import useTimeout from '../hooks/useTimeout'
|
||||
import { FaCheckCircle, FaInfoCircle, FaExclamation, FaTimesCircle } from 'react-icons/fa'
|
||||
import { FaCheckCircle, FaInfoCircle, FaExclamation, FaTimesCircle } from '@/components/ui/icons'
|
||||
import { motion } from 'framer-motion'
|
||||
import CloseButton from '../CloseButton'
|
||||
import StatusIcon from '../StatusIcon'
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { forwardRef, useState, useCallback, useMemo, useEffect } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import { CheckboxGroupContextProvider } from './context'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import remove from 'lodash/remove'
|
||||
import { cloneDeep, remove } from '@/utils/collection'
|
||||
import shallowEqual from '../utils/shallowEqual'
|
||||
import type { CommonProps } from '../@types/common'
|
||||
import type { CheckboxGroupValue, CheckboxValue } from './context'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { forwardRef } from 'react'
|
||||
import { FaTimes } from 'react-icons/fa';
|
||||
import { FaTimes } from '@/components/ui/icons';
|
||||
import classNames from 'classnames'
|
||||
import type { CommonProps } from '../@types/common'
|
||||
import type { MouseEvent } from 'react'
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import dayjs from 'dayjs'
|
|||
import localizedFormat from 'dayjs/plugin/localizedFormat'
|
||||
import { Input } from '../Input'
|
||||
import useMergedRef from '../hooks/useMergeRef'
|
||||
import { FaCalendarAlt } from 'react-icons/fa';
|
||||
import { FaCalendarAlt } from '@/components/ui/icons';
|
||||
import CloseButton from '../CloseButton'
|
||||
import type { CommonProps, TypeAttributes } from '../@types/common'
|
||||
import type {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import classNames from 'classnames'
|
||||
import { Button } from '../../Button'
|
||||
import { FaChevronRight, FaChevronLeft } from 'react-icons/fa';
|
||||
import { FaChevronRight, FaChevronLeft } from '@/components/ui/icons';
|
||||
import type { CommonProps } from '../../@types/common'
|
||||
|
||||
export interface HeaderProps extends CommonProps {
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ export interface DialogProps extends ReactModal.Props {
|
|||
contentClassName?: string
|
||||
draggable?: boolean
|
||||
height?: string | number
|
||||
onClose?: (e: MouseEvent<HTMLSpanElement>) => void
|
||||
onClose?: (e: MouseEvent<HTMLElement>) => void
|
||||
width?: string | number
|
||||
showWindowControls?: boolean
|
||||
onMaximize?: () => void
|
||||
|
|
@ -139,12 +139,12 @@ const Dialog = (props: DialogProps) => {
|
|||
})
|
||||
}, [currentSize.width, currentSize.height])
|
||||
|
||||
const onCloseClick = (e: MouseEvent<HTMLSpanElement>) => {
|
||||
const onCloseClick = (e: MouseEvent<HTMLElement>) => {
|
||||
onClose?.(e)
|
||||
}
|
||||
|
||||
const handleMaximize = useCallback(
|
||||
(e: MouseEvent<HTMLSpanElement>) => {
|
||||
(e: MouseEvent<HTMLElement>) => {
|
||||
e.stopPropagation()
|
||||
if (!isMaximized) {
|
||||
setIsMaximized(true)
|
||||
|
|
@ -156,7 +156,7 @@ const Dialog = (props: DialogProps) => {
|
|||
)
|
||||
|
||||
const handleRestore = useCallback(
|
||||
(e: MouseEvent<HTMLSpanElement>) => {
|
||||
(e: MouseEvent<HTMLElement>) => {
|
||||
e.stopPropagation()
|
||||
if (isMaximized) {
|
||||
setIsMaximized(false)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
isValidElement,
|
||||
cloneElement,
|
||||
} from 'react'
|
||||
import isNil from 'lodash/isNil'
|
||||
import { isNil } from '@/utils/collection'
|
||||
import chainedFunction from '../utils/chainedFunction'
|
||||
import DropdownContext from './context/dropdownContext'
|
||||
import MenuContext from './context/menuContext'
|
||||
|
|
@ -19,7 +19,7 @@ import DropdownMenuContext, {
|
|||
DropdownMenuContextProvider,
|
||||
} from './context/dropdownMenuContext'
|
||||
import classNames from 'classnames'
|
||||
import { FaChevronRight, FaChevronLeft } from 'react-icons/fa';
|
||||
import { FaChevronRight, FaChevronLeft } from '@/components/ui/icons';
|
||||
import MenuItem from '../MenuItem'
|
||||
import { DROPDOWN_ITEM_TYPE } from '../utils/constants'
|
||||
import type { CommonProps } from '../@types/common'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { forwardRef } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import { FaChevronDown, FaChevronUp, FaChevronRight, FaChevronLeft } from 'react-icons/fa';
|
||||
import { FaChevronDown, FaChevronUp, FaChevronRight, FaChevronLeft } from '@/components/ui/icons';
|
||||
import type { CommonProps } from '../@types/common'
|
||||
import type { DropdownPlacement } from '../@types/placement'
|
||||
import type { ReactNode } from 'react'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { createContext, useState, useRef, useCallback } from 'react'
|
||||
import isNil from 'lodash/isNil'
|
||||
import { isNil } from '@/utils/collection'
|
||||
|
||||
type Item = {
|
||||
element: HTMLElement | null
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import classNames from 'classnames'
|
||||
import get from 'lodash/get'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import isNil from 'lodash/isNil'
|
||||
import { get, isEmpty, isNil } from '@/utils/collection'
|
||||
import type { ElementType, HTMLInputTypeAttribute, InputHTMLAttributes, ReactNode } from 'react'
|
||||
import { forwardRef, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import type { CommonProps, TypeAttributes } from '../@types/common'
|
||||
|
|
@ -75,7 +73,7 @@ const Input = forwardRef<ElementType | HTMLInputElement, InputProps>((props, ref
|
|||
const { touched, errors } = form
|
||||
const touchedField = get(touched, field.name)
|
||||
const errorField = get(errors, field.name)
|
||||
validate = touchedField && errorField
|
||||
validate = !!touchedField && !!errorField
|
||||
}
|
||||
if (typeof invalid === 'boolean') {
|
||||
validate = invalid
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { CollapseContextProvider } from './context/collapseContext'
|
|||
import classNames from 'classnames'
|
||||
import { motion } from 'framer-motion'
|
||||
import MenuContext from './context/menuContext'
|
||||
import { FaChevronDown } from 'react-icons/fa';
|
||||
import { FaChevronDown } from '@/components/ui/icons';
|
||||
import type { CommonProps } from '../@types/common'
|
||||
import type { ReactNode, MouseEvent } from 'react'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import classNames from 'classnames'
|
||||
import { FaChevronRight } from 'react-icons/fa';
|
||||
import { FaChevronRight } from '@/components/ui/icons';
|
||||
import type { CommonProps } from '../@types/common'
|
||||
import type { MouseEvent } from 'react'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState, useEffect, useCallback, useMemo } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import { FaChevronCircleLeft, FaEllipsisH, FaChevronCircleRight } from 'react-icons/fa';
|
||||
import { FaChevronCircleLeft, FaEllipsisH, FaChevronCircleRight } from '@/components/ui/icons';
|
||||
|
||||
const PAGER_COUNT = 7
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import classNames from 'classnames'
|
||||
import { FaChevronLeft } from 'react-icons/fa';
|
||||
import { FaChevronLeft } from '@/components/ui/icons';
|
||||
import type { CommonProps } from '../@types/common'
|
||||
import type { MouseEvent } from 'react'
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,11 @@ import ReactSelect, { components as ReactSelectComponents } from 'react-select'
|
|||
import CreatableSelect from 'react-select/creatable'
|
||||
import AsyncSelect from 'react-select/async'
|
||||
import { TW_COLORS, BORDER_RADIUS } from '@/utils/tailwind'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import get from 'lodash/get'
|
||||
import { isEmpty, get } from '@/utils/collection'
|
||||
import { useConfig } from '../ConfigProvider'
|
||||
import { useForm } from '../Form/context'
|
||||
import { useInputGroup } from '../InputGroup/context'
|
||||
import { FaCheck, FaChevronDown, FaTimes } from 'react-icons/fa'
|
||||
import { FaCheck, FaChevronDown, FaTimes } from '@/components/ui/icons'
|
||||
import Spinner from '../Spinner/Spinner'
|
||||
import type { CommonProps, TypeAttributes } from '../@types/common'
|
||||
import type {
|
||||
|
|
@ -184,7 +183,7 @@ function SelectBase<
|
|||
const touchedField = get(touched, field.name)
|
||||
const errorField = get(errors, field.name)
|
||||
|
||||
isInvalid = touchedField && errorField
|
||||
isInvalid = !!touchedField && !!errorField
|
||||
}
|
||||
|
||||
const getBoxShadow = (state: ControlProps<Option, IsMulti, Group>) => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { forwardRef } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import { useConfig } from '../ConfigProvider'
|
||||
import { FaSpinner } from 'react-icons/fa';
|
||||
import { FaSpinner } from '@/components/ui/icons';
|
||||
import type { CommonProps } from '../@types/common'
|
||||
import type { ElementType } from 'react'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { FaCheckCircle, FaInfoCircle, FaExclamation, FaTimesCircle } from 'react-icons/fa';
|
||||
import { FaCheckCircle, FaInfoCircle, FaExclamation, FaTimesCircle } from '@/components/ui/icons';
|
||||
import type { TypeAttributes, CommonProps } from '../@types/common'
|
||||
import type { ReactElement, ReactNode } from 'react'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { forwardRef } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import { useConfig } from '../ConfigProvider'
|
||||
import { FaCheck, FaTimes } from 'react-icons/fa';
|
||||
import { FaCheck, FaTimes } from '@/components/ui/icons';
|
||||
import { STEPS_STATUS } from '../utils/constants'
|
||||
import type { CommonProps, StepStatus } from '../@types/common'
|
||||
import type { ReactNode } from 'react'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useConfig } from '../ConfigProvider'
|
||||
import { FaSort, FaSortUp, FaSortDown } from 'react-icons/fa'
|
||||
import { FaSort, FaSortUp, FaSortDown } from '@/components/ui/icons'
|
||||
|
||||
export type SorterProps = { sort?: boolean | 'asc' | 'desc' }
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
createAmPmHandler,
|
||||
createTimeHandler,
|
||||
} from './utils'
|
||||
import { FaClock } from 'react-icons/fa';
|
||||
import { FaClock } from '@/components/ui/icons';
|
||||
import type { CommonProps, TypeAttributes } from '../@types/common'
|
||||
import type { ReactNode, RefObject, Ref } from 'react'
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import useMergedRef from '../hooks/useMergeRef'
|
|||
import useDidUpdate from '../hooks/useDidUpdate'
|
||||
import TimeInput from './TimeInput'
|
||||
import CloseButton from '../CloseButton'
|
||||
import { FaClock } from 'react-icons/fa';
|
||||
import { FaClock } from '@/components/ui/icons';
|
||||
import Input from '../Input/Input'
|
||||
import type { CommonProps, TypeAttributes } from '../@types/common'
|
||||
import type { RefObject, Ref, ReactNode } from 'react'
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import {
|
|||
FaCaretLeft,
|
||||
FaCaretUp,
|
||||
FaCaretRight,
|
||||
} from 'react-icons/fa';
|
||||
} from '@/components/ui/icons';
|
||||
|
||||
export type ArrowPlacement =
|
||||
| 'top'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { FaFilePdf, FaFileArchive, FaFile } from 'react-icons/fa';
|
||||
import { FaFilePdf, FaFileArchive, FaFile } from '@/components/ui/icons';
|
||||
import type { CommonProps } from '../@types/common'
|
||||
|
||||
const BYTE = 1000
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { forwardRef, useRef, useState, useCallback, useEffect } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import { useConfig } from '../ConfigProvider'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import { cloneDeep } from '@/utils/collection'
|
||||
import FileItem from './FileItem'
|
||||
import Button from '../Button/Button'
|
||||
import CloseButton from '../CloseButton'
|
||||
|
|
|
|||
|
|
@ -1,63 +1,87 @@
|
|||
import { forwardRef } from 'react'
|
||||
import { FaTimes, FaWindowMaximize, FaWindowRestore } from 'react-icons/fa'
|
||||
import { FaTimes, FaWindowMaximize, FaWindowRestore } from '@/components/ui/icons'
|
||||
import classNames from 'classnames'
|
||||
import type { CommonProps } from '../@types/common'
|
||||
import type { MouseEvent } from 'react'
|
||||
|
||||
export interface WindowControlsProps extends CommonProps {
|
||||
absolute?: boolean
|
||||
defaultStyle?: boolean
|
||||
isMaximized?: boolean
|
||||
onClose?: (e: MouseEvent<HTMLSpanElement>) => void
|
||||
onMaximize?: (e: MouseEvent<HTMLSpanElement>) => void
|
||||
onRestore?: (e: MouseEvent<HTMLSpanElement>) => void
|
||||
onClose?: (e: MouseEvent<HTMLElement>) => void
|
||||
onMaximize?: (e: MouseEvent<HTMLElement>) => void
|
||||
onRestore?: (e: MouseEvent<HTMLElement>) => void
|
||||
showMaximize?: boolean
|
||||
showRestore?: boolean
|
||||
showClose?: boolean
|
||||
maximizeTitle?: string
|
||||
restoreTitle?: string
|
||||
closeTitle?: string
|
||||
}
|
||||
|
||||
const buttonClass = 'window-controls-btn'
|
||||
|
||||
const WindowControls = forwardRef<HTMLDivElement, WindowControlsProps>((props, ref) => {
|
||||
const {
|
||||
absolute,
|
||||
className,
|
||||
defaultStyle,
|
||||
isMaximized,
|
||||
onClose,
|
||||
onMaximize,
|
||||
onRestore,
|
||||
showMaximize = true,
|
||||
showRestore = true,
|
||||
showClose = true,
|
||||
maximizeTitle = 'Maximize',
|
||||
restoreTitle = 'Restore',
|
||||
closeTitle = 'Close',
|
||||
...rest
|
||||
} = props
|
||||
|
||||
const windowControlsAbsoluteClass = 'absolute z-10'
|
||||
|
||||
const windowControlsClass = classNames(
|
||||
'window-controls flex items-center gap-2',
|
||||
defaultStyle && 'window-controls-default',
|
||||
absolute && windowControlsAbsoluteClass,
|
||||
absolute && 'absolute z-10 top-3',
|
||||
className
|
||||
)
|
||||
|
||||
const buttonClass = 'close-btn cursor-pointer hover:opacity-70 transition-opacity'
|
||||
const stopPropagation = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={windowControlsClass} {...rest} ref={ref}>
|
||||
{showMaximize && !isMaximized && (
|
||||
<span className={buttonClass} role="button" onClick={onMaximize} title="Maximize">
|
||||
<FaWindowMaximize />
|
||||
</span>
|
||||
)}
|
||||
{showRestore && isMaximized && (
|
||||
<span className={buttonClass} role="button" onClick={onRestore} title="Restore">
|
||||
<FaWindowRestore />
|
||||
</span>
|
||||
)}
|
||||
{showMaximize &&
|
||||
(isMaximized ? (
|
||||
<button
|
||||
type="button"
|
||||
className={buttonClass}
|
||||
title={restoreTitle}
|
||||
aria-label={restoreTitle}
|
||||
onMouseDown={stopPropagation}
|
||||
onClick={onRestore}
|
||||
>
|
||||
<FaWindowRestore />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className={buttonClass}
|
||||
title={maximizeTitle}
|
||||
aria-label={maximizeTitle}
|
||||
onMouseDown={stopPropagation}
|
||||
onClick={onMaximize}
|
||||
>
|
||||
<FaWindowMaximize />
|
||||
</button>
|
||||
))}
|
||||
{showClose && (
|
||||
<span className={buttonClass} role="button" onClick={onClose} title="Close">
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(buttonClass, 'window-controls-close')}
|
||||
title={closeTitle}
|
||||
aria-label={closeTitle}
|
||||
onMouseDown={stopPropagation}
|
||||
onClick={onClose}
|
||||
>
|
||||
<FaTimes />
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useRef } from 'react'
|
||||
import uniqueId from 'lodash/uniqueId'
|
||||
import { uniqueId } from '@/utils/collection'
|
||||
import createUID from '../utils/createUid'
|
||||
|
||||
export default function useUniqueId(prefix = '', len = 10) {
|
||||
|
|
|
|||
412
ui/src/components/ui/icons.tsx
Normal file
412
ui/src/components/ui/icons.tsx
Normal file
|
|
@ -0,0 +1,412 @@
|
|||
import { GenIcon } from 'react-icons/lib'
|
||||
import type { IconType } from 'react-icons/lib'
|
||||
|
||||
/**
|
||||
* Platform kabuğunun (ui/ bileşenleri) kullandığı Font Awesome ikonları.
|
||||
*
|
||||
* NEDEN BURADA: menü ikonları veritabanından ada göre çözüldüğü için
|
||||
* `navigation-icon.config` tüm FA setini namespace olarak yüklemek zorunda.
|
||||
* Bu, `react-icons/fa` modülünü tree-shake edilemez hale getiriyor; sonuçta
|
||||
* TEK bir ikon import eden dosya bile ~1.28 MB'lık fa chunk'ını sürüklüyordu.
|
||||
* ui/ bileşenleri uygulamanın her yerinde (ve ilk boyamada) kullanıldığından,
|
||||
* kullandıkları 24 ikon buraya birebir aynı tanımlarla gömüldü (~10 KB) ve
|
||||
* kabuk artık fa chunk'ına hiç dokunmuyor.
|
||||
*
|
||||
* Render çıktısı react-icons ile aynıdır: aynı `GenIcon` fabrikası, aynı
|
||||
* viewBox/path verisi, aynı prop davranışı (size, color, title, IconContext).
|
||||
*
|
||||
* Yeni bir ikon gerekirse: node_modules/react-icons/fa/index.mjs içindeki
|
||||
* tanımı buraya aynı biçimde kopyalayın.
|
||||
*/
|
||||
|
||||
export const FaCaretDown: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 320 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaCaretLeft: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 192 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M192 127.338v257.324c0 17.818-21.543 26.741-34.142 14.142L29.196 270.142c-7.81-7.81-7.81-20.474 0-28.284l128.662-128.662c12.599-12.6 34.142-3.676 34.142 14.142z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaCaretRight: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 192 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M0 384.662V127.338c0-17.818 21.543-26.741 34.142-14.142l128.662 128.662c7.81 7.81 7.81 20.474 0 28.284L34.142 398.804C21.543 411.404 0 402.48 0 384.662z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaCaretUp: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 320 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M288.662 352H31.338c-17.818 0-26.741-21.543-14.142-34.142l128.662-128.662c7.81-7.81 20.474-7.81 28.284 0l128.662 128.662c12.6 12.599 3.676 34.142-14.142 34.142z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaCalendarAlt: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 448 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M0 464c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V192H0v272zm320-196c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12h-40c-6.6 0-12-5.4-12-12v-40zm0 128c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12h-40c-6.6 0-12-5.4-12-12v-40zM192 268c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12h-40c-6.6 0-12-5.4-12-12v-40zm0 128c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12h-40c-6.6 0-12-5.4-12-12v-40zM64 268c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12H76c-6.6 0-12-5.4-12-12v-40zm0 128c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12H76c-6.6 0-12-5.4-12-12v-40zM400 64h-48V16c0-8.8-7.2-16-16-16h-32c-8.8 0-16 7.2-16 16v48H160V16c0-8.8-7.2-16-16-16h-32c-8.8 0-16 7.2-16 16v48H48C21.5 64 0 85.5 0 112v48h448v-48c0-26.5-21.5-48-48-48z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaCheck: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 512 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaCheckCircle: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 512 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaChevronCircleLeft: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 512 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M256 504C119 504 8 393 8 256S119 8 256 8s248 111 248 248-111 248-248 248zM142.1 273l135.5 135.5c9.4 9.4 24.6 9.4 33.9 0l17-17c9.4-9.4 9.4-24.6 0-33.9L226.9 256l101.6-101.6c9.4-9.4 9.4-24.6 0-33.9l-17-17c-9.4-9.4-24.6-9.4-33.9 0L142.1 239c-9.4 9.4-9.4 24.6 0 34z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaChevronCircleRight: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 512 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M256 8c137 0 248 111 248 248S393 504 256 504 8 393 8 256 119 8 256 8zm113.9 231L234.4 103.5c-9.4-9.4-24.6-9.4-33.9 0l-17 17c-9.4 9.4-9.4 24.6 0 33.9L285.1 256 183.5 357.6c-9.4 9.4-9.4 24.6 0 33.9l17 17c9.4 9.4 24.6 9.4 33.9 0L369.9 273c9.4-9.4 9.4-24.6 0-34z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaChevronDown: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 448 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M207.029 381.476L12.686 187.132c-9.373-9.373-9.373-24.569 0-33.941l22.667-22.667c9.357-9.357 24.522-9.375 33.901-.04L224 284.505l154.745-154.021c9.379-9.335 24.544-9.317 33.901.04l22.667 22.667c9.373 9.373 9.373 24.569 0 33.941L240.971 381.476c-9.373 9.372-24.569 9.372-33.942 0z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaChevronLeft: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 320 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M34.52 239.03L228.87 44.69c9.37-9.37 24.57-9.37 33.94 0l22.67 22.67c9.36 9.36 9.37 24.52.04 33.9L131.49 256l154.02 154.75c9.34 9.38 9.32 24.54-.04 33.9l-22.67 22.67c-9.37 9.37-24.57 9.37-33.94 0L34.52 272.97c-9.37-9.37-9.37-24.57 0-33.94z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaChevronRight: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 320 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaChevronUp: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 448 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M240.971 130.524l194.343 194.343c9.373 9.373 9.373 24.569 0 33.941l-22.667 22.667c-9.357 9.357-24.522 9.375-33.901.04L224 227.495 69.255 381.516c-9.379 9.335-24.544 9.317-33.901-.04l-22.667-22.667c-9.373-9.373-9.373-24.569 0-33.941L207.03 130.525c9.372-9.373 24.568-9.373 33.941-.001z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaClock: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 512 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M256,8C119,8,8,119,8,256S119,504,256,504,504,393,504,256,393,8,256,8Zm92.49,313h0l-20,25a16,16,0,0,1-22.49,2.5h0l-67-49.72a40,40,0,0,1-15-31.23V112a16,16,0,0,1,16-16h32a16,16,0,0,1,16,16V256l58,42.5A16,16,0,0,1,348.49,321Z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaEllipsisH: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 512 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaExclamation: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 192 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M176 432c0 44.112-35.888 80-80 80s-80-35.888-80-80 35.888-80 80-80 80 35.888 80 80zM25.26 25.199l13.6 272C39.499 309.972 50.041 320 62.83 320h66.34c12.789 0 23.331-10.028 23.97-22.801l13.6-272C167.425 11.49 156.496 0 142.77 0H49.23C35.504 0 24.575 11.49 25.26 25.199z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaFile: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 384 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M224 136V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zm160-14.1v6.1H256V0h6.1c6.4 0 12.5 2.5 17 7l97.9 98c4.5 4.5 7 10.6 7 16.9z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaFileArchive: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 384 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M377 105L279.1 7c-4.5-4.5-10.6-7-17-7H256v128h128v-6.1c0-6.3-2.5-12.4-7-16.9zM128.4 336c-17.9 0-32.4 12.1-32.4 27 0 15 14.6 27 32.5 27s32.4-12.1 32.4-27-14.6-27-32.5-27zM224 136V0h-63.6v32h-32V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zM95.9 32h32v32h-32zm32.3 384c-33.2 0-58-30.4-51.4-62.9L96.4 256v-32h32v-32h-32v-32h32v-32h-32V96h32V64h32v32h-32v32h32v32h-32v32h32v32h-32v32h22.1c5.7 0 10.7 4.1 11.8 9.7l17.3 87.7c6.4 32.4-18.4 62.6-51.4 62.6z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaFilePdf: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 384 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M181.9 256.1c-5-16-4.9-46.9-2-46.9 8.4 0 7.6 36.9 2 46.9zm-1.7 47.2c-7.7 20.2-17.3 43.3-28.4 62.7 18.3-7 39-17.2 62.9-21.9-12.7-9.6-24.9-23.4-34.5-40.8zM86.1 428.1c0 .8 13.2-5.4 34.9-40.2-6.7 6.3-29.1 24.5-34.9 40.2zM248 160h136v328c0 13.3-10.7 24-24 24H24c-13.3 0-24-10.7-24-24V24C0 10.7 10.7 0 24 0h200v136c0 13.2 10.8 24 24 24zm-8 171.8c-20-12.2-33.3-29-42.7-53.8 4.5-18.5 11.6-46.6 6.2-64.2-4.7-29.4-42.4-26.5-47.8-6.8-5 18.3-.4 44.1 8.1 77-11.6 27.6-28.7 64.6-40.8 85.8-.1 0-.1.1-.2.1-27.1 13.9-73.6 44.5-54.5 68 5.6 6.9 16 10 21.5 10 17.9 0 35.7-18 61.1-61.8 25.8-8.5 54.1-19.1 79-23.2 21.7 11.8 47.1 19.5 64 19.5 29.2 0 31.2-32 19.7-43.4-13.9-13.6-54.3-9.7-73.6-7.2zM377 105L279 7c-4.5-4.5-10.6-7-17-7h-6v128h128v-6.1c0-6.3-2.5-12.4-7-16.9zm-74.1 255.3c4.1-2.7-2.5-11.9-42.8-9 37.1 15.8 42.8 9 42.8 9z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaInfoCircle: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 512 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaSort: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 320 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M41 288h238c21.4 0 32.1 25.9 17 41L177 448c-9.4 9.4-24.6 9.4-33.9 0L24 329c-15.1-15.1-4.4-41 17-41zm255-105L177 64c-9.4-9.4-24.6-9.4-33.9 0L24 183c-15.1 15.1-4.4 41 17 41h238c21.4 0 32.1-25.9 17-41z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaSortDown: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 320 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M41 288h238c21.4 0 32.1 25.9 17 41L177 448c-9.4 9.4-24.6 9.4-33.9 0L24 329c-15.1-15.1-4.4-41 17-41z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaSortUp: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 320 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M279 224H41c-21.4 0-32.1-25.9-17-41L143 64c9.4-9.4 24.6-9.4 33.9 0l119 119c15.2 15.1 4.5 41-16.9 41z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaSpinner: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 512 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M304 48c0 26.51-21.49 48-48 48s-48-21.49-48-48 21.49-48 48-48 48 21.49 48 48zm-48 368c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48zm208-208c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48zM96 256c0-26.51-21.49-48-48-48S0 229.49 0 256s21.49 48 48 48 48-21.49 48-48zm12.922 99.078c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48c0-26.509-21.491-48-48-48zm294.156 0c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48c0-26.509-21.49-48-48-48zM108.922 60.922c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.491-48-48-48z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaTimes: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 352 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaTimesCircle: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 512 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm121.6 313.1c4.7 4.7 4.7 12.3 0 17L338 377.6c-4.7 4.7-12.3 4.7-17 0L256 312l-65.1 65.6c-4.7 4.7-12.3 4.7-17 0L134.4 338c-4.7-4.7-4.7-12.3 0-17l65.6-65-65.6-65.1c-4.7-4.7-4.7-12.3 0-17l39.6-39.6c4.7-4.7 12.3-4.7 17 0l65 65.7 65.1-65.6c4.7-4.7 12.3-4.7 17 0l39.6 39.6c4.7 4.7 4.7 12.3 0 17L312 256l65.6 65.1z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaWindowMaximize: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 512 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M464 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h416c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm-16 160H64v-84c0-6.6 5.4-12 12-12h360c6.6 0 12 5.4 12 12v84z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const FaWindowRestore: IconType = GenIcon({
|
||||
tag: 'svg',
|
||||
attr: { viewBox: '0 0 512 512' },
|
||||
child: [
|
||||
{
|
||||
tag: 'path',
|
||||
attr: {
|
||||
d: 'M512 48v288c0 26.5-21.5 48-48 48h-48V176c0-44.1-35.9-80-80-80H128V48c0-26.5 21.5-48 48-48h288c26.5 0 48 21.5 48 48zM384 176v288c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V176c0-26.5 21.5-48 48-48h288c26.5 0 48 21.5 48 48zm-68 28c0-6.6-5.4-12-12-12H76c-6.6 0-12 5.4-12 12v52h252v-52z',
|
||||
},
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
|
@ -151,30 +151,47 @@ div.dialog-after-open > div.dialog-content.maximized {
|
|||
/* React Modal prevent bg scroll */
|
||||
|
||||
/* Window Controls Styles */
|
||||
.window-controls {
|
||||
top: 12px;
|
||||
}
|
||||
|
||||
.window-controls .close-btn {
|
||||
.window-controls .window-controls-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
transition:
|
||||
background-color 0.2s ease,
|
||||
color 0.2s ease;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.window-controls .close-btn:hover {
|
||||
.window-controls .window-controls-btn:hover {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.window-controls .close-btn:last-child:hover {
|
||||
.window-controls .window-controls-btn:focus-visible {
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.6);
|
||||
}
|
||||
|
||||
.window-controls .window-controls-close:hover {
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dark .window-controls .window-controls-btn {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.dark .window-controls .window-controls-btn:hover {
|
||||
background-color: rgba(255, 255, 255, 0.12);
|
||||
color: #f3f4f6;
|
||||
}
|
||||
|
||||
.dark .window-controls .window-controls-close:hover {
|
||||
background-color: #e74c3c;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Maximized dialog styles */
|
||||
|
|
|
|||
|
|
@ -1,24 +1,81 @@
|
|||
import * as fc from 'react-icons/fc'
|
||||
import * as fa from 'react-icons/fa'
|
||||
import { useSyncExternalStore } from 'react'
|
||||
import type { ComponentType } from 'react'
|
||||
|
||||
/**
|
||||
* Menü ikonları veritabanında ada göre tutulduğu için (`FaUser`, `FcFolder` …)
|
||||
* tüm react-icons setinin ad→bileşen sözlüğü olarak hazır olması gerekiyor.
|
||||
*
|
||||
* Bu setler `import * as` ile STATİK alındığında rollup tree-shaking'i tamamen
|
||||
* kapanıyordu: modül tek parça olarak korunduğu için, uygulamada tek bir `FaCog`
|
||||
* kullanan dosya bile ~1.3 MB'lık fa chunk'ını ilk açılış grafiğine sokuyordu
|
||||
* (ölçüm: eager JS 1756 KB'ın 1281 KB'ı sadece bu).
|
||||
*
|
||||
* Çözüm: setler DİNAMİK import ile yükleniyor.
|
||||
* - Named import kullanan (~190) dosya artık normal şekilde tree-shake ediliyor,
|
||||
* yalnızca kullandıkları ikonlar bundle'a giriyor.
|
||||
* - Tam sözlük ilk boyamayı bloklamayan ayrı bir chunk'a düşüyor; ilk abone
|
||||
* olan bileşen (kenar çubuğu) yüklemeyi tetikler, hazır olunca `useSyncExternalStore`
|
||||
* üzerinden yeniden render edilir. Menü verisi zaten API'den asenkron geldiği
|
||||
* için pratikte ikonlar menüyle birlikte görünür.
|
||||
*/
|
||||
|
||||
export type NavigationIcons = Record<string, ComponentType<any>>
|
||||
|
||||
const navigationIcon: NavigationIcons = {}
|
||||
/**
|
||||
* Snapshot referansı yükleme bitince DEĞİŞİR; böylece `useSyncExternalStore`
|
||||
* yeniden render tetikler ve tüketicilerdeki `useMemo(..., [navigationIcon])`
|
||||
* bağımlılıkları da doğru şekilde tazelenir.
|
||||
*/
|
||||
let snapshot: NavigationIcons = {}
|
||||
|
||||
function registerIcons(iconModule: Record<string, any>) {
|
||||
for (const [key, Icon] of Object.entries(iconModule)) {
|
||||
if (
|
||||
Icon &&
|
||||
(typeof Icon === 'function' ||
|
||||
(typeof Icon === 'object' && 'render' in Icon))
|
||||
) {
|
||||
navigationIcon[key] = Icon as ComponentType<any>
|
||||
const listeners = new Set<() => void>()
|
||||
let loadPromise: Promise<void> | null = null
|
||||
|
||||
function collectIcons(iconModules: Record<string, unknown>[]): NavigationIcons {
|
||||
const icons: NavigationIcons = {}
|
||||
for (const iconModule of iconModules) {
|
||||
for (const [key, Icon] of Object.entries(iconModule)) {
|
||||
if (Icon && (typeof Icon === 'function' || (typeof Icon === 'object' && 'render' in Icon))) {
|
||||
icons[key] = Icon as ComponentType<any>
|
||||
}
|
||||
}
|
||||
}
|
||||
return icons
|
||||
}
|
||||
|
||||
/** İkon sözlüğünü (bir kez) yükler. Birden çok çağrı aynı promise'i paylaşır. */
|
||||
export function loadNavigationIcons(): Promise<void> {
|
||||
if (!loadPromise) {
|
||||
loadPromise = Promise.all([import('react-icons/fc'), import('react-icons/fa')])
|
||||
.then((modules) => {
|
||||
snapshot = collectIcons(modules)
|
||||
listeners.forEach((listener) => listener())
|
||||
})
|
||||
.catch((error) => {
|
||||
// Yükleme başarısızsa yeniden denenebilsin; ikonsuz menü çalışmaya devam eder.
|
||||
loadPromise = null
|
||||
console.error('Navigation icons could not be loaded', error)
|
||||
})
|
||||
}
|
||||
|
||||
return loadPromise
|
||||
}
|
||||
|
||||
function subscribe(listener: () => void) {
|
||||
listeners.add(listener)
|
||||
void loadNavigationIcons()
|
||||
return () => {
|
||||
listeners.delete(listener)
|
||||
}
|
||||
}
|
||||
|
||||
registerIcons(fc)
|
||||
registerIcons(fa)
|
||||
const getSnapshot = () => snapshot
|
||||
|
||||
export default navigationIcon
|
||||
/**
|
||||
* Ad→ikon sözlüğünü döner ve sözlük yüklendiğinde bileşeni yeniden render eder.
|
||||
* Yükleme tamamlanana kadar boş sözlük döner; çağıran taraflar zaten
|
||||
* "ikon bulunamadı" durumunu (fallback ikon / null) ele alıyor.
|
||||
*/
|
||||
export function useNavigationIcons(): NavigationIcons {
|
||||
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import axios from 'axios'
|
||||
import { store } from '../store'
|
||||
import { jwtDecode } from 'jwt-decode'
|
||||
import { jwtDecode } from '@/utils/jwt'
|
||||
import appConfig from '../proxy/configs/app.config'
|
||||
import { refreshToken, signOut as revokeTokens } from './auth.service'
|
||||
import { isLoginSuccess } from '../proxy/account/models'
|
||||
|
|
|
|||
148
ui/src/utils/collection.ts
Normal file
148
ui/src/utils/collection.ts
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
/**
|
||||
* lodash yerine kullanılan minimal yardımcılar.
|
||||
*
|
||||
* Uygulamada lodash'ten yalnızca aşağıdaki dokuz fonksiyon kullanılıyordu;
|
||||
* bunlar için ~25 KB'lık bir bağımlılık taşımak yerine davranışları birebir
|
||||
* karşılayan yerel sürümler tutuluyor. Davranış farkı olmaması önemli:
|
||||
* çağrı yerleri lodash semantiğine göre yazılmıştı.
|
||||
*/
|
||||
|
||||
/** lodash/isNil */
|
||||
export const isNil = (value: unknown): value is null | undefined => value == null
|
||||
|
||||
/**
|
||||
* lodash/isEmpty — nesne, dizi, string, Map/Set ve primitive'ler için.
|
||||
* Primitive (number/boolean) daima "boş" sayılır; lodash de böyle davranır.
|
||||
*/
|
||||
export const isEmpty = (value: unknown): boolean => {
|
||||
if (value == null) return true
|
||||
if (Array.isArray(value) || typeof value === 'string') return value.length === 0
|
||||
if (value instanceof Map || value instanceof Set) return value.size === 0
|
||||
if (typeof value === 'object') return Object.keys(value as object).length === 0
|
||||
return true
|
||||
}
|
||||
|
||||
/** lodash/isPlainObject — düz nesne mi (dizi, Date, class örneği değil). */
|
||||
export const isPlainObject = (value: unknown): boolean => {
|
||||
if (typeof value !== 'object' || value === null) return false
|
||||
const proto = Object.getPrototypeOf(value)
|
||||
return proto === null || proto === Object.prototype
|
||||
}
|
||||
|
||||
/** lodash/keys */
|
||||
export const keys = (value: object | null | undefined): string[] =>
|
||||
value == null ? [] : Object.keys(value)
|
||||
|
||||
/**
|
||||
* lodash/cloneDeep — düz veri (nesne/dizi/Date/Map/Set) için derin kopya.
|
||||
* structuredClone fonksiyon ve React elemanı içeren yapılarda patlar; bu
|
||||
* yüzden elle dolaşılıyor, klonlanamayan değerler referansla taşınıyor.
|
||||
*/
|
||||
export const cloneDeep = <T>(value: T, seen = new WeakMap<object, unknown>()): T => {
|
||||
if (value === null || typeof value !== 'object') return value
|
||||
|
||||
const objectValue = value as unknown as object
|
||||
const cached = seen.get(objectValue)
|
||||
if (cached !== undefined) return cached as T
|
||||
|
||||
if (value instanceof Date) return new Date(value.getTime()) as unknown as T
|
||||
if (value instanceof RegExp) return new RegExp(value.source, value.flags) as unknown as T
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
const copy: unknown[] = []
|
||||
seen.set(objectValue, copy)
|
||||
value.forEach((item, index) => {
|
||||
copy[index] = cloneDeep(item, seen)
|
||||
})
|
||||
return copy as unknown as T
|
||||
}
|
||||
|
||||
if (value instanceof Map) {
|
||||
const copy = new Map()
|
||||
seen.set(objectValue, copy)
|
||||
value.forEach((item, key) => copy.set(key, cloneDeep(item, seen)))
|
||||
return copy as unknown as T
|
||||
}
|
||||
|
||||
if (value instanceof Set) {
|
||||
const copy = new Set()
|
||||
seen.set(objectValue, copy)
|
||||
value.forEach((item) => copy.add(cloneDeep(item, seen)))
|
||||
return copy as unknown as T
|
||||
}
|
||||
|
||||
// File/Blob gibi klonlanamayan host nesneleri referansla taşınır.
|
||||
if (!isPlainObject(value)) return value
|
||||
|
||||
const copy: Record<string, unknown> = {}
|
||||
seen.set(objectValue, copy)
|
||||
Object.entries(value as Record<string, unknown>).forEach(([key, item]) => {
|
||||
copy[key] = cloneDeep(item, seen)
|
||||
})
|
||||
return copy as unknown as T
|
||||
}
|
||||
|
||||
/**
|
||||
* lodash/get — 'a.b[0].c' biçimli yol desteğiyle güvenli okuma.
|
||||
* Formik'in iç içe alan adları (`address.city`, `rows[0].name`) bu formatta.
|
||||
*/
|
||||
export const get = <T = unknown>(object: unknown, path: string, defaultValue?: T): T => {
|
||||
if (object == null) return defaultValue as T
|
||||
|
||||
const segments = path
|
||||
.replace(/\[(\d+)\]/g, '.$1')
|
||||
.split('.')
|
||||
.filter((segment) => segment.length > 0)
|
||||
|
||||
let current: unknown = object
|
||||
for (const segment of segments) {
|
||||
if (current == null) return defaultValue as T
|
||||
current = (current as Record<string, unknown>)[segment]
|
||||
}
|
||||
|
||||
return (current === undefined ? defaultValue : current) as T
|
||||
}
|
||||
|
||||
/** lodash/groupBy — anahtar üreticisi alan/fonksiyon olabilir. */
|
||||
export const groupBy = <T>(
|
||||
collection: readonly T[] | null | undefined,
|
||||
iteratee: ((item: T) => string | number) | keyof T,
|
||||
): Record<string, T[]> => {
|
||||
const result: Record<string, T[]> = {}
|
||||
if (!collection) return result
|
||||
|
||||
const resolveKey =
|
||||
typeof iteratee === 'function'
|
||||
? (item: T) => (iteratee as (item: T) => string | number)(item)
|
||||
: (item: T) => (item as Record<string, unknown>)[iteratee as string] as string | number
|
||||
|
||||
for (const item of collection) {
|
||||
const key = String(resolveKey(item))
|
||||
;(result[key] ??= []).push(item)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* lodash/remove — diziyi YERİNDE değiştirir ve çıkarılan elemanları döner.
|
||||
* Çağrı yerleri bu mutasyona güveniyor.
|
||||
*/
|
||||
export const remove = <T>(array: T[], predicate: (item: T) => boolean): T[] => {
|
||||
const removed: T[] = []
|
||||
for (let index = array.length - 1; index >= 0; index -= 1) {
|
||||
if (predicate(array[index])) {
|
||||
removed.unshift(array[index])
|
||||
array.splice(index, 1)
|
||||
}
|
||||
}
|
||||
return removed
|
||||
}
|
||||
|
||||
let uniqueIdCounter = 0
|
||||
|
||||
/** lodash/uniqueId */
|
||||
export const uniqueId = (prefix = ''): string => {
|
||||
uniqueIdCounter += 1
|
||||
return `${prefix}${uniqueIdCounter}`
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { jwtDecode } from 'jwt-decode'
|
||||
import { jwtDecode } from '@/utils/jwt'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import useQuery from './useQuery'
|
||||
import { store, useStoreActions, useStoreState } from '../../store/store'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useMemo } from 'react'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import { isEmpty } from '@/utils/collection'
|
||||
|
||||
function useAuthority(
|
||||
userAuthority: string[] = [],
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useStoreState } from '@/store/store'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { FaUser } from 'react-icons/fa'
|
||||
import navigationIcon from '@/proxy/menus/navigation-icon.config'
|
||||
import { useNavigationIcons } from '@/proxy/menus/navigation-icon.config'
|
||||
import { navigationTreeToFlat } from '@/utils/navigation'
|
||||
import { ReactElement } from 'react'
|
||||
|
||||
|
|
@ -18,6 +18,7 @@ const extractListFormCode = (path: string): string | null => {
|
|||
export function useCurrentMenuIcon(className = 'w-6 h-6'): ReactElement {
|
||||
const mainMenu = useStoreState((state) => state.abpConfig.menu.mainMenu)
|
||||
const location = useLocation()
|
||||
const navigationIcon = useNavigationIcons()
|
||||
const menus = navigationTreeToFlat(mainMenu)
|
||||
|
||||
const currentMenu = menus.find((menu) => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// eslint-disable @typescript-eslint/no-explicit-any
|
||||
import type { NavigationTree } from '@/proxy/menus/navigation'
|
||||
import isPlainObject from 'lodash/isPlainObject'
|
||||
import { isPlainObject } from '@/utils/collection'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
interface NavInfo extends NavigationTree {
|
||||
|
|
|
|||
27
ui/src/utils/jwt.ts
Normal file
27
ui/src/utils/jwt.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* jwt-decode yerine kullanılan minimal çözücü.
|
||||
*
|
||||
* Sadece payload okunuyor; imza doğrulaması zaten sunucunun işi. Token
|
||||
* base64url kodlu olduğu için `-`/`_` çevrilir ve padding tamamlanır;
|
||||
* ayrıca Türkçe karakter içeren claim'lerin bozulmaması için atob çıktısı
|
||||
* UTF-8 olarak yeniden çözülür.
|
||||
*/
|
||||
export const jwtDecode = <T = Record<string, unknown>>(token: string): T => {
|
||||
const payload = token?.split('.')[1]
|
||||
if (!payload) {
|
||||
throw new Error('Invalid token specified: missing payload')
|
||||
}
|
||||
|
||||
const base64 = payload.replace(/-/g, '+').replace(/_/g, '/')
|
||||
const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=')
|
||||
|
||||
try {
|
||||
const binary = atob(padded)
|
||||
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0))
|
||||
return JSON.parse(new TextDecoder().decode(bytes)) as T
|
||||
} catch {
|
||||
throw new Error('Invalid token specified: payload could not be decoded')
|
||||
}
|
||||
}
|
||||
|
||||
export default jwtDecode
|
||||
|
|
@ -3,8 +3,8 @@ import Loading from '@/components/shared/Loading'
|
|||
import { Alert, Button } from '@/components/ui'
|
||||
import { store, useStoreState } from '@/store'
|
||||
import { Suspense, useEffect } from 'react'
|
||||
import { ErrorBoundary } from 'react-error-boundary'
|
||||
import type { FallbackProps } from 'react-error-boundary'
|
||||
import ErrorBoundary from '@/components/shared/ErrorBoundary'
|
||||
import type { FallbackProps } from '@/components/shared/ErrorBoundary'
|
||||
import { FaArrowLeft } from 'react-icons/fa'
|
||||
import { Navigate, useLocation } from 'react-router-dom'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
|
@ -19,8 +19,8 @@ interface ViewsProps {
|
|||
layout?: LayoutType
|
||||
}
|
||||
|
||||
// react-error-boundary v6'dan itibaren `error` `unknown` tipinde geliyor;
|
||||
// throw edilen her şey Error olmayabilir, o yüzden daraltıyoruz.
|
||||
// `error` `unknown` tipinde geliyor; throw edilen her şey Error olmayabilir,
|
||||
// o yüzden daraltıyoruz.
|
||||
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
|
||||
const { translate } = useLocalization()
|
||||
const { name, message } =
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { NotificationDto } from '@/proxy/notification/models'
|
|||
import { getList } from '@/services/notification.service'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import dayjs from 'dayjs'
|
||||
import groupBy from 'lodash/groupBy'
|
||||
import { groupBy } from '@/utils/collection'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import Log from './components/Log'
|
||||
import LogFilter from './components/LogFilter'
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ import { AVATAR_URL } from '@/constants/app.constant'
|
|||
import { NotificationDto } from '@/proxy/notification/models'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import dayjs from 'dayjs'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import keys from 'lodash/keys'
|
||||
import { isEmpty, keys } from '@/utils/collection'
|
||||
import Event from './Event'
|
||||
import TimeLineAvatar from './TimeLineAvatar'
|
||||
import useLocale from '@/utils/hooks/useLocale'
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ import { JsonRowDialogData } from './json-row-operations/types'
|
|||
import JsonRowOpDialogSeries from './json-row-operations/JsonRowOpDialogSeries'
|
||||
import CreatableSelect from 'react-select/creatable'
|
||||
import { getListFormFields } from '@/services/admin/list-form-field.service'
|
||||
import groupBy from 'lodash/groupBy'
|
||||
import { groupBy } from '@/utils/collection'
|
||||
|
||||
// Bu sekme ListForm nesnesinin tamamını düzenler; `name` / `title` / `height`
|
||||
// gibi alanların girdisi burada yok. Şema bu alanları zorunlu tutuyordu ve
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import { FormEditProps } from './FormEdit'
|
|||
import { SelectBoxOption } from '@/types/shared'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { getListFormFields } from '@/services/admin/list-form-field.service'
|
||||
import groupBy from 'lodash/groupBy'
|
||||
import { groupBy } from '@/utils/collection'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import type { GanttScaleType } from 'devextreme/ui/gantt'
|
||||
import { gantScaleOptions } from './options'
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import { FormEditProps } from './FormEdit'
|
|||
import { SelectBoxOption } from '@/types/shared'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { getListFormFields } from '@/services/admin/list-form-field.service'
|
||||
import groupBy from 'lodash/groupBy'
|
||||
import { groupBy } from '@/utils/collection'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { firstDayOfWeekOptions, schedulerViewOptions } from './options'
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { getListFormFields } from '@/services/admin/list-form-field.service'
|
|||
import { useStoreState } from '@/store'
|
||||
import type { SelectBoxOption } from '@/types/shared'
|
||||
import { Field, FieldProps, Form, Formik } from 'formik'
|
||||
import groupBy from 'lodash/groupBy'
|
||||
import { groupBy } from '@/utils/collection'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import type { FormEditProps } from './FormEdit'
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import { FormEditProps } from './FormEdit'
|
|||
import { SelectBoxOption } from '@/types/shared'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { getListFormFields } from '@/services/admin/list-form-field.service'
|
||||
import groupBy from 'lodash/groupBy'
|
||||
import { groupBy } from '@/utils/collection'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
const validationSchema = Yup.object().shape({})
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import {
|
|||
import { cascadeFilterOperator } from '../options'
|
||||
import CreatableSelect from 'react-select/creatable'
|
||||
import { getListFormFields } from '@/services/admin/list-form-field.service'
|
||||
import groupBy from 'lodash/groupBy'
|
||||
import { groupBy } from '@/utils/collection'
|
||||
import { extraFilterControlTypeOptions } from '@/proxy/admin/list-form/options'
|
||||
|
||||
const schema = object().shape({
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
|
|||
import { useStoreState } from '@/store/store'
|
||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||
import { WizardFileInfoDto } from '@/proxy/admin/wizard/models'
|
||||
import navigationIcon from '@/proxy/menus/navigation-icon.config'
|
||||
import { useNavigationIcons } from '@/proxy/menus/navigation-icon.config'
|
||||
|
||||
interface ConfirmState {
|
||||
fileName: string
|
||||
|
|
@ -32,6 +32,7 @@ const WizardFileManager = () => {
|
|||
const { translate } = useLocalization()
|
||||
const mode = useStoreState((state) => state.theme.mode)
|
||||
const MenuIcon = useCurrentMenuIcon('w-5 h-5')
|
||||
const navigationIcon = useNavigationIcons()
|
||||
|
||||
const [files, setFiles] = useState<WizardFileInfoDto[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Button, FormItem, Input, Notification, Select, toast } from '@/components/ui'
|
||||
import { MenuDto } from '@/proxy/menus/models'
|
||||
import { SelectBoxOption } from '@/types/shared'
|
||||
import navigationIcon from '@/proxy/menus/navigation-icon.config'
|
||||
import { useNavigationIcons } from '@/proxy/menus/navigation-icon.config'
|
||||
import { MenuItem } from '@/proxy/menus/menu'
|
||||
import { MenuService } from '@/services/menu.service'
|
||||
import { Field, FieldProps, FormikErrors, FormikTouched } from 'formik'
|
||||
|
|
@ -132,6 +132,7 @@ function TreeNode({
|
|||
const isExpanded = expanded.has(node.code)
|
||||
const isSelected = node.code === selectedCode
|
||||
const isEditing = editingCode === node.code
|
||||
const navigationIcon = useNavigationIcons()
|
||||
const NodeIcon = node.icon ? navigationIcon[node.icon] : null
|
||||
const { translate } = useLocalization()
|
||||
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
|
|||
import TableNoRecords from '@/views/shared/TableNoRecords'
|
||||
import classNames from 'classnames'
|
||||
import { Field, FieldProps, Form, Formik, FormikHelpers } from 'formik'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import { isEmpty } from '@/utils/collection'
|
||||
import { memo, Suspense, useEffect, useMemo, useState } from 'react'
|
||||
import { NodeApi, NodeRendererProps } from 'react-arborist'
|
||||
import { Tree } from 'react-arborist/dist/module/components/tree'
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import Container from '@/components/shared/Container'
|
|||
import { Button, Table, toast } from '@/components/ui'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import useThemeClass from '@/utils/hooks/useThemeClass'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import { isEmpty } from '@/utils/collection'
|
||||
import { useEffect, useState } from 'react'
|
||||
import PageTitle from '@/components/shared/PageTitle'
|
||||
import RolesPermission from './RolesPermission'
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Table } from '@/components/ui'
|
|||
import { TenantDto } from '@/proxy/config/models'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import useThemeClass from '@/utils/hooks/useThemeClass'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import { isEmpty } from '@/utils/collection'
|
||||
import { useEffect, useState } from 'react'
|
||||
import PageTitle from '@/components/shared/PageTitle'
|
||||
import TenantsConnectionString from './TenantsConnectionString'
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Table } from '@/components/ui'
|
|||
import { getUsers } from '@/services/identity.service'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import useThemeClass from '@/utils/hooks/useThemeClass'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import { isEmpty } from '@/utils/collection'
|
||||
import { useEffect, useState } from 'react'
|
||||
import PageTitle from '@/components/shared/PageTitle'
|
||||
import UsersPermission from './UsersPermission'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { Alert, Button } from '@/components/ui'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import useAccount from '@/utils/hooks/useAccount'
|
||||
|
|
@ -12,12 +12,17 @@ const VerifyConfirmationCode = () => {
|
|||
const navigate = useNavigate()
|
||||
const { translate } = useLocalization()
|
||||
|
||||
useQuery({
|
||||
queryKey: ['verifyConfirmationCode', userId, token],
|
||||
queryFn: () => verifyConfirmationCode(userId!, token!),
|
||||
retry: false,
|
||||
refetchOnWindowFocus: false,
|
||||
})
|
||||
// Doğrulama tek seferlik bir yan etki; StrictMode'un çift render'ında
|
||||
// isteğin iki kez gitmemesi için ref ile bir kereye kilitleniyor.
|
||||
const verifiedKey = useRef<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!userId || !token) return
|
||||
const key = `${userId}|${token}`
|
||||
if (verifiedKey.current === key) return
|
||||
verifiedKey.current = key
|
||||
verifyConfirmationCode(userId, token)
|
||||
}, [userId, token, verifyConfirmationCode])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
|
|||
import { getMenus } from '@/services/menu.service'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import { CascadeBehavior, SqlTableRelation, RelationshipType } from '@/proxy/developerKit/models'
|
||||
import navigationIcon from '@/proxy/menus/navigation-icon.config'
|
||||
import { useNavigationIcons } from '@/proxy/menus/navigation-icon.config'
|
||||
import { MenuItem } from '@/proxy/menus/menu'
|
||||
import {
|
||||
MenuTreeNode,
|
||||
|
|
@ -1122,6 +1122,7 @@ function SimpleMenuTreeNode({
|
|||
const hasChildren = node.children.length > 0
|
||||
const isExpanded = expanded.has(node.code)
|
||||
const isSelected = node.code === selectedCode
|
||||
const navigationIcon = useNavigationIcons()
|
||||
const NodeIcon = node.icon ? navigationIcon[node.icon] : null
|
||||
const { translate } = useLocalization()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Badge, Button, Dialog, Notification, toast } from '@/components/ui'
|
||||
import navigationIcon from '@/proxy/menus/navigation-icon.config'
|
||||
import { useNavigationIcons } from '@/proxy/menus/navigation-icon.config'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import CustomStore from 'devextreme/data/custom_store'
|
||||
import { useState } from 'react'
|
||||
|
|
@ -162,6 +162,8 @@ const FormButtons = (props: {
|
|||
}
|
||||
}
|
||||
|
||||
const navigationIcon = useNavigationIcons()
|
||||
|
||||
const toolbarExcludedNames = ['deleteAllRecords', 'deleteSelectedRecords', 'refreshButton']
|
||||
const hasVisibleToolbarButtons = toolbarData?.some(isToolbarButtonVisible)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import React from 'react'
|
||||
import Lightbox from 'yet-another-react-lightbox'
|
||||
import 'yet-another-react-lightbox/styles.css'
|
||||
import Video from 'yet-another-react-lightbox/plugins/video'
|
||||
import Zoom from 'yet-another-react-lightbox/plugins/zoom'
|
||||
import Counter from 'yet-another-react-lightbox/plugins/counter'
|
||||
import 'yet-another-react-lightbox/plugins/counter.css'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
import { FaChevronLeft, FaChevronRight, FaTimes } from 'react-icons/fa'
|
||||
import { Button } from '@/components/ui'
|
||||
import { SocialMediaDto } from '@/proxy/intranet/models'
|
||||
|
||||
interface MediaLightboxProps {
|
||||
|
|
@ -14,53 +12,153 @@ interface MediaLightboxProps {
|
|||
startIndex?: number
|
||||
}
|
||||
|
||||
const MIN_ZOOM = 1
|
||||
const MAX_ZOOM = 3
|
||||
const ZOOM_STEP = 0.25
|
||||
|
||||
/**
|
||||
* Sosyal duvar medya görüntüleyici.
|
||||
*
|
||||
* Daha önce `yet-another-react-lightbox` kullanıyordu; ihtiyaç duyulan yüzey
|
||||
* (görsel gezinme + sayaç + tekerlek ile yakınlaştırma + video oynatma) burada
|
||||
* zaten bağımlılık olan framer-motion ile karşılanıyor, böylece paket ve ona
|
||||
* ait üç eklenti + CSS bundle'dan düşüyor.
|
||||
*/
|
||||
const MediaLightbox: React.FC<MediaLightboxProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
media,
|
||||
startIndex = 0
|
||||
startIndex = 0,
|
||||
}) => {
|
||||
const slides = React.useMemo(() => {
|
||||
if (media.type === 'video' && media.urls && media.urls.length > 0) {
|
||||
return [
|
||||
{
|
||||
type: 'video' as const,
|
||||
sources: [
|
||||
{
|
||||
src: media.urls[0],
|
||||
type: 'video/mp4'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
const isVideo = media.type === 'video'
|
||||
const urls = React.useMemo(() => media.urls ?? [], [media])
|
||||
const [index, setIndex] = React.useState(startIndex)
|
||||
const [zoom, setZoom] = React.useState(MIN_ZOOM)
|
||||
|
||||
// Aynı bileşen farklı medya/indeks ile yeniden açılabiliyor.
|
||||
React.useEffect(() => {
|
||||
if (isOpen) {
|
||||
setIndex(startIndex)
|
||||
setZoom(MIN_ZOOM)
|
||||
}
|
||||
}, [isOpen, startIndex, media])
|
||||
|
||||
const step = React.useCallback(
|
||||
(delta: number) => {
|
||||
if (urls.length < 2) return
|
||||
setIndex((current) => (current + delta + urls.length) % urls.length)
|
||||
setZoom(MIN_ZOOM)
|
||||
},
|
||||
[urls.length],
|
||||
)
|
||||
|
||||
// Klavye ile gezinme ve kapatma; lightbox açıkken sayfa kaydırması kilitlenir.
|
||||
React.useEffect(() => {
|
||||
if (!isOpen) return
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') onClose()
|
||||
if (event.key === 'ArrowLeft') step(-1)
|
||||
if (event.key === 'ArrowRight') step(1)
|
||||
}
|
||||
|
||||
const urls = media.urls || []
|
||||
return urls.map((url) => ({
|
||||
src: url
|
||||
}))
|
||||
}, [media])
|
||||
const previousOverflow = document.body.style.overflow
|
||||
document.body.style.overflow = 'hidden'
|
||||
window.addEventListener('keydown', onKeyDown)
|
||||
|
||||
return (
|
||||
<Lightbox
|
||||
open={isOpen}
|
||||
close={onClose}
|
||||
slides={slides}
|
||||
index={startIndex}
|
||||
plugins={[Video, Zoom, Counter]}
|
||||
counter={{ container: { style: { top: 'unset', bottom: 0 } } }}
|
||||
zoom={{
|
||||
maxZoomPixelRatio: 3,
|
||||
scrollToZoom: true
|
||||
}}
|
||||
video={{
|
||||
controls: true,
|
||||
autoPlay: false
|
||||
}}
|
||||
styles={{
|
||||
container: { backgroundColor: 'rgba(0, 0, 0, 0.95)' }
|
||||
}}
|
||||
/>
|
||||
return () => {
|
||||
document.body.style.overflow = previousOverflow
|
||||
window.removeEventListener('keydown', onKeyDown)
|
||||
}
|
||||
}, [isOpen, onClose, step])
|
||||
|
||||
const onWheel = (event: React.WheelEvent) => {
|
||||
if (isVideo) return
|
||||
setZoom((current) =>
|
||||
Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, current - Math.sign(event.deltaY) * ZOOM_STEP)),
|
||||
)
|
||||
}
|
||||
|
||||
const stop = (event: React.MouseEvent) => event.stopPropagation()
|
||||
|
||||
return createPortal(
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 z-[1100] flex items-center justify-center bg-black/95"
|
||||
onClick={onClose}
|
||||
onWheel={onWheel}
|
||||
>
|
||||
<Button
|
||||
variant="plain"
|
||||
shape="circle"
|
||||
icon={<FaTimes className="h-7 w-7" />}
|
||||
className="absolute top-4 right-4 z-10 !h-12 !w-12 !px-0 text-white hover:text-gray-300"
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{!isVideo && urls.length > 1 && (
|
||||
<>
|
||||
<Button
|
||||
variant="plain"
|
||||
shape="circle"
|
||||
icon={<FaChevronLeft className="h-6 w-6" />}
|
||||
className="absolute left-4 top-1/2 z-10 !h-12 !w-12 -translate-y-1/2 !px-0 bg-black/50 text-white hover:!bg-black/70"
|
||||
onClick={(event) => {
|
||||
stop(event)
|
||||
step(-1)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="plain"
|
||||
shape="circle"
|
||||
icon={<FaChevronRight className="h-6 w-6" />}
|
||||
className="absolute right-4 top-1/2 z-10 !h-12 !w-12 -translate-y-1/2 !px-0 bg-black/50 text-white hover:!bg-black/70"
|
||||
onClick={(event) => {
|
||||
stop(event)
|
||||
step(1)
|
||||
}}
|
||||
/>
|
||||
<div className="absolute bottom-4 left-1/2 z-10 -translate-x-1/2 rounded-full bg-black/50 px-3 py-1 text-sm text-white">
|
||||
{index + 1} / {urls.length}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isVideo
|
||||
? urls[0] && (
|
||||
<video
|
||||
controls
|
||||
playsInline
|
||||
src={urls[0]}
|
||||
className="max-h-[90vh] max-w-[90vw] rounded-lg"
|
||||
onClick={stop}
|
||||
/>
|
||||
)
|
||||
: urls[index] && (
|
||||
<motion.img
|
||||
key={index}
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: zoom }}
|
||||
exit={{ opacity: 0 }}
|
||||
src={urls[index]}
|
||||
alt={`${index + 1}`}
|
||||
draggable={false}
|
||||
className="max-h-[90vh] max-w-[90vw] rounded-lg object-contain"
|
||||
style={{ cursor: zoom > MIN_ZOOM ? 'zoom-out' : 'zoom-in' }}
|
||||
onClick={(event) => {
|
||||
stop(event)
|
||||
setZoom((current) => (current > MIN_ZOOM ? MIN_ZOOM : 2))
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>,
|
||||
document.body,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import {
|
|||
import { ConfirmDialog } from '@/components/shared'
|
||||
import { MenuItem } from '@/proxy/menus/menu'
|
||||
import { MenuDto } from '@/proxy/menus/models'
|
||||
import navigationIcon from '@/proxy/menus/navigation-icon.config'
|
||||
import { useNavigationIcons } from '@/proxy/menus/navigation-icon.config'
|
||||
import { MenuService } from '@/services/menu.service'
|
||||
import { SelectBoxOption } from '@/types/shared'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
|
@ -102,6 +102,7 @@ const MenuItemComponentBase: React.FC<MenuItemComponentProps> = ({
|
|||
|
||||
const childCount = item.children?.length ?? 0
|
||||
const hasChildren = childCount > 0
|
||||
const navigationIcon = useNavigationIcons()
|
||||
const IconComponent = (item.icon && navigationIcon[item.icon]) || FaQuestionCircle
|
||||
const displayLabel = item.displayName ? translate('::' + item.displayName) : (item.code ?? '')
|
||||
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ const Blog: React.FC = () => {
|
|||
<input
|
||||
type="search"
|
||||
value={searchQuery}
|
||||
placeholder={translateLabel(translate, 'Public.blog.searchPlaceholder')}
|
||||
placeholder={translateLabel(translate, 'Public.blog.SearchPlaceholder')}
|
||||
aria-label={translate('::App.Platform.Search3')}
|
||||
className="w-full rounded-lg border border-gray-300 bg-white py-2 pl-10 text-gray-900 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400"
|
||||
onChange={(event) => setSearchQuery(event.target.value)}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import Loading from '@/components/shared/Loading'
|
||||
import navigationIcon from '@/proxy/menus/navigation-icon.config'
|
||||
import { useNavigationIcons } from '@/proxy/menus/navigation-icon.config'
|
||||
|
||||
interface PageIconProps {
|
||||
name?: string
|
||||
|
|
@ -10,6 +10,7 @@ interface PageIconProps {
|
|||
|
||||
/** Ikon anahtari tanimli degilse hicbir sey render etmez. */
|
||||
export const PageIcon: React.FC<PageIconProps> = ({ name, className, size }) => {
|
||||
const navigationIcon = useNavigationIcons()
|
||||
const IconComponent = name ? navigationIcon[name] : undefined
|
||||
|
||||
if (!IconComponent) {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { useStoreActions, useStoreState } from '@/store'
|
|||
import { SelectBoxOption } from '@/types/shared'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import { Field, FieldProps, Form, Formik } from 'formik'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import { isEmpty } from '@/utils/collection'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import PageTitle from '@/components/shared/PageTitle'
|
||||
import { FaQuestionCircle } from 'react-icons/fa'
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import { Button, Dialog, Notification, toast } from '@/components/ui'
|
|||
import { MenuDto } from '@/proxy/menus/models'
|
||||
import { MenuItem } from '@/proxy/menus/menu'
|
||||
import { MenuService } from '@/services/menu.service'
|
||||
import navigationIcon from '@/proxy/menus/navigation-icon.config'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useNavigationIcons } from '@/proxy/menus/navigation-icon.config'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { FaChevronDown, FaPlus } from 'react-icons/fa'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import { useConfig } from '@/components/ui/ConfigProvider'
|
||||
|
|
@ -19,10 +19,12 @@ interface IconPickerFieldProps {
|
|||
invalid?: boolean
|
||||
}
|
||||
|
||||
const ALL_ICON_ENTRIES = Object.entries(navigationIcon)
|
||||
const ICON_PAGE_SIZE = 100
|
||||
|
||||
export function IconPickerField({ value, onChange, invalid }: IconPickerFieldProps) {
|
||||
const navigationIcon = useNavigationIcons()
|
||||
// Sözlük asenkron dolduğu için liste yükleme bittiğinde yeniden türetilir.
|
||||
const allIconEntries = useMemo(() => Object.entries(navigationIcon), [navigationIcon])
|
||||
const [open, setOpen] = useState(false)
|
||||
const [search, setSearch] = useState('')
|
||||
const [limit, setLimit] = useState(ICON_PAGE_SIZE)
|
||||
|
|
@ -36,8 +38,8 @@ export function IconPickerField({ value, onChange, invalid }: IconPickerFieldPro
|
|||
|
||||
const SelectedIcon = value ? navigationIcon[value] : null
|
||||
const filtered = search.trim()
|
||||
? ALL_ICON_ENTRIES.filter(([key]) => key.toLowerCase().includes(search.toLowerCase()))
|
||||
: ALL_ICON_ENTRIES
|
||||
? allIconEntries.filter(([key]) => key.toLowerCase().includes(search.toLowerCase()))
|
||||
: allIconEntries
|
||||
const displayed = filtered.slice(0, limit)
|
||||
const hasMore = displayed.length < filtered.length
|
||||
const { translate } = useLocalization()
|
||||
|
|
|
|||
|
|
@ -187,14 +187,12 @@ export default defineConfig(({ mode }) => {
|
|||
'react-dom',
|
||||
'react-dom/client',
|
||||
'react-router-dom',
|
||||
'@tanstack/react-query',
|
||||
'@tanstack/react-table',
|
||||
'axios',
|
||||
'dayjs',
|
||||
'formik',
|
||||
'yup',
|
||||
'classnames',
|
||||
'lodash',
|
||||
'easy-peasy',
|
||||
'framer-motion',
|
||||
'react-icons/fa',
|
||||
|
|
@ -246,7 +244,7 @@ export default defineConfig(({ mode }) => {
|
|||
// ayrılır; bunlar sürümler arası değişmediği için tarayıcı cache'inde
|
||||
// kalır ve deploy sonrası yeniden indirilmez.
|
||||
//
|
||||
// Not: framer-motion / lodash / formik gibi paketleri de buraya eklemek
|
||||
// Not: framer-motion / formik gibi paketleri de buraya eklemek
|
||||
// ölçüldü ve zararlı çıktı — Rollup'ın otomatik chunk'lamasında bu
|
||||
// paketlerin bir kısmı yalnızca lazy view'lara giriyor; tek chunk'a
|
||||
// toplandıklarında ilk yükleme ~650 KB büyüyor.
|
||||
|
|
|
|||
Loading…
Reference in a new issue