Ölü Paketler kaldırıldı

This commit is contained in:
Sedat ÖZTÜRK 2026-08-16 18:30:07 +03:00
parent b517b7346e
commit 39ad5cab12
76 changed files with 1097 additions and 579 deletions

View file

@ -12,7 +12,7 @@
"code": "App.SiteManagement.General.NewMemberNotificationEmails", "code": "App.SiteManagement.General.NewMemberNotificationEmails",
"nameKey": "App.SiteManagement.General.NewMemberNotificationEmails", "nameKey": "App.SiteManagement.General.NewMemberNotificationEmails",
"descriptionKey": "App.GeneralNewMemberNotificationEmails.NewMemberNotificationEmailsDescription", "descriptionKey": "App.GeneralNewMemberNotificationEmails.NewMemberNotificationEmailsDescription",
"defaultValue": "system@sozsoft.com", "defaultValue": "destek@sozsoft.com",
"isVisibleToClients": false, "isVisibleToClients": false,
"providers": "T|G|D", "providers": "T|G|D",
"isInherited": false, "isInherited": false,
@ -28,7 +28,7 @@
"code": "App.SiteManagement.General.TimedLoginEmails", "code": "App.SiteManagement.General.TimedLoginEmails",
"nameKey": "App.SiteManagement.General.TimedLoginEmails", "nameKey": "App.SiteManagement.General.TimedLoginEmails",
"descriptionKey": "App.GeneralTimedLoginEmails.TimedLoginEmailsDescription", "descriptionKey": "App.GeneralTimedLoginEmails.TimedLoginEmailsDescription",
"defaultValue": "system@sozsoft.com", "defaultValue": "destek@sozsoft.com",
"isVisibleToClients": false, "isVisibleToClients": false,
"providers": "T|G|D", "providers": "T|G|D",
"isInherited": false, "isInherited": false,

View file

@ -6846,6 +6846,12 @@
"tr": "Bültene Abone Ol", "tr": "Bültene Abone Ol",
"en": "Subscribe to Newsletter" "en": "Subscribe to Newsletter"
}, },
{
"resourceName": "Platform",
"key": "App.PublicBlog.SearchPlaceholder",
"en": "Search blog posts...",
"tr": "Blog yazıları ara..."
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.BlogSubscribe.SubscribeDesc", "key": "App.BlogSubscribe.SubscribeDesc",

180
ui/package-lock.json generated
View file

@ -11,14 +11,9 @@
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@floating-ui/react": "^0.27.20", "@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", "@marsidev/react-turnstile": "^1.6.0",
"@microsoft/signalr": "^10.0.11", "@microsoft/signalr": "^10.0.11",
"@monaco-editor/react": "^4.7.0", "@monaco-editor/react": "^4.7.0",
"@tanstack/react-query": "^5.101.4",
"@tanstack/react-table": "^8.21.3", "@tanstack/react-table": "^8.21.3",
"axios": "^1.19.0", "axios": "^1.19.0",
"classnames": "^2.5.1", "classnames": "^2.5.1",
@ -33,30 +28,23 @@
"formik": "^2.4.9", "formik": "^2.4.9",
"framer-motion": "^13.1.0", "framer-motion": "^13.1.0",
"jspdf": "^4.2.1", "jspdf": "^4.2.1",
"jwt-decode": "^4.0.0",
"lodash": "^4.18.1",
"react": "^19.2.8", "react": "^19.2.8",
"react-advanced-cropper": "^0.20.1", "react-advanced-cropper": "^0.20.1",
"react-arborist": "^3.16.0", "react-arborist": "^3.16.0",
"react-dom": "^19.2.8", "react-dom": "^19.2.8",
"react-error-boundary": "^6.1.2",
"react-highlight-words": "^0.21.0",
"react-icons": "^5.7.0", "react-icons": "^5.7.0",
"react-modal": "^3.16.3", "react-modal": "^3.16.3",
"react-router-dom": "^7.18.2", "react-router-dom": "^7.18.2",
"react-select": "^5.10.2", "react-select": "^5.10.2",
"yet-another-react-lightbox": "^3.32.2",
"yup": "^1.7.1" "yup": "^1.7.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.39.5", "@eslint/js": "^9.39.5",
"@tailwindcss/typography": "^0.5.20", "@tailwindcss/typography": "^0.5.20",
"@types/file-saver": "^2.0.7", "@types/file-saver": "^2.0.7",
"@types/lodash": "^4.17.25",
"@types/node": "^24.10.1", "@types/node": "^24.10.1",
"@types/react": "^19.2.18", "@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4", "@types/react-dom": "^19.2.4",
"@types/react-highlight-words": "^0.20.1",
"@types/react-modal": "^3.16.3", "@types/react-modal": "^3.16.3",
"@typescript-eslint/eslint-plugin": "^8.67.0", "@typescript-eslint/eslint-plugin": "^8.67.0",
"@typescript-eslint/parser": "^8.67.0", "@typescript-eslint/parser": "^8.67.0",
@ -2182,57 +2170,6 @@
"integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==",
"license": "MIT" "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": { "node_modules/@humanfs/core": {
"version": "0.19.2", "version": "0.19.2",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
@ -3266,32 +3203,6 @@
"tailwindcss": ">=3.0.0 || >=4.0.0 || insiders" "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": { "node_modules/@tanstack/react-table": {
"version": "8.21.3", "version": "8.21.3",
"resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz",
@ -3402,13 +3313,6 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/@types/node": {
"version": "24.13.3", "version": "24.13.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
@ -3463,16 +3367,6 @@
"@types/react": "^19.2.0" "@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": { "node_modules/@types/react-modal": {
"version": "3.16.3", "version": "3.16.3",
"resolved": "https://registry.npmjs.org/@types/react-modal/-/react-modal-3.16.3.tgz", "resolved": "https://registry.npmjs.org/@types/react-modal/-/react-modal-3.16.3.tgz",
@ -7669,12 +7563,6 @@
"hermes-estree": "0.25.1" "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": { "node_modules/hoist-non-react-statics": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "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" "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": { "node_modules/keyv": {
"version": "4.5.4", "version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@ -10383,17 +10262,6 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -10647,34 +10515,12 @@
"react": "^19.2.8" "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": { "node_modules/react-fast-compare": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
"integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==", "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==",
"license": "MIT" "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": { "node_modules/react-icons": {
"version": "5.7.0", "version": "5.7.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.7.0.tgz", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.7.0.tgz",
@ -13240,32 +13086,6 @@
"dev": true, "dev": true,
"license": "ISC" "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": { "node_modules/yocto-queue": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View file

@ -29,14 +29,9 @@
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@floating-ui/react": "^0.27.20", "@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", "@marsidev/react-turnstile": "^1.6.0",
"@microsoft/signalr": "^10.0.11", "@microsoft/signalr": "^10.0.11",
"@monaco-editor/react": "^4.7.0", "@monaco-editor/react": "^4.7.0",
"@tanstack/react-query": "^5.101.4",
"@tanstack/react-table": "^8.21.3", "@tanstack/react-table": "^8.21.3",
"axios": "^1.19.0", "axios": "^1.19.0",
"classnames": "^2.5.1", "classnames": "^2.5.1",
@ -51,30 +46,23 @@
"formik": "^2.4.9", "formik": "^2.4.9",
"framer-motion": "^13.1.0", "framer-motion": "^13.1.0",
"jspdf": "^4.2.1", "jspdf": "^4.2.1",
"jwt-decode": "^4.0.0",
"lodash": "^4.18.1",
"react": "^19.2.8", "react": "^19.2.8",
"react-advanced-cropper": "^0.20.1", "react-advanced-cropper": "^0.20.1",
"react-arborist": "^3.16.0", "react-arborist": "^3.16.0",
"react-dom": "^19.2.8", "react-dom": "^19.2.8",
"react-error-boundary": "^6.1.2",
"react-highlight-words": "^0.21.0",
"react-icons": "^5.7.0", "react-icons": "^5.7.0",
"react-modal": "^3.16.3", "react-modal": "^3.16.3",
"react-router-dom": "^7.18.2", "react-router-dom": "^7.18.2",
"react-select": "^5.10.2", "react-select": "^5.10.2",
"yet-another-react-lightbox": "^3.32.2",
"yup": "^1.7.1" "yup": "^1.7.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.39.5", "@eslint/js": "^9.39.5",
"@tailwindcss/typography": "^0.5.20", "@tailwindcss/typography": "^0.5.20",
"@types/file-saver": "^2.0.7", "@types/file-saver": "^2.0.7",
"@types/lodash": "^4.17.25",
"@types/node": "^24.10.1", "@types/node": "^24.10.1",
"@types/react": "^19.2.18", "@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4", "@types/react-dom": "^19.2.4",
"@types/react-highlight-words": "^0.20.1",
"@types/react-modal": "^3.16.3", "@types/react-modal": "^3.16.3",
"@typescript-eslint/eslint-plugin": "^8.67.0", "@typescript-eslint/eslint-plugin": "^8.67.0",
"@typescript-eslint/parser": "^8.67.0", "@typescript-eslint/parser": "^8.67.0",

View file

@ -1,6 +1,5 @@
import Layout from '@/components/layouts' import Layout from '@/components/layouts'
import Theme from '@/components/template/Theme' import Theme from '@/components/template/Theme'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { StoreProvider } from 'easy-peasy' import { StoreProvider } from 'easy-peasy'
import { BrowserRouter } from 'react-router-dom' import { BrowserRouter } from 'react-router-dom'
import { store } from './store' import { store } from './store'
@ -10,15 +9,6 @@ import { registerServiceWorker } from './views/version/swRegistration'
import { useEffect } from 'react' import { useEffect } from 'react'
import DevExtremeBoundary from './components/devExtreme/DevExtremeBoundary' import DevExtremeBoundary from './components/devExtreme/DevExtremeBoundary'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 30_000,
refetchOnWindowFocus: false,
},
},
})
function App() { function App() {
useEffect(() => { useEffect(() => {
registerServiceWorker() registerServiceWorker()
@ -30,13 +20,11 @@ function App() {
<BrowserRouter> <BrowserRouter>
<DynamicRoutesProvider> <DynamicRoutesProvider>
<ComponentProvider> <ComponentProvider>
<QueryClientProvider client={queryClient}> <DevExtremeBoundary>
<DevExtremeBoundary> <Theme>
<Theme> <Layout />
<Layout /> </Theme>
</Theme> </DevExtremeBoundary>
</DevExtremeBoundary>
</QueryClientProvider>
</ComponentProvider> </ComponentProvider>
</DynamicRoutesProvider> </DynamicRoutesProvider>
</BrowserRouter> </BrowserRouter>

View file

@ -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

View 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

View 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

View file

@ -3,7 +3,6 @@ export { default as AdaptableCard } from './AdaptableCard'
export { default as Affix } from './Affix' export { default as Affix } from './Affix'
export { default as AuthorityCheck } from './AuthorityCheck' export { default as AuthorityCheck } from './AuthorityCheck'
export { default as PermissionCheck } from './PermissionCheck' export { default as PermissionCheck } from './PermissionCheck'
export { default as CalendarView } from './CalendarView'
export { default as ConfirmDialog } from './ConfirmDialog' export { default as ConfirmDialog } from './ConfirmDialog'
export { default as Container } from './Container' export { default as Container } from './Container'
export { default as DataTable } from './DataTable' export { default as DataTable } from './DataTable'

View file

@ -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 type { ElementType, ComponentPropsWithRef } from 'react'
import React from 'react' import React from 'react'
import { FaQuestionCircle } from 'react-icons/fa' import { FaQuestionCircle } from 'react-icons/fa'
@ -18,6 +18,8 @@ export const Icon = <T extends ElementType>({
} }
const HorizontalMenuIcon = ({ icon }: HorizontalMenuIconProps) => { const HorizontalMenuIcon = ({ icon }: HorizontalMenuIconProps) => {
const navigationIcon = useNavigationIcons()
if (typeof icon !== 'string' || !icon) { if (typeof icon !== 'string' || !icon) {
return <></> return <></>
} }

View file

@ -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 MenuItem from '@/components/ui/MenuItem'
import HorizontalMenuNavLink from './HorizontalMenuNavLink' import HorizontalMenuNavLink from './HorizontalMenuNavLink'
import type { NavMode } from '@/proxy/theme/models' import type { NavMode } from '@/proxy/theme/models'
@ -20,6 +20,7 @@ export type HorizontalMenuItemProps = {
const HorizontalMenuItem = ({ nav, isLink, manuVariant }: HorizontalMenuItemProps) => { const HorizontalMenuItem = ({ nav, isLink, manuVariant }: HorizontalMenuItemProps) => {
const { title, icon, path, isExternalLink } = nav const { title, icon, path, isExternalLink } = nav
const navigationIcon = useNavigationIcons()
// Dinamik ikon render etmek için createElement kullanıyoruz // Dinamik ikon render etmek için createElement kullanıyoruz
const renderIcon = const renderIcon =

View file

@ -5,7 +5,7 @@ import withHeaderItem from '@/utils/hoc/withHeaderItem'
import useThemeClass from '@/utils/hooks/useThemeClass' import useThemeClass from '@/utils/hooks/useThemeClass'
import classNames from 'classnames' import classNames from 'classnames'
import { useEffect, useRef, useState } from 'react' 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 { FaChevronRight, FaSearch } from 'react-icons/fa'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { PermissionCheck } from '../shared' import { PermissionCheck } from '../shared'
@ -61,7 +61,6 @@ const ListItem = (props: {
</div> */} </div> */}
<div className="text-gray-900 dark:text-gray-300"> <div className="text-gray-900 dark:text-gray-300">
<Highlighter <Highlighter
autoEscape
highlightClassName={classNames( highlightClassName={classNames(
textTheme, textTheme,
'underline bg-transparent font-semibold dark:text-white', 'underline bg-transparent font-semibold dark:text-white',

View file

@ -10,7 +10,7 @@ import {
import StackedSideNavMini, { SelectedMenuItem } from './StackedSideNavMini' import StackedSideNavMini, { SelectedMenuItem } from './StackedSideNavMini'
import StackedSideNavSecondary from './StackedSideNavSecondary' import StackedSideNavSecondary from './StackedSideNavSecondary'
import useResponsive from '@/utils/hooks/useResponsive' import useResponsive from '@/utils/hooks/useResponsive'
import isEmpty from 'lodash/isEmpty' import { isEmpty } from '@/utils/collection'
import { useStoreState } from '@/store' import { useStoreState } from '@/store'
const stackedSideNavDefaultStyle = { const stackedSideNavDefaultStyle = {

View file

@ -5,7 +5,7 @@ import { PermissionCheck } from '@/components/shared'
import Logo from '@/components/template/Logo' import Logo from '@/components/template/Logo'
import Menu from '@/components/ui/Menu' import Menu from '@/components/ui/Menu'
import ScrollBar from '@/components/ui/ScrollBar' 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_ITEM_TYPE_ITEM } from '@/constants/navigation.constant'
import { import {
NAV_MODE_DARK, NAV_MODE_DARK,
@ -15,7 +15,7 @@ import {
} from '@/constants/theme.constant' } from '@/constants/theme.constant'
import { useStoreState } from '@/store' import { useStoreState } from '@/store'
import useMenuActive from '@/utils/hooks/useMenuActive' import useMenuActive from '@/utils/hooks/useMenuActive'
import isEmpty from 'lodash/isEmpty' import { isEmpty } from '@/utils/collection'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { FaQuestionCircle } from 'react-icons/fa' import { FaQuestionCircle } from 'react-icons/fa'
import { Link, useLocation } from 'react-router-dom' import { Link, useLocation } from 'react-router-dom'
@ -41,6 +41,7 @@ interface StackedSideNavMiniProps extends CommonProps {
const StackedSideNavMini = (props: StackedSideNavMiniProps) => { const StackedSideNavMini = (props: StackedSideNavMiniProps) => {
const { pathname } = useLocation() const { pathname } = useLocation()
const navigationIcon = useNavigationIcons()
const { const {
navMode, navMode,

View file

@ -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 type { ElementType, ComponentPropsWithRef } from 'react'
import React from 'react' import React from 'react'
@ -17,6 +17,8 @@ export const Icon = <T extends ElementType>({
} }
const VerticalMenuIcon = ({ icon }: VerticalMenuIconProps) => { const VerticalMenuIcon = ({ icon }: VerticalMenuIconProps) => {
const navigationIcon = useNavigationIcons()
// Eğer icon boş veya string değilse, boş bir öğe döndür // Eğer icon boş veya string değilse, boş bir öğe döndür
if (typeof icon !== 'string' || !icon) { if (typeof icon !== 'string' || !icon) {
return <></> return <></>

View file

@ -1,7 +1,7 @@
import { useState, forwardRef } from 'react' import { useState, forwardRef } from 'react'
import classNames from 'classnames' import classNames from 'classnames'
import useTimeout from '../hooks/useTimeout' 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 { motion } from 'framer-motion'
import CloseButton from '../CloseButton' import CloseButton from '../CloseButton'
import StatusIcon from '../StatusIcon' import StatusIcon from '../StatusIcon'

View file

@ -1,8 +1,7 @@
import { forwardRef, useState, useCallback, useMemo, useEffect } from 'react' import { forwardRef, useState, useCallback, useMemo, useEffect } from 'react'
import classNames from 'classnames' import classNames from 'classnames'
import { CheckboxGroupContextProvider } from './context' import { CheckboxGroupContextProvider } from './context'
import cloneDeep from 'lodash/cloneDeep' import { cloneDeep, remove } from '@/utils/collection'
import remove from 'lodash/remove'
import shallowEqual from '../utils/shallowEqual' import shallowEqual from '../utils/shallowEqual'
import type { CommonProps } from '../@types/common' import type { CommonProps } from '../@types/common'
import type { CheckboxGroupValue, CheckboxValue } from './context' import type { CheckboxGroupValue, CheckboxValue } from './context'

View file

@ -1,5 +1,5 @@
import { forwardRef } from 'react' import { forwardRef } from 'react'
import { FaTimes } from 'react-icons/fa'; import { FaTimes } from '@/components/ui/icons';
import classNames from 'classnames' import classNames from 'classnames'
import type { CommonProps } from '../@types/common' import type { CommonProps } from '../@types/common'
import type { MouseEvent } from 'react' import type { MouseEvent } from 'react'

View file

@ -3,7 +3,7 @@ import dayjs from 'dayjs'
import localizedFormat from 'dayjs/plugin/localizedFormat' import localizedFormat from 'dayjs/plugin/localizedFormat'
import { Input } from '../Input' import { Input } from '../Input'
import useMergedRef from '../hooks/useMergeRef' import useMergedRef from '../hooks/useMergeRef'
import { FaCalendarAlt } from 'react-icons/fa'; import { FaCalendarAlt } from '@/components/ui/icons';
import CloseButton from '../CloseButton' import CloseButton from '../CloseButton'
import type { CommonProps, TypeAttributes } from '../@types/common' import type { CommonProps, TypeAttributes } from '../@types/common'
import type { import type {

View file

@ -1,6 +1,6 @@
import classNames from 'classnames' import classNames from 'classnames'
import { Button } from '../../Button' import { Button } from '../../Button'
import { FaChevronRight, FaChevronLeft } from 'react-icons/fa'; import { FaChevronRight, FaChevronLeft } from '@/components/ui/icons';
import type { CommonProps } from '../../@types/common' import type { CommonProps } from '../../@types/common'
export interface HeaderProps extends CommonProps { export interface HeaderProps extends CommonProps {

View file

@ -65,7 +65,7 @@ export interface DialogProps extends ReactModal.Props {
contentClassName?: string contentClassName?: string
draggable?: boolean draggable?: boolean
height?: string | number height?: string | number
onClose?: (e: MouseEvent<HTMLSpanElement>) => void onClose?: (e: MouseEvent<HTMLElement>) => void
width?: string | number width?: string | number
showWindowControls?: boolean showWindowControls?: boolean
onMaximize?: () => void onMaximize?: () => void
@ -139,12 +139,12 @@ const Dialog = (props: DialogProps) => {
}) })
}, [currentSize.width, currentSize.height]) }, [currentSize.width, currentSize.height])
const onCloseClick = (e: MouseEvent<HTMLSpanElement>) => { const onCloseClick = (e: MouseEvent<HTMLElement>) => {
onClose?.(e) onClose?.(e)
} }
const handleMaximize = useCallback( const handleMaximize = useCallback(
(e: MouseEvent<HTMLSpanElement>) => { (e: MouseEvent<HTMLElement>) => {
e.stopPropagation() e.stopPropagation()
if (!isMaximized) { if (!isMaximized) {
setIsMaximized(true) setIsMaximized(true)
@ -156,7 +156,7 @@ const Dialog = (props: DialogProps) => {
) )
const handleRestore = useCallback( const handleRestore = useCallback(
(e: MouseEvent<HTMLSpanElement>) => { (e: MouseEvent<HTMLElement>) => {
e.stopPropagation() e.stopPropagation()
if (isMaximized) { if (isMaximized) {
setIsMaximized(false) setIsMaximized(false)

View file

@ -7,7 +7,7 @@ import {
isValidElement, isValidElement,
cloneElement, cloneElement,
} from 'react' } from 'react'
import isNil from 'lodash/isNil' import { isNil } from '@/utils/collection'
import chainedFunction from '../utils/chainedFunction' import chainedFunction from '../utils/chainedFunction'
import DropdownContext from './context/dropdownContext' import DropdownContext from './context/dropdownContext'
import MenuContext from './context/menuContext' import MenuContext from './context/menuContext'
@ -19,7 +19,7 @@ import DropdownMenuContext, {
DropdownMenuContextProvider, DropdownMenuContextProvider,
} from './context/dropdownMenuContext' } from './context/dropdownMenuContext'
import classNames from 'classnames' import classNames from 'classnames'
import { FaChevronRight, FaChevronLeft } from 'react-icons/fa'; import { FaChevronRight, FaChevronLeft } from '@/components/ui/icons';
import MenuItem from '../MenuItem' import MenuItem from '../MenuItem'
import { DROPDOWN_ITEM_TYPE } from '../utils/constants' import { DROPDOWN_ITEM_TYPE } from '../utils/constants'
import type { CommonProps } from '../@types/common' import type { CommonProps } from '../@types/common'

View file

@ -1,6 +1,6 @@
import { forwardRef } from 'react' import { forwardRef } from 'react'
import classNames from 'classnames' 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 { CommonProps } from '../@types/common'
import type { DropdownPlacement } from '../@types/placement' import type { DropdownPlacement } from '../@types/placement'
import type { ReactNode } from 'react' import type { ReactNode } from 'react'

View file

@ -1,5 +1,5 @@
import React, { createContext, useState, useRef, useCallback } from 'react' import React, { createContext, useState, useRef, useCallback } from 'react'
import isNil from 'lodash/isNil' import { isNil } from '@/utils/collection'
type Item = { type Item = {
element: HTMLElement | null element: HTMLElement | null

View file

@ -1,7 +1,5 @@
import classNames from 'classnames' import classNames from 'classnames'
import get from 'lodash/get' import { get, isEmpty, isNil } from '@/utils/collection'
import isEmpty from 'lodash/isEmpty'
import isNil from 'lodash/isNil'
import type { ElementType, HTMLInputTypeAttribute, InputHTMLAttributes, ReactNode } from 'react' import type { ElementType, HTMLInputTypeAttribute, InputHTMLAttributes, ReactNode } from 'react'
import { forwardRef, useEffect, useMemo, useRef, useState } from 'react' import { forwardRef, useEffect, useMemo, useRef, useState } from 'react'
import type { CommonProps, TypeAttributes } from '../@types/common' import type { CommonProps, TypeAttributes } from '../@types/common'
@ -75,7 +73,7 @@ const Input = forwardRef<ElementType | HTMLInputElement, InputProps>((props, ref
const { touched, errors } = form const { touched, errors } = form
const touchedField = get(touched, field.name) const touchedField = get(touched, field.name)
const errorField = get(errors, field.name) const errorField = get(errors, field.name)
validate = touchedField && errorField validate = !!touchedField && !!errorField
} }
if (typeof invalid === 'boolean') { if (typeof invalid === 'boolean') {
validate = invalid validate = invalid

View file

@ -4,7 +4,7 @@ import { CollapseContextProvider } from './context/collapseContext'
import classNames from 'classnames' import classNames from 'classnames'
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import MenuContext from './context/menuContext' 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 { CommonProps } from '../@types/common'
import type { ReactNode, MouseEvent } from 'react' import type { ReactNode, MouseEvent } from 'react'

View file

@ -1,5 +1,5 @@
import classNames from 'classnames' import classNames from 'classnames'
import { FaChevronRight } from 'react-icons/fa'; import { FaChevronRight } from '@/components/ui/icons';
import type { CommonProps } from '../@types/common' import type { CommonProps } from '../@types/common'
import type { MouseEvent } from 'react' import type { MouseEvent } from 'react'

View file

@ -1,6 +1,6 @@
import React, { useState, useEffect, useCallback, useMemo } from 'react' import React, { useState, useEffect, useCallback, useMemo } from 'react'
import classNames from 'classnames' import classNames from 'classnames'
import { FaChevronCircleLeft, FaEllipsisH, FaChevronCircleRight } from 'react-icons/fa'; import { FaChevronCircleLeft, FaEllipsisH, FaChevronCircleRight } from '@/components/ui/icons';
const PAGER_COUNT = 7 const PAGER_COUNT = 7

View file

@ -1,5 +1,5 @@
import classNames from 'classnames' import classNames from 'classnames'
import { FaChevronLeft } from 'react-icons/fa'; import { FaChevronLeft } from '@/components/ui/icons';
import type { CommonProps } from '../@types/common' import type { CommonProps } from '../@types/common'
import type { MouseEvent } from 'react' import type { MouseEvent } from 'react'

View file

@ -4,12 +4,11 @@ import ReactSelect, { components as ReactSelectComponents } from 'react-select'
import CreatableSelect from 'react-select/creatable' import CreatableSelect from 'react-select/creatable'
import AsyncSelect from 'react-select/async' import AsyncSelect from 'react-select/async'
import { TW_COLORS, BORDER_RADIUS } from '@/utils/tailwind' import { TW_COLORS, BORDER_RADIUS } from '@/utils/tailwind'
import isEmpty from 'lodash/isEmpty' import { isEmpty, get } from '@/utils/collection'
import get from 'lodash/get'
import { useConfig } from '../ConfigProvider' import { useConfig } from '../ConfigProvider'
import { useForm } from '../Form/context' import { useForm } from '../Form/context'
import { useInputGroup } from '../InputGroup/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 Spinner from '../Spinner/Spinner'
import type { CommonProps, TypeAttributes } from '../@types/common' import type { CommonProps, TypeAttributes } from '../@types/common'
import type { import type {
@ -184,7 +183,7 @@ function SelectBase<
const touchedField = get(touched, field.name) const touchedField = get(touched, field.name)
const errorField = get(errors, field.name) const errorField = get(errors, field.name)
isInvalid = touchedField && errorField isInvalid = !!touchedField && !!errorField
} }
const getBoxShadow = (state: ControlProps<Option, IsMulti, Group>) => { const getBoxShadow = (state: ControlProps<Option, IsMulti, Group>) => {

View file

@ -1,7 +1,7 @@
import { forwardRef } from 'react' import { forwardRef } from 'react'
import classNames from 'classnames' import classNames from 'classnames'
import { useConfig } from '../ConfigProvider' import { useConfig } from '../ConfigProvider'
import { FaSpinner } from 'react-icons/fa'; import { FaSpinner } from '@/components/ui/icons';
import type { CommonProps } from '../@types/common' import type { CommonProps } from '../@types/common'
import type { ElementType } from 'react' import type { ElementType } from 'react'

View file

@ -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 { TypeAttributes, CommonProps } from '../@types/common'
import type { ReactElement, ReactNode } from 'react' import type { ReactElement, ReactNode } from 'react'

View file

@ -1,7 +1,7 @@
import { forwardRef } from 'react' import { forwardRef } from 'react'
import classNames from 'classnames' import classNames from 'classnames'
import { useConfig } from '../ConfigProvider' 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 { STEPS_STATUS } from '../utils/constants'
import type { CommonProps, StepStatus } from '../@types/common' import type { CommonProps, StepStatus } from '../@types/common'
import type { ReactNode } from 'react' import type { ReactNode } from 'react'

View file

@ -1,5 +1,5 @@
import { useConfig } from '../ConfigProvider' 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' } export type SorterProps = { sort?: boolean | 'asc' | 'desc' }

View file

@ -12,7 +12,7 @@ import {
createAmPmHandler, createAmPmHandler,
createTimeHandler, createTimeHandler,
} from './utils' } from './utils'
import { FaClock } from 'react-icons/fa'; import { FaClock } from '@/components/ui/icons';
import type { CommonProps, TypeAttributes } from '../@types/common' import type { CommonProps, TypeAttributes } from '../@types/common'
import type { ReactNode, RefObject, Ref } from 'react' import type { ReactNode, RefObject, Ref } from 'react'

View file

@ -4,7 +4,7 @@ import useMergedRef from '../hooks/useMergeRef'
import useDidUpdate from '../hooks/useDidUpdate' import useDidUpdate from '../hooks/useDidUpdate'
import TimeInput from './TimeInput' import TimeInput from './TimeInput'
import CloseButton from '../CloseButton' import CloseButton from '../CloseButton'
import { FaClock } from 'react-icons/fa'; import { FaClock } from '@/components/ui/icons';
import Input from '../Input/Input' import Input from '../Input/Input'
import type { CommonProps, TypeAttributes } from '../@types/common' import type { CommonProps, TypeAttributes } from '../@types/common'
import type { RefObject, Ref, ReactNode } from 'react' import type { RefObject, Ref, ReactNode } from 'react'

View file

@ -4,7 +4,7 @@ import {
FaCaretLeft, FaCaretLeft,
FaCaretUp, FaCaretUp,
FaCaretRight, FaCaretRight,
} from 'react-icons/fa'; } from '@/components/ui/icons';
export type ArrowPlacement = export type ArrowPlacement =
| 'top' | 'top'

View file

@ -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' import type { CommonProps } from '../@types/common'
const BYTE = 1000 const BYTE = 1000

View file

@ -1,7 +1,7 @@
import { forwardRef, useRef, useState, useCallback, useEffect } from 'react' import { forwardRef, useRef, useState, useCallback, useEffect } from 'react'
import classNames from 'classnames' import classNames from 'classnames'
import { useConfig } from '../ConfigProvider' import { useConfig } from '../ConfigProvider'
import cloneDeep from 'lodash/cloneDeep' import { cloneDeep } from '@/utils/collection'
import FileItem from './FileItem' import FileItem from './FileItem'
import Button from '../Button/Button' import Button from '../Button/Button'
import CloseButton from '../CloseButton' import CloseButton from '../CloseButton'

View file

@ -1,63 +1,87 @@
import { forwardRef } from 'react' 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 classNames from 'classnames'
import type { CommonProps } from '../@types/common' import type { CommonProps } from '../@types/common'
import type { MouseEvent } from 'react' import type { MouseEvent } from 'react'
export interface WindowControlsProps extends CommonProps { export interface WindowControlsProps extends CommonProps {
absolute?: boolean absolute?: boolean
defaultStyle?: boolean
isMaximized?: boolean isMaximized?: boolean
onClose?: (e: MouseEvent<HTMLSpanElement>) => void onClose?: (e: MouseEvent<HTMLElement>) => void
onMaximize?: (e: MouseEvent<HTMLSpanElement>) => void onMaximize?: (e: MouseEvent<HTMLElement>) => void
onRestore?: (e: MouseEvent<HTMLSpanElement>) => void onRestore?: (e: MouseEvent<HTMLElement>) => void
showMaximize?: boolean showMaximize?: boolean
showRestore?: boolean
showClose?: boolean showClose?: boolean
maximizeTitle?: string
restoreTitle?: string
closeTitle?: string
} }
const buttonClass = 'window-controls-btn'
const WindowControls = forwardRef<HTMLDivElement, WindowControlsProps>((props, ref) => { const WindowControls = forwardRef<HTMLDivElement, WindowControlsProps>((props, ref) => {
const { const {
absolute, absolute,
className, className,
defaultStyle,
isMaximized, isMaximized,
onClose, onClose,
onMaximize, onMaximize,
onRestore, onRestore,
showMaximize = true, showMaximize = true,
showRestore = true,
showClose = true, showClose = true,
maximizeTitle = 'Maximize',
restoreTitle = 'Restore',
closeTitle = 'Close',
...rest ...rest
} = props } = props
const windowControlsAbsoluteClass = 'absolute z-10'
const windowControlsClass = classNames( const windowControlsClass = classNames(
'window-controls flex items-center gap-2', 'window-controls flex items-center gap-2',
defaultStyle && 'window-controls-default', absolute && 'absolute z-10 top-3',
absolute && windowControlsAbsoluteClass,
className className
) )
const buttonClass = 'close-btn cursor-pointer hover:opacity-70 transition-opacity' const stopPropagation = (e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
}
return ( return (
<div className={windowControlsClass} {...rest} ref={ref}> <div className={windowControlsClass} {...rest} ref={ref}>
{showMaximize && !isMaximized && ( {showMaximize &&
<span className={buttonClass} role="button" onClick={onMaximize} title="Maximize"> (isMaximized ? (
<FaWindowMaximize /> <button
</span> type="button"
)} className={buttonClass}
{showRestore && isMaximized && ( title={restoreTitle}
<span className={buttonClass} role="button" onClick={onRestore} title="Restore"> aria-label={restoreTitle}
<FaWindowRestore /> onMouseDown={stopPropagation}
</span> onClick={onRestore}
)} >
<FaWindowRestore />
</button>
) : (
<button
type="button"
className={buttonClass}
title={maximizeTitle}
aria-label={maximizeTitle}
onMouseDown={stopPropagation}
onClick={onMaximize}
>
<FaWindowMaximize />
</button>
))}
{showClose && ( {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 /> <FaTimes />
</span> </button>
)} )}
</div> </div>
) )

View file

@ -1,5 +1,5 @@
import { useRef } from 'react' import { useRef } from 'react'
import uniqueId from 'lodash/uniqueId' import { uniqueId } from '@/utils/collection'
import createUID from '../utils/createUid' import createUID from '../utils/createUid'
export default function useUniqueId(prefix = '', len = 10) { export default function useUniqueId(prefix = '', len = 10) {

View 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: [],
},
],
})

View file

@ -151,30 +151,47 @@ div.dialog-after-open > div.dialog-content.maximized {
/* React Modal prevent bg scroll */ /* React Modal prevent bg scroll */
/* Window Controls Styles */ /* Window Controls Styles */
.window-controls { .window-controls .window-controls-btn {
top: 12px;
}
.window-controls .close-btn {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 20px; width: 20px;
height: 20px; height: 20px;
border-radius: 4px; border-radius: 4px;
transition: all 0.2s ease; transition:
background-color 0.2s ease,
color 0.2s ease;
color: #666; color: #666;
font-size: 14px; font-size: 14px;
outline: none;
} }
.window-controls .close-btn:hover { .window-controls .window-controls-btn:hover {
background-color: rgba(0, 0, 0, 0.1); background-color: rgba(0, 0, 0, 0.1);
color: #333; 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; 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 */ /* Maximized dialog styles */

View file

@ -1,24 +1,81 @@
import * as fc from 'react-icons/fc' import { useSyncExternalStore } from 'react'
import * as fa from 'react-icons/fa'
import type { ComponentType } 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 adbileş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 ı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>> 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>) { const listeners = new Set<() => void>()
for (const [key, Icon] of Object.entries(iconModule)) { let loadPromise: Promise<void> | null = null
if (
Icon && function collectIcons(iconModules: Record<string, unknown>[]): NavigationIcons {
(typeof Icon === 'function' || const icons: NavigationIcons = {}
(typeof Icon === 'object' && 'render' in Icon)) for (const iconModule of iconModules) {
) { for (const [key, Icon] of Object.entries(iconModule)) {
navigationIcon[key] = Icon as ComponentType<any> 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) const getSnapshot = () => snapshot
registerIcons(fa)
export default navigationIcon /**
* Adikon 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; çıran taraflar zaten
* "ikon bulunamadı" durumunu (fallback ikon / null) ele alıyor.
*/
export function useNavigationIcons(): NavigationIcons {
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
}

View file

@ -1,6 +1,6 @@
import axios from 'axios' import axios from 'axios'
import { store } from '../store' import { store } from '../store'
import { jwtDecode } from 'jwt-decode' import { jwtDecode } from '@/utils/jwt'
import appConfig from '../proxy/configs/app.config' import appConfig from '../proxy/configs/app.config'
import { refreshToken, signOut as revokeTokens } from './auth.service' import { refreshToken, signOut as revokeTokens } from './auth.service'
import { isLoginSuccess } from '../proxy/account/models' import { isLoginSuccess } from '../proxy/account/models'

148
ui/src/utils/collection.ts Normal file
View 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ç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}`
}

View file

@ -1,4 +1,4 @@
import { jwtDecode } from 'jwt-decode' import { jwtDecode } from '@/utils/jwt'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import useQuery from './useQuery' import useQuery from './useQuery'
import { store, useStoreActions, useStoreState } from '../../store/store' import { store, useStoreActions, useStoreState } from '../../store/store'

View file

@ -1,5 +1,5 @@
import { useMemo } from 'react' import { useMemo } from 'react'
import isEmpty from 'lodash/isEmpty' import { isEmpty } from '@/utils/collection'
function useAuthority( function useAuthority(
userAuthority: string[] = [], userAuthority: string[] = [],

View file

@ -1,7 +1,7 @@
import { useStoreState } from '@/store/store' import { useStoreState } from '@/store/store'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { FaUser } from 'react-icons/fa' 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 { navigationTreeToFlat } from '@/utils/navigation'
import { ReactElement } from 'react' import { ReactElement } from 'react'
@ -18,6 +18,7 @@ const extractListFormCode = (path: string): string | null => {
export function useCurrentMenuIcon(className = 'w-6 h-6'): ReactElement { export function useCurrentMenuIcon(className = 'w-6 h-6'): ReactElement {
const mainMenu = useStoreState((state) => state.abpConfig.menu.mainMenu) const mainMenu = useStoreState((state) => state.abpConfig.menu.mainMenu)
const location = useLocation() const location = useLocation()
const navigationIcon = useNavigationIcons()
const menus = navigationTreeToFlat(mainMenu) const menus = navigationTreeToFlat(mainMenu)
const currentMenu = menus.find((menu) => { const currentMenu = menus.find((menu) => {

View file

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
// eslint-disable @typescript-eslint/no-explicit-any // eslint-disable @typescript-eslint/no-explicit-any
import type { NavigationTree } from '@/proxy/menus/navigation' import type { NavigationTree } from '@/proxy/menus/navigation'
import isPlainObject from 'lodash/isPlainObject' import { isPlainObject } from '@/utils/collection'
import { useMemo } from 'react' import { useMemo } from 'react'
interface NavInfo extends NavigationTree { interface NavInfo extends NavigationTree {

27
ui/src/utils/jwt.ts Normal file
View 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

View file

@ -3,8 +3,8 @@ import Loading from '@/components/shared/Loading'
import { Alert, Button } from '@/components/ui' import { Alert, Button } from '@/components/ui'
import { store, useStoreState } from '@/store' import { store, useStoreState } from '@/store'
import { Suspense, useEffect } from 'react' import { Suspense, useEffect } from 'react'
import { ErrorBoundary } from 'react-error-boundary' import ErrorBoundary from '@/components/shared/ErrorBoundary'
import type { FallbackProps } from 'react-error-boundary' import type { FallbackProps } from '@/components/shared/ErrorBoundary'
import { FaArrowLeft } from 'react-icons/fa' import { FaArrowLeft } from 'react-icons/fa'
import { Navigate, useLocation } from 'react-router-dom' import { Navigate, useLocation } from 'react-router-dom'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
@ -19,8 +19,8 @@ interface ViewsProps {
layout?: LayoutType layout?: LayoutType
} }
// react-error-boundary v6'dan itibaren `error` `unknown` tipinde geliyor; // `error` `unknown` tipinde geliyor; throw edilen her şey Error olmayabilir,
// throw edilen her şey Error olmayabilir, o yüzden daraltıyoruz. // o yüzden daraltıyoruz.
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
const { translate } = useLocalization() const { translate } = useLocalization()
const { name, message } = const { name, message } =

View file

@ -7,7 +7,7 @@ import { NotificationDto } from '@/proxy/notification/models'
import { getList } from '@/services/notification.service' import { getList } from '@/services/notification.service'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import groupBy from 'lodash/groupBy' import { groupBy } from '@/utils/collection'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import Log from './components/Log' import Log from './components/Log'
import LogFilter from './components/LogFilter' import LogFilter from './components/LogFilter'

View file

@ -5,8 +5,7 @@ import { AVATAR_URL } from '@/constants/app.constant'
import { NotificationDto } from '@/proxy/notification/models' import { NotificationDto } from '@/proxy/notification/models'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import isEmpty from 'lodash/isEmpty' import { isEmpty, keys } from '@/utils/collection'
import keys from 'lodash/keys'
import Event from './Event' import Event from './Event'
import TimeLineAvatar from './TimeLineAvatar' import TimeLineAvatar from './TimeLineAvatar'
import useLocale from '@/utils/hooks/useLocale' import useLocale from '@/utils/hooks/useLocale'

View file

@ -39,7 +39,7 @@ import { JsonRowDialogData } from './json-row-operations/types'
import JsonRowOpDialogSeries from './json-row-operations/JsonRowOpDialogSeries' import JsonRowOpDialogSeries from './json-row-operations/JsonRowOpDialogSeries'
import CreatableSelect from 'react-select/creatable' import CreatableSelect from 'react-select/creatable'
import { getListFormFields } from '@/services/admin/list-form-field.service' 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` // Bu sekme ListForm nesnesinin tamamını düzenler; `name` / `title` / `height`
// gibi alanların girdisi burada yok. Şema bu alanları zorunlu tutuyordu ve // gibi alanların girdisi burada yok. Şema bu alanları zorunlu tutuyordu ve

View file

@ -18,7 +18,7 @@ import { FormEditProps } from './FormEdit'
import { SelectBoxOption } from '@/types/shared' import { SelectBoxOption } from '@/types/shared'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { getListFormFields } from '@/services/admin/list-form-field.service' 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 { useParams } from 'react-router-dom'
import type { GanttScaleType } from 'devextreme/ui/gantt' import type { GanttScaleType } from 'devextreme/ui/gantt'
import { gantScaleOptions } from './options' import { gantScaleOptions } from './options'

View file

@ -18,7 +18,7 @@ import { FormEditProps } from './FormEdit'
import { SelectBoxOption } from '@/types/shared' import { SelectBoxOption } from '@/types/shared'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { getListFormFields } from '@/services/admin/list-form-field.service' 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 { useParams } from 'react-router-dom'
import { firstDayOfWeekOptions, schedulerViewOptions } from './options' import { firstDayOfWeekOptions, schedulerViewOptions } from './options'

View file

@ -14,7 +14,7 @@ import { getListFormFields } from '@/services/admin/list-form-field.service'
import { useStoreState } from '@/store' import { useStoreState } from '@/store'
import type { SelectBoxOption } from '@/types/shared' import type { SelectBoxOption } from '@/types/shared'
import { Field, FieldProps, Form, Formik } from 'formik' 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 { useCallback, useEffect, useRef, useState } from 'react'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import type { FormEditProps } from './FormEdit' import type { FormEditProps } from './FormEdit'

View file

@ -17,7 +17,7 @@ import { FormEditProps } from './FormEdit'
import { SelectBoxOption } from '@/types/shared' import { SelectBoxOption } from '@/types/shared'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { getListFormFields } from '@/services/admin/list-form-field.service' 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 { useParams } from 'react-router-dom'
const validationSchema = Yup.object().shape({}) const validationSchema = Yup.object().shape({})

View file

@ -25,7 +25,7 @@ import {
import { cascadeFilterOperator } from '../options' import { cascadeFilterOperator } from '../options'
import CreatableSelect from 'react-select/creatable' import CreatableSelect from 'react-select/creatable'
import { getListFormFields } from '@/services/admin/list-form-field.service' 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' import { extraFilterControlTypeOptions } from '@/proxy/admin/list-form/options'
const schema = object().shape({ const schema = object().shape({

View file

@ -20,7 +20,7 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
import { useStoreState } from '@/store/store' import { useStoreState } from '@/store/store'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
import { WizardFileInfoDto } from '@/proxy/admin/wizard/models' 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 { interface ConfirmState {
fileName: string fileName: string
@ -32,6 +32,7 @@ const WizardFileManager = () => {
const { translate } = useLocalization() const { translate } = useLocalization()
const mode = useStoreState((state) => state.theme.mode) const mode = useStoreState((state) => state.theme.mode)
const MenuIcon = useCurrentMenuIcon('w-5 h-5') const MenuIcon = useCurrentMenuIcon('w-5 h-5')
const navigationIcon = useNavigationIcons()
const [files, setFiles] = useState<WizardFileInfoDto[]>([]) const [files, setFiles] = useState<WizardFileInfoDto[]>([])
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)

View file

@ -1,7 +1,7 @@
import { Button, FormItem, Input, Notification, Select, toast } from '@/components/ui' import { Button, FormItem, Input, Notification, Select, toast } from '@/components/ui'
import { MenuDto } from '@/proxy/menus/models' import { MenuDto } from '@/proxy/menus/models'
import { SelectBoxOption } from '@/types/shared' 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 { MenuItem } from '@/proxy/menus/menu'
import { MenuService } from '@/services/menu.service' import { MenuService } from '@/services/menu.service'
import { Field, FieldProps, FormikErrors, FormikTouched } from 'formik' import { Field, FieldProps, FormikErrors, FormikTouched } from 'formik'
@ -132,6 +132,7 @@ function TreeNode({
const isExpanded = expanded.has(node.code) const isExpanded = expanded.has(node.code)
const isSelected = node.code === selectedCode const isSelected = node.code === selectedCode
const isEditing = editingCode === node.code const isEditing = editingCode === node.code
const navigationIcon = useNavigationIcons()
const NodeIcon = node.icon ? navigationIcon[node.icon] : null const NodeIcon = node.icon ? navigationIcon[node.icon] : null
const { translate } = useLocalization() const { translate } = useLocalization()

View file

@ -47,7 +47,7 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
import TableNoRecords from '@/views/shared/TableNoRecords' import TableNoRecords from '@/views/shared/TableNoRecords'
import classNames from 'classnames' import classNames from 'classnames'
import { Field, FieldProps, Form, Formik, FormikHelpers } from 'formik' 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 { memo, Suspense, useEffect, useMemo, useState } from 'react'
import { NodeApi, NodeRendererProps } from 'react-arborist' import { NodeApi, NodeRendererProps } from 'react-arborist'
import { Tree } from 'react-arborist/dist/module/components/tree' import { Tree } from 'react-arborist/dist/module/components/tree'

View file

@ -3,7 +3,7 @@ import Container from '@/components/shared/Container'
import { Button, Table, toast } from '@/components/ui' import { Button, Table, toast } from '@/components/ui'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import useThemeClass from '@/utils/hooks/useThemeClass' import useThemeClass from '@/utils/hooks/useThemeClass'
import isEmpty from 'lodash/isEmpty' import { isEmpty } from '@/utils/collection'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import PageTitle from '@/components/shared/PageTitle' import PageTitle from '@/components/shared/PageTitle'
import RolesPermission from './RolesPermission' import RolesPermission from './RolesPermission'

View file

@ -5,7 +5,7 @@ import { Table } from '@/components/ui'
import { TenantDto } from '@/proxy/config/models' import { TenantDto } from '@/proxy/config/models'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import useThemeClass from '@/utils/hooks/useThemeClass' import useThemeClass from '@/utils/hooks/useThemeClass'
import isEmpty from 'lodash/isEmpty' import { isEmpty } from '@/utils/collection'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import PageTitle from '@/components/shared/PageTitle' import PageTitle from '@/components/shared/PageTitle'
import TenantsConnectionString from './TenantsConnectionString' import TenantsConnectionString from './TenantsConnectionString'

View file

@ -4,7 +4,7 @@ import { Table } from '@/components/ui'
import { getUsers } from '@/services/identity.service' import { getUsers } from '@/services/identity.service'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import useThemeClass from '@/utils/hooks/useThemeClass' import useThemeClass from '@/utils/hooks/useThemeClass'
import isEmpty from 'lodash/isEmpty' import { isEmpty } from '@/utils/collection'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import PageTitle from '@/components/shared/PageTitle' import PageTitle from '@/components/shared/PageTitle'
import UsersPermission from './UsersPermission' import UsersPermission from './UsersPermission'

View file

@ -1,4 +1,4 @@
import { useQuery } from '@tanstack/react-query' import { useEffect, useRef } from 'react'
import { Alert, Button } from '@/components/ui' import { Alert, Button } from '@/components/ui'
import { useNavigate, useParams } from 'react-router-dom' import { useNavigate, useParams } from 'react-router-dom'
import useAccount from '@/utils/hooks/useAccount' import useAccount from '@/utils/hooks/useAccount'
@ -12,12 +12,17 @@ const VerifyConfirmationCode = () => {
const navigate = useNavigate() const navigate = useNavigate()
const { translate } = useLocalization() const { translate } = useLocalization()
useQuery({ // Doğrulama tek seferlik bir yan etki; StrictMode'un çift render'ında
queryKey: ['verifyConfirmationCode', userId, token], // isteğin iki kez gitmemesi için ref ile bir kereye kilitleniyor.
queryFn: () => verifyConfirmationCode(userId!, token!), const verifiedKey = useRef<string | null>(null)
retry: false,
refetchOnWindowFocus: false, useEffect(() => {
}) if (!userId || !token) return
const key = `${userId}|${token}`
if (verifiedKey.current === key) return
verifiedKey.current = key
verifyConfirmationCode(userId, token)
}, [userId, token, verifyConfirmationCode])
return ( return (
<> <>

View file

@ -21,7 +21,7 @@ import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
import { getMenus } from '@/services/menu.service' import { getMenus } from '@/services/menu.service'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { CascadeBehavior, SqlTableRelation, RelationshipType } from '@/proxy/developerKit/models' 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 { MenuItem } from '@/proxy/menus/menu'
import { import {
MenuTreeNode, MenuTreeNode,
@ -1122,6 +1122,7 @@ function SimpleMenuTreeNode({
const hasChildren = node.children.length > 0 const hasChildren = node.children.length > 0
const isExpanded = expanded.has(node.code) const isExpanded = expanded.has(node.code)
const isSelected = node.code === selectedCode const isSelected = node.code === selectedCode
const navigationIcon = useNavigationIcons()
const NodeIcon = node.icon ? navigationIcon[node.icon] : null const NodeIcon = node.icon ? navigationIcon[node.icon] : null
const { translate } = useLocalization() const { translate } = useLocalization()

View file

@ -1,5 +1,5 @@
import { Badge, Button, Dialog, Notification, toast } from '@/components/ui' 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 { useLocalization } from '@/utils/hooks/useLocalization'
import CustomStore from 'devextreme/data/custom_store' import CustomStore from 'devextreme/data/custom_store'
import { useState } from 'react' import { useState } from 'react'
@ -162,6 +162,8 @@ const FormButtons = (props: {
} }
} }
const navigationIcon = useNavigationIcons()
const toolbarExcludedNames = ['deleteAllRecords', 'deleteSelectedRecords', 'refreshButton'] const toolbarExcludedNames = ['deleteAllRecords', 'deleteSelectedRecords', 'refreshButton']
const hasVisibleToolbarButtons = toolbarData?.some(isToolbarButtonVisible) const hasVisibleToolbarButtons = toolbarData?.some(isToolbarButtonVisible)

View file

@ -1,10 +1,8 @@
import React from 'react' import React from 'react'
import Lightbox from 'yet-another-react-lightbox' import { createPortal } from 'react-dom'
import 'yet-another-react-lightbox/styles.css' import { AnimatePresence, motion } from 'framer-motion'
import Video from 'yet-another-react-lightbox/plugins/video' import { FaChevronLeft, FaChevronRight, FaTimes } from 'react-icons/fa'
import Zoom from 'yet-another-react-lightbox/plugins/zoom' import { Button } from '@/components/ui'
import Counter from 'yet-another-react-lightbox/plugins/counter'
import 'yet-another-react-lightbox/plugins/counter.css'
import { SocialMediaDto } from '@/proxy/intranet/models' import { SocialMediaDto } from '@/proxy/intranet/models'
interface MediaLightboxProps { interface MediaLightboxProps {
@ -14,53 +12,153 @@ interface MediaLightboxProps {
startIndex?: number 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> = ({ const MediaLightbox: React.FC<MediaLightboxProps> = ({
isOpen, isOpen,
onClose, onClose,
media, media,
startIndex = 0 startIndex = 0,
}) => { }) => {
const slides = React.useMemo(() => { const isVideo = media.type === 'video'
if (media.type === 'video' && media.urls && media.urls.length > 0) { const urls = React.useMemo(() => media.urls ?? [], [media])
return [ const [index, setIndex] = React.useState(startIndex)
{ const [zoom, setZoom] = React.useState(MIN_ZOOM)
type: 'video' as const,
sources: [ // Aynı bileşen farklı medya/indeks ile yeniden açılabiliyor.
{ React.useEffect(() => {
src: media.urls[0], if (isOpen) {
type: 'video/mp4' 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 || [] const previousOverflow = document.body.style.overflow
return urls.map((url) => ({ document.body.style.overflow = 'hidden'
src: url window.addEventListener('keydown', onKeyDown)
}))
}, [media])
return ( return () => {
<Lightbox document.body.style.overflow = previousOverflow
open={isOpen} window.removeEventListener('keydown', onKeyDown)
close={onClose} }
slides={slides} }, [isOpen, onClose, step])
index={startIndex}
plugins={[Video, Zoom, Counter]} const onWheel = (event: React.WheelEvent) => {
counter={{ container: { style: { top: 'unset', bottom: 0 } } }} if (isVideo) return
zoom={{ setZoom((current) =>
maxZoomPixelRatio: 3, Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, current - Math.sign(event.deltaY) * ZOOM_STEP)),
scrollToZoom: true )
}} }
video={{
controls: true, const stop = (event: React.MouseEvent) => event.stopPropagation()
autoPlay: false
}} return createPortal(
styles={{ <AnimatePresence>
container: { backgroundColor: 'rgba(0, 0, 0, 0.95)' } {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,
) )
} }

View file

@ -27,7 +27,7 @@ import {
import { ConfirmDialog } from '@/components/shared' import { ConfirmDialog } from '@/components/shared'
import { MenuItem } from '@/proxy/menus/menu' import { MenuItem } from '@/proxy/menus/menu'
import { MenuDto } from '@/proxy/menus/models' 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 { MenuService } from '@/services/menu.service'
import { SelectBoxOption } from '@/types/shared' import { SelectBoxOption } from '@/types/shared'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
@ -102,6 +102,7 @@ const MenuItemComponentBase: React.FC<MenuItemComponentProps> = ({
const childCount = item.children?.length ?? 0 const childCount = item.children?.length ?? 0
const hasChildren = childCount > 0 const hasChildren = childCount > 0
const navigationIcon = useNavigationIcons()
const IconComponent = (item.icon && navigationIcon[item.icon]) || FaQuestionCircle const IconComponent = (item.icon && navigationIcon[item.icon]) || FaQuestionCircle
const displayLabel = item.displayName ? translate('::' + item.displayName) : (item.code ?? '') const displayLabel = item.displayName ? translate('::' + item.displayName) : (item.code ?? '')

View file

@ -135,7 +135,7 @@ const Blog: React.FC = () => {
<input <input
type="search" type="search"
value={searchQuery} value={searchQuery}
placeholder={translateLabel(translate, 'Public.blog.searchPlaceholder')} placeholder={translateLabel(translate, 'Public.blog.SearchPlaceholder')}
aria-label={translate('::App.Platform.Search3')} 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" 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)} onChange={(event) => setSearchQuery(event.target.value)}

View file

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import Loading from '@/components/shared/Loading' import Loading from '@/components/shared/Loading'
import navigationIcon from '@/proxy/menus/navigation-icon.config' import { useNavigationIcons } from '@/proxy/menus/navigation-icon.config'
interface PageIconProps { interface PageIconProps {
name?: string name?: string
@ -10,6 +10,7 @@ interface PageIconProps {
/** Ikon anahtari tanimli degilse hicbir sey render etmez. */ /** Ikon anahtari tanimli degilse hicbir sey render etmez. */
export const PageIcon: React.FC<PageIconProps> = ({ name, className, size }) => { export const PageIcon: React.FC<PageIconProps> = ({ name, className, size }) => {
const navigationIcon = useNavigationIcons()
const IconComponent = name ? navigationIcon[name] : undefined const IconComponent = name ? navigationIcon[name] : undefined
if (!IconComponent) { if (!IconComponent) {

View file

@ -20,7 +20,7 @@ import { useStoreActions, useStoreState } from '@/store'
import { SelectBoxOption } from '@/types/shared' import { SelectBoxOption } from '@/types/shared'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { Field, FieldProps, Form, Formik } from 'formik' import { Field, FieldProps, Form, Formik } from 'formik'
import isEmpty from 'lodash/isEmpty' import { isEmpty } from '@/utils/collection'
import { useEffect, useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import PageTitle from '@/components/shared/PageTitle' import PageTitle from '@/components/shared/PageTitle'
import { FaQuestionCircle } from 'react-icons/fa' import { FaQuestionCircle } from 'react-icons/fa'

View file

@ -2,8 +2,8 @@ import { Button, Dialog, Notification, toast } from '@/components/ui'
import { MenuDto } from '@/proxy/menus/models' import { MenuDto } from '@/proxy/menus/models'
import { MenuItem } from '@/proxy/menus/menu' import { MenuItem } from '@/proxy/menus/menu'
import { MenuService } from '@/services/menu.service' import { MenuService } from '@/services/menu.service'
import navigationIcon from '@/proxy/menus/navigation-icon.config' import { useNavigationIcons } from '@/proxy/menus/navigation-icon.config'
import { useEffect, useRef, useState } from 'react' import { useEffect, useMemo, useRef, useState } from 'react'
import { FaChevronDown, FaPlus } from 'react-icons/fa' import { FaChevronDown, FaPlus } from 'react-icons/fa'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { useConfig } from '@/components/ui/ConfigProvider' import { useConfig } from '@/components/ui/ConfigProvider'
@ -19,10 +19,12 @@ interface IconPickerFieldProps {
invalid?: boolean invalid?: boolean
} }
const ALL_ICON_ENTRIES = Object.entries(navigationIcon)
const ICON_PAGE_SIZE = 100 const ICON_PAGE_SIZE = 100
export function IconPickerField({ value, onChange, invalid }: IconPickerFieldProps) { 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 [open, setOpen] = useState(false)
const [search, setSearch] = useState('') const [search, setSearch] = useState('')
const [limit, setLimit] = useState(ICON_PAGE_SIZE) 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 SelectedIcon = value ? navigationIcon[value] : null
const filtered = search.trim() const filtered = search.trim()
? ALL_ICON_ENTRIES.filter(([key]) => key.toLowerCase().includes(search.toLowerCase())) ? allIconEntries.filter(([key]) => key.toLowerCase().includes(search.toLowerCase()))
: ALL_ICON_ENTRIES : allIconEntries
const displayed = filtered.slice(0, limit) const displayed = filtered.slice(0, limit)
const hasMore = displayed.length < filtered.length const hasMore = displayed.length < filtered.length
const { translate } = useLocalization() const { translate } = useLocalization()

View file

@ -187,14 +187,12 @@ export default defineConfig(({ mode }) => {
'react-dom', 'react-dom',
'react-dom/client', 'react-dom/client',
'react-router-dom', 'react-router-dom',
'@tanstack/react-query',
'@tanstack/react-table', '@tanstack/react-table',
'axios', 'axios',
'dayjs', 'dayjs',
'formik', 'formik',
'yup', 'yup',
'classnames', 'classnames',
'lodash',
'easy-peasy', 'easy-peasy',
'framer-motion', 'framer-motion',
'react-icons/fa', '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 // 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. // 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 // ö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 // paketlerin bir kısmı yalnızca lazy view'lara giriyor; tek chunk'a
// toplandıklarında ilk yükleme ~650 KB büyüyor. // toplandıklarında ilk yükleme ~650 KB büyüyor.