Vulnerabilities, React 19, Devexpress 26.1.4

This commit is contained in:
Sedat ÖZTÜRK 2026-08-13 11:41:48 +03:00
parent f949d0ae78
commit 7d365946bb
100 changed files with 1839 additions and 3155 deletions

View file

@ -10,8 +10,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DevExpress.AspNetCore.Reporting" Version="25.1.7" />
<PackageReference Include="DevExpress.Drawing.Skia" Version="25.1.7" />
<!-- DevExpress rapor motorunda client (ui/package.json) ve server sürümü BİREBİR
aynı olmak zorundadır; farklı olursa tasarımcı kaydetme ve viewer preview
serileştirmesi bozulur. İkisini birlikte güncelleyin. -->
<PackageReference Include="DevExpress.AspNetCore.Reporting" Version="26.1.4" />
<PackageReference Include="DevExpress.Drawing.Skia" Version="26.1.4" />
<PackageReference Include="Volo.Abp.AspNetCore.MultiTenancy" Version="10.0.0" />
<PackageReference Include="Volo.Abp.Autofac" Version="10.0.0" />
<PackageReference Include="Volo.Abp.AspNetCore.Serilog" Version="10.0.0" />
@ -29,6 +32,12 @@
<PackageReference Include="Serilog.Sinks.PostgreSQL" Version="2.3.0" />
<PackageReference Include="Serilog.Sinks.MSSqlServer" Version="8.2.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="10.0.0" />
<!-- SignalR Redis backplane'i MessagePack 2.5.187'yi geçişli olarak getiriyor;
o sürümde 2 yüksek + 9 orta seviye açık var (NU1902/NU1903). NuGet'te
override yok, geçişli sürümü ezmenin yolu doğrudan referans vermek.
2.5.302 açıkları kapatan ilk 2.x sürümü ve SignalR'ın istediği
">= 2.5.187" aralığını karşılıyor. 3.x kırıcı, bilerek geçilmedi. -->
<PackageReference Include="MessagePack" Version="2.5.302" />
</ItemGroup>
<ItemGroup>

View file

@ -1,6 +1,6 @@
ARG ENV="dev"
FROM node:22-alpine
FROM node:24-alpine
ARG ENV=$ENV
RUN apk add --no-cache ca-certificates && update-ca-certificates
@ -8,8 +8,10 @@ ENV GENERATE_SOURCEMAP=false
ENV NODE_OPTIONS=--max-old-space-size=16384
WORKDIR /app
COPY package.json package-lock.json ./
# Vite 8 rolldown ile bundle ediyor; workbox-build hâlâ rollup çekiyor.
# Alpine (musl) native binding'leri lockfile'da optional olduğu için ikisi de elle kuruluyor.
RUN npm i && \
npm install @rollup/rollup-linux-x64-musl --save-dev && \
npm install @rolldown/binding-linux-x64-musl @rollup/rollup-linux-x64-musl --save-dev && \
npm rebuild rollup
COPY . .
RUN npm run build:production -- --mode $ENV

3584
ui/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -2,12 +2,21 @@
"name": "sozsoft-platform-ui",
"private": true,
"elstarVersion": "2.1.6",
"engines": {
"node": ">=24.19.0",
"npm": ">=11.19.0"
},
"type": "module",
"scripts": {
"devextreme:license": "devextreme-license --out src/devextreme-license.ts --force",
"prestart": "npm run devextreme:license",
"prebuild": "npm run devextreme:license",
"prebuild:production": "npm run devextreme:license",
"start": "vite",
"build": "vite build",
"build:production": "vite build",
"preview": "vite preview",
"typecheck": "tsc --noEmit",
"lint": "eslint .",
"lint:fix": "npm run lint -- --fix",
"prettier": "prettier --ignore-path .gitignore src --check",
@ -15,97 +24,94 @@
"format": "npm run prettier:fix && npm run lint:fix"
},
"dependencies": {
"@babel/generator": "^7.28.3",
"@babel/parser": "^7.28.0",
"@babel/standalone": "^7.28.0",
"@babel/traverse": "^7.28.0",
"@babel/types": "^7.28.0",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",
"@babel/standalone": "^7.29.8",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@floating-ui/react": "^0.27.2",
"@fullcalendar/daygrid": "^6.1.8",
"@fullcalendar/interaction": "^6.1.8",
"@fullcalendar/react": "^6.1.8",
"@fullcalendar/timegrid": "^6.1.8",
"@marsidev/react-turnstile": "^0.2.1",
"@microsoft/signalr": "^9.0.6",
"@monaco-editor/react": "^4.6.0",
"@tanstack/react-query": "^4.29.19",
"@tanstack/react-table": "^8.8.5",
"axios": "^1.18.1",
"@floating-ui/react": "^0.27.20",
"@fullcalendar/daygrid": "^6.1.21",
"@fullcalendar/interaction": "^6.1.21",
"@fullcalendar/react": "^6.1.21",
"@fullcalendar/timegrid": "^6.1.21",
"@marsidev/react-turnstile": "^1.6.0",
"@microsoft/signalr": "^10.0.11",
"@monaco-editor/react": "^4.7.0",
"@tanstack/react-query": "^5.101.4",
"@tanstack/react-table": "^8.21.3",
"axios": "^1.19.0",
"classnames": "^2.5.1",
"dayjs": "^1.11.13",
"devexpress-reporting-react": "^25.2.3",
"devextreme": "^25.2.3",
"devextreme-exceljs-fork": "^4.4.11",
"devextreme-react": "^25.2.3",
"easy-peasy": "^6.0.5",
"emoji-picker-react": "^4.14.1",
"file-saver": "^2.0.2",
"formik": "^2.4.6",
"framer-motion": "^11.15.0",
"dayjs": "^1.11.21",
"devexpress-reporting-react": "26.1.4",
"devextreme": "26.1.4",
"devextreme-exceljs-fork": "^4.4.13",
"devextreme-react": "26.1.4",
"easy-peasy": "^6.1.1",
"emoji-picker-react": "^4.19.1",
"file-saver": "^2.0.5",
"formik": "^2.4.9",
"framer-motion": "^13.1.0",
"jspdf": "^4.2.1",
"jwt-decode": "^4.0.0",
"lodash": "^4.18.1",
"react": "^18.3.1",
"react-advanced-cropper": "^0.20.0",
"react-arborist": "^3.4.0",
"react-custom-scrollbars-2": "^4.5.0",
"react-dom": "^18.3.1",
"react-error-boundary": "^5.0.0",
"react-helmet": "^6.1.0",
"react-highlight-words": "^0.20.0",
"react-icons": "^5.4.0",
"react": "^19.2.8",
"react-advanced-cropper": "^0.20.1",
"react-arborist": "^3.16.0",
"react-dom": "^19.2.8",
"react-error-boundary": "^6.1.2",
"react-highlight-words": "^0.21.0",
"react-icons": "^5.7.0",
"react-modal": "^3.16.3",
"react-router-dom": "^6.30.4",
"react-select": "^5.9.0",
"yet-another-react-lightbox": "^3.25.0",
"yup": "^1.6.1"
"react-router-dom": "^7.18.2",
"react-select": "^5.10.2",
"yet-another-react-lightbox": "^3.32.2",
"yup": "^1.7.1"
},
"devDependencies": {
"@eslint/js": "^9.39.0",
"@rolldown/plugin-babel": "^0.1.7",
"@tailwindcss/typography": "^0.5.15",
"@types/babel__standalone": "^7.1.9",
"@eslint/js": "^9.39.5",
"@tailwindcss/typography": "^0.5.20",
"@types/file-saver": "^2.0.7",
"@types/lodash": "^4.14.191",
"@types/node": "^20.19.11",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@types/react-helmet": "^6.1.9",
"@types/react-highlight-words": "^0.16.4",
"@types/react-modal": "^3.13.1",
"@typescript-eslint/eslint-plugin": "^8.62.1",
"@typescript-eslint/parser": "^8.62.1",
"@types/lodash": "^4.17.25",
"@types/node": "^24.10.1",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4",
"@types/react-highlight-words": "^0.20.1",
"@types/react-modal": "^3.16.3",
"@typescript-eslint/eslint-plugin": "^8.67.0",
"@typescript-eslint/parser": "^8.67.0",
"@vitejs/plugin-react": "^6.0.5",
"autoprefixer": "^10.4.14",
"browserslist": "^4.25.2",
"cssnano": "^6.0.1",
"eslint": "^9.39.0",
"autoprefixer": "^10.5.4",
"cssnano": "^8.0.5",
"eslint": "^9.39.5",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-import-resolver-typescript": "^4.4.5",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.3",
"globals": "^16.0.0",
"postcss": "^8.4.21",
"prettier": "^3.1.1",
"tailwindcss": "^3.4.17",
"eslint-plugin-react-refresh": "^0.5.4",
"globals": "^17.11.0",
"postcss": "^8.5.26",
"prettier": "^3.9.6",
"tailwindcss": "^3.4.19",
"typescript": "5.9",
"vite": "^8.2.0",
"vite": "^8.2.1",
"vite-plugin-pwa": "^1.3.0"
},
"overrides": {
"dompurify": "3.4.11",
"glob": "13.0.6"
"dompurify": "3.4.13",
"glob": "13.0.6",
"brace-expansion@1": "1.1.18",
"brace-expansion@2": "2.1.4",
"brace-expansion@5": "5.0.9",
"fast-uri": "3.1.5",
"workbox-build": {
"source-map": "0.8.0"
},
"source-map-support": {
"source-map": "0.6.1"
}
},
"volta": {
"node": "24.13.0",
"npm": "11.0.0"
"node": "24.19.0",
"npm": "11.19.0"
}
}

View file

@ -26,7 +26,8 @@ function App() {
return (
<StoreProvider store={store}>
<BrowserRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
{/* v7_startTransition / v7_relativeSplatPath react-router v7'de varsayılan davranış. */}
<BrowserRouter>
<DynamicRoutesProvider>
<ComponentProvider>
<QueryClientProvider client={queryClient}>

View file

@ -46,6 +46,35 @@
background: transparent;
}
/* ScrollBar bileşeninin scroll konteyneri (eski react-custom-scrollbars-2'nin
yerini alıyor). Thumb/track görünümü yukarıdaki `*` kurallarından geliyor. */
.app-scrollbar {
height: 100%;
overflow: auto;
overscroll-behavior: contain;
}
/* autoHide: thumb yalnızca hover/odakta görünür, yer kaplaması değişmez. */
.app-scrollbar-autohide {
scrollbar-color: transparent var(--app-scrollbar-track);
transition: scrollbar-color 0.2s ease;
}
.app-scrollbar-autohide:hover,
.app-scrollbar-autohide:focus-within {
scrollbar-color: var(--app-scrollbar-thumb) var(--app-scrollbar-track);
}
.app-scrollbar-autohide::-webkit-scrollbar-thumb {
background-color: transparent;
transition: background-color 0.2s ease;
}
.app-scrollbar-autohide:hover::-webkit-scrollbar-thumb,
.app-scrollbar-autohide:focus-within::-webkit-scrollbar-thumb {
background-color: var(--app-scrollbar-thumb);
}
.dx-scrollable-scrollbar.dx-scrollbar-vertical,
.dx-scrollable-scrollbar.dx-scrollbar-vertical .dx-scrollable-scroll {
width: var(--app-scrollbar-size) !important;

View file

@ -1,165 +0,0 @@
import React from "react";
import classNames from "classnames";
import { FaChevronUp, FaChevronDown, FaBox } from "react-icons/fa";
export interface Column<T> {
key: keyof T | string;
header: string;
width?: string;
sortable?: boolean;
render?: (item: T) => React.ReactNode;
}
interface DataTableProps<T> {
data: T[];
columns: Column<T>[];
loading?: boolean;
sortBy?: string;
sortDirection?: "asc" | "desc";
onSort?: (key: string) => void;
className?: string;
selectable?: boolean; // ✅ ekledik
selectedKeys?: string[]; // ✅ ekledik
onSelectionChange?: (keys: string[]) => void; // ✅ ekledik
}
function DataTable<T extends { id: string }>({
data,
columns,
loading = false,
sortBy,
sortDirection,
onSort,
className,
selectable = false,
selectedKeys = [],
onSelectionChange,
}: DataTableProps<T>) {
const allSelected = data.length > 0 && selectedKeys.length === data.length;
const handleSelectAll = (checked: boolean) => {
if (!onSelectionChange) return;
if (checked) {
onSelectionChange(data.map((d) => d.id));
} else {
onSelectionChange([]);
}
};
const handleSelectRow = (id: string, checked: boolean) => {
if (!onSelectionChange) return;
if (checked) {
onSelectionChange([...selectedKeys, id]);
} else {
onSelectionChange(selectedKeys.filter((k) => k !== id));
}
};
if (loading) {
// (loading skeleton senin kodundaki gibi kalabilir)
}
return (
<div
className={classNames(
"bg-white rounded-md shadow-sm border border-gray-200",
className
)}
>
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-50">
<tr>
{selectable && (
<th className="px-3 py-2">
<input
type="checkbox"
checked={allSelected}
onChange={(e) => handleSelectAll(e.target.checked)}
/>
</th>
)}
{columns.map((column) => (
<th
key={String(column.key)}
className={classNames(
"px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider",
column.sortable &&
onSort &&
"cursor-pointer hover:bg-gray-100 transition-colors",
column.width && `w-${column.width}`
)}
onClick={() =>
column.sortable && onSort && onSort(String(column.key))
}
>
<div className="flex items-center">
{column.header}
{column.sortable && onSort && (
<div className="ml-1">
{sortBy === column.key ? (
sortDirection === "asc" ? (
<FaChevronUp size={12} className="text-gray-400" />
) : (
<FaChevronDown
size={12}
className="text-gray-400"
/>
)
) : (
<div className="w-3 h-3" />
)}
</div>
)}
</div>
</th>
))}
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{data.map((item) => (
<tr key={item.id} className="hover:bg-gray-50 transition-colors">
{selectable && (
<td className="px-3 py-2">
<input
type="checkbox"
checked={selectedKeys.includes(item.id)}
onChange={(e) =>
handleSelectRow(item.id, e.target.checked)
}
/>
</td>
)}
{columns.map((column) => (
<td
key={String(column.key)}
className="px-3 py-2 whitespace-nowrap text-sm text-gray-900"
>
{column.render
? column.render(item)
: String((item as any)[column.key] || "-")}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
{data.length === 0 && (
<div className="text-center py-8">
<div className="text-gray-400 mb-3">
<FaBox className="mx-auto h-8 w-8" />
</div>
<h3 className="text-sm font-medium text-gray-900">Veri bulunamadı</h3>
<p className="mt-1 text-xs text-gray-500">
Kriterlere uygun kayıt bulunmuyor.
</p>
</div>
)}
</div>
);
}
export default DataTable;

View file

@ -129,7 +129,7 @@ const Simple = ({ children, content, ...rest }: SimpleProps) => {
<div className="text-center">
{content}
{children
? cloneElement(children as ReactElement, {
? cloneElement(children as ReactElement<any>, {
contentClassName: 'text-center',
...rest,
})

View file

@ -0,0 +1,70 @@
import { useLayoutEffect } from 'react'
import { APP_NAME } from '@/constants/app.constant'
/**
* `react-helmet` yerine geçer. O paket 2020'den beri bakımsız ve
* `UNSAFE_componentWillMount` kullanıyor.
*
* React 19 `<title>` etiketini kendisi `<head>`'e taşıyor, ama **dedupe
* etmiyor**: aynı anda iki bileşen `<title>` render ederse head'de iki etiket
* oluşur ve tarayıcı belge sırasındaki ilkini kullanır. Bu uygulamada
* `RouteTitle` (bkz. routes/dynamicRouter.tsx) sayfadan ÖNCE render olup
* yalnızca boşluğu doldurmak istiyor; native davranışla yanlış taraf kazanırdı.
*
* Bu yüzden Helmet'in çözümleme mantığı küçük bir kayıt defterinde korunuyor:
* - `fallback` olmayan bir başlık, `fallback` olanı her zaman yener,
* - önceliktekiler arasında en son kaydedilen kazanır,
* - hiçbiri yoksa `APP_NAME`'e düşülür (Helmet'teki `defaultTitle`).
*/
type Entry = {
id: number
value: string
fallback: boolean
}
const entries: Entry[] = []
let nextId = 0
const resolve = () => {
for (let i = entries.length - 1; i >= 0; i--) {
if (!entries[i].fallback) return entries[i].value
}
return entries.length ? entries[entries.length - 1].value : APP_NAME
}
const apply = () => {
const next = resolve()
if (document.title !== next) document.title = next
}
export interface PageTitleProps {
/** Sayfa adı. Boş/undefined ise yalnızca uygulama adı gösterilir. */
title?: string | null
/**
* Helmet'teki `titleTemplate`/`defaultTitle` için uygulama adı.
* Varsayılan `APP_NAME`.
*/
appName?: string
/** Yalnızca daha spesifik bir başlık yoksa uygulanır. */
fallback?: boolean
}
const PageTitle = ({ title, appName = APP_NAME, fallback = false }: PageTitleProps) => {
const value = title ? `${title} | ${appName}` : appName
useLayoutEffect(() => {
const entry: Entry = { id: nextId++, value, fallback }
entries.push(entry)
apply()
return () => {
const index = entries.indexOf(entry)
if (index >= 0) entries.splice(index, 1)
apply()
}
}, [value, fallback])
return null
}
export default PageTitle

View file

@ -24,7 +24,7 @@ const TenantSelector = () => {
const { setTenant } = useStoreActions((actions) => actions.auth.tenant)
const { setWarning } = useStoreActions((actions) => actions.base.messages)
const requestIdRef = useRef(0)
const lastRequestedTenantNameRef = useRef<string>()
const lastRequestedTenantNameRef = useRef<string | undefined>(undefined)
const subDomainName = getSubdomain()
const isSubdomainTenant = !!subDomainName && subDomainName !== defaultDomain

View file

@ -6,7 +6,7 @@ import useDarkMode from '@/utils/hooks/useDarkmode'
import { useSetting } from '@/utils/hooks/useSetting'
import useTabFocus from '@/utils/hooks/useTabFocus'
import { ComponentProps, useEffect } from 'react'
import { Helmet } from 'react-helmet'
import { useStylesheet } from '@/utils/hooks/useStylesheet'
import { useNavigate, useLocation } from 'react-router-dom'
import { getSetupStatus } from '@/services/setup.service'
import { ROUTES_ENUM } from '@/routes/route.constant'
@ -92,20 +92,13 @@ const Theme = (props: CommonProps) => {
...{ locale },
} as ConfigProviderValue
return (
<>
{setting('App.SiteManagement.Theme.Style') && (
<Helmet key="Devexpress stylesheet Helmet">
<link
key="Devexpress stylesheet Helmet link"
rel="stylesheet"
href={`/css/${getThemeStyle()}.css`}
/>
</Helmet>
)}
<ConfigProvider value={currentTheme}>{props.children}</ConfigProvider>
</>
// DevExtreme tema CSS'i. Ayar kapalıysa href null olur ve etiket kaldırılır.
useStylesheet(
setting('App.SiteManagement.Theme.Style') ? `/css/${getThemeStyle()}.css` : null,
'devextreme-theme-css',
)
return <ConfigProvider value={currentTheme}>{props.children}</ConfigProvider>
}
export default Theme

View file

@ -95,7 +95,7 @@ const Card = forwardRef<HTMLDivElement, CardProps>((props, ref) => {
const cardHeaderClass = classNames(
'card-header',
headerBorder && 'card-header-border',
headerExtra && 'card-header-extra',
!!headerExtra && 'card-header-extra',
headerClass
)
const cardFooterClass = classNames(

View file

@ -11,7 +11,6 @@ export interface CheckboxProps extends CommonProps {
color?: string
defaultChecked?: boolean
disabled?: boolean
labelRef?: string
name?: string
onChange?: (values: boolean, e: ChangeEvent<HTMLInputElement>) => void
readOnly?: boolean
@ -39,7 +38,6 @@ const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>((props, ref) => {
defaultChecked,
value,
checked: controlledChecked,
labelRef,
field,
...rest
} = props
@ -137,7 +135,7 @@ const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>((props, ref) => {
)
return (
<label ref={labelRef} className={labelClass}>
<label className={labelClass}>
<input
ref={ref}
className={checkBoxClass}

View file

@ -62,8 +62,8 @@ const Dropdown = forwardRef<HTMLDivElement, DropdownProps>((props, ref) => {
...rest
} = props
const overlayTarget = useRef(null)
const triggerTarget = useRef(null)
const overlayTarget = useRef<HTMLElement>(null)
const triggerTarget = useRef<HTMLDivElement>(null)
const menuControl = useDropdownMenuContext(overlayTarget)
const open = menuControl.open

View file

@ -33,7 +33,7 @@ const Menu = forwardRef<HTMLElement, DropdownInnerMenuProps>((props, ref) => {
const menuRef = useUncertainRef<HTMLElement>(ref)
const menuId = useUniqueId('menu-')
const menuControl = useDropdownMenuContext<HTMLElement>(
menuRef as RefObject<HTMLElement>
menuRef as RefObject<HTMLElement | null>
)
const getTransform = (deg: number) => {

View file

@ -64,9 +64,9 @@ const DropdownItem = forwardRef<HTMLElement, DropdownItemProps>(
const menuitemRef = useUncertainRef<HTMLElement>(
ref
) as RefObject<HTMLElement>
) as RefObject<HTMLElement | null>
const menuitemId = useUniqueId('menu-item-')
const submenuRef = useRef(null)
const submenuRef = useRef<HTMLElement>(null)
const dropdown = useContext(DropdownContext)
const menu = useContext(MenuContext)
@ -185,7 +185,7 @@ const DropdownItem = forwardRef<HTMLElement, DropdownItemProps>(
return (
<DropdownMenuContextProvider value={submenuControl}>
{cloneElement(submenu, {
{cloneElement(submenu as ReactElement<any>, {
ref: submenuRef,
hidden: !open,
})}

View file

@ -22,7 +22,7 @@ export const DropdownMenuContextProvider = DropdownMenuContext.Provider
export const DropdownMenuContextConsumer = DropdownMenuContext.Consumer
export function useDropdownMenuContext<E extends HTMLElement>(
menuRef: React.RefObject<E>
menuRef: React.RefObject<E | null>
) {
const [open, setOpen] = useState(false)

View file

@ -87,7 +87,7 @@ const Notification = forwardRef<HTMLDivElement, NotificationProps>(
<div
className={classNames(
'notification-title',
children && 'mb-1'
!!children && 'mb-1'
)}
>
{title}

View file

@ -19,7 +19,6 @@ export interface RadioProps
color?: string
defaultChecked?: boolean
disabled?: boolean
labelRef?: string
name?: string
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onChange?: (value: any, e: MouseEvent) => void
@ -50,7 +49,6 @@ const Radio = forwardRef<HTMLInputElement, RadioProps>((props, ref) => {
defaultChecked,
disabled = disabledContext,
field,
labelRef,
name = nameContext,
onChange,
readOnly,
@ -122,7 +120,7 @@ const Radio = forwardRef<HTMLInputElement, RadioProps>((props, ref) => {
)
return (
<label ref={labelRef} className={labelClass}>
<label className={labelClass}>
<input
ref={ref}
type="radio"

View file

@ -1,101 +1,38 @@
import { forwardRef } from 'react'
import { Scrollbars } from 'react-custom-scrollbars-2'
import type { ScrollbarProps as ReactCustomScrollbarProps } from 'react-custom-scrollbars-2'
import classNames from 'classnames'
import type { ComponentPropsWithoutRef } from 'react'
import type { TypeAttributes } from '../@types/common'
export interface ScrollbarProps extends ReactCustomScrollbarProps {
direction?: TypeAttributes.Direction
/**
* Eskiden `react-custom-scrollbars-2` sarmalayıcısıydı. O paket React 19'u
* desteklemiyor (peer aralığı ^18'de duruyor) ve 2021'den beri bakımsız.
*
* Yerine düz bir overflow konteyneri kondu: scrollbar görünümü zaten
* `src/assets/styles/tailwind/index.css` içinde `*` seçicisine uygulanan
* `--app-scrollbar-*` değişkenleriyle tanımlı, yani eski özel `renderThumb*` /
* `renderTrack*` fonksiyonlarının ürettiği görünümün aynısı bedavaya geliyor.
*/
export interface ScrollbarProps extends ComponentPropsWithoutRef<'div'> {
direction?: TypeAttributes.Direction
/** Scrollbar yalnızca hover/odak sırasında görünsün. */
autoHide?: boolean
}
export type ScrollbarRef = Scrollbars
export type ScrollbarRef = HTMLDivElement
const ScrollBar = forwardRef<ScrollbarRef, ScrollbarProps>((props, ref) => {
const {
direction = 'ltr',
renderThumbVertical,
renderThumbHorizontal,
renderTrackVertical,
renderTrackHorizontal,
...rest
} = props
const { autoHide = false, className, direction = 'ltr', children, ...rest } = props
return (
<Scrollbars
ref={ref}
renderView={(props) => (
<div
{...props}
style={{
...props.style,
...(direction === 'rtl' && {
marginLeft: props.style.marginRight,
marginRight: 0,
}),
}}
/>
)}
renderTrackVertical={
renderTrackVertical ||
((props) => (
<div
{...props}
style={{
...props.style,
top: 0,
right: direction === 'rtl' ? 'auto' : 0,
left: direction === 'rtl' ? 0 : 'auto',
bottom: 0,
width: 'var(--app-scrollbar-size)',
backgroundColor: 'var(--app-scrollbar-track)',
}}
/>
))
}
renderTrackHorizontal={
renderTrackHorizontal ||
((props) => (
<div
{...props}
style={{
...props.style,
right: 0,
left: 0,
bottom: 0,
height: 'var(--app-scrollbar-size)',
backgroundColor: 'var(--app-scrollbar-track)',
}}
/>
))
}
renderThumbVertical={
renderThumbVertical ||
((props) => (
<div
{...props}
style={{
...props.style,
borderRadius: 999,
backgroundColor: 'var(--app-scrollbar-thumb)',
}}
/>
))
}
renderThumbHorizontal={
renderThumbHorizontal ||
((props) => (
<div
{...props}
style={{
...props.style,
borderRadius: 999,
backgroundColor: 'var(--app-scrollbar-thumb)',
}}
/>
))
}
{...rest}
/>
)
return (
<div
ref={ref}
className={classNames('app-scrollbar', autoHide && 'app-scrollbar-autohide', className)}
dir={direction}
{...rest}
>
{children}
</div>
)
})
ScrollBar.displayName = 'ScrollBar'

View file

@ -73,7 +73,7 @@ const StepItem = forwardRef<HTMLDivElement, StepItemProps>((props, ref) => {
const stepConnectClass = classNames(
'step-connect',
title && 'ml-2.5 rtl:mr-2.5',
!!title && 'ml-2.5 rtl:mr-2.5',
vertical && 'step-connect-vertical',
status === COMPLETE ? `bg-${color}` : `inactive`
)

View file

@ -12,7 +12,6 @@ export interface SwitcherProps extends CommonProps {
defaultChecked?: boolean
disabled?: boolean
isLoading?: boolean
labelRef?: string
name?: string
onChange?: (checked: boolean, e: ChangeEvent<HTMLInputElement>) => void
readOnly?: boolean
@ -30,7 +29,6 @@ const Switcher = forwardRef<HTMLInputElement, SwitcherProps>((props, ref) => {
defaultChecked,
disabled,
isLoading = false,
labelRef,
name,
onChange,
readOnly,
@ -102,7 +100,7 @@ const Switcher = forwardRef<HTMLInputElement, SwitcherProps>((props, ref) => {
)
return (
<label ref={labelRef} className={switcherClass}>
<label className={switcherClass}>
<input
ref={ref}
type="checkbox"

View file

@ -24,7 +24,7 @@ const AmPmInput = forwardRef<HTMLInputElement, AmPmInputProps>((props, ref) => {
const { className, onChange, onFocus, value, amLabel, pmLabel, ...rest } =
props
const inputRef = useRef<HTMLInputElement>()
const inputRef = useRef<HTMLInputElement | undefined>(undefined)
const handleClick = (event: MouseEvent<HTMLInputElement>) => {
event.stopPropagation()

View file

@ -75,10 +75,10 @@ const TimeInput = forwardRef<HTMLInputElement, TimeInputProps>((props, ref) => {
const uuid = useUniqueId(id)
const hoursRef = useRef<HTMLInputElement>()
const minutesRef = useRef<HTMLInputElement>()
const secondsRef = useRef<HTMLInputElement>()
const amPmRef = useRef<HTMLInputElement>()
const hoursRef = useRef<HTMLInputElement | undefined>(undefined)
const minutesRef = useRef<HTMLInputElement | undefined>(undefined)
const secondsRef = useRef<HTMLInputElement | undefined>(undefined)
const amPmRef = useRef<HTMLInputElement | undefined>(undefined)
const [time, setTime] = useState(
getTimeValues(value || (defaultValue as Date), format, amLabel, pmLabel)
)

View file

@ -43,7 +43,7 @@ const TimeInputField = forwardRef<HTMLInputElement, TimeInputFieldProps>(
const [digitsEntered, setDigitsEntered] = useState(0)
const inputRef = useRef<HTMLInputElement>()
const inputRef = useRef<HTMLInputElement | undefined>(undefined)
const handleFocus = (event: FocusEvent<HTMLInputElement>) => {
typeof onFocus === 'function' && onFocus(event)

View file

@ -64,8 +64,8 @@ const TimeInputRange = forwardRef<HTMLInputElement, TimeInputRangeProps>(
const uuid = useUniqueId(id)
const fromTimeRef = useRef<HTMLInputElement>()
const toTimeRef = useRef<HTMLInputElement>()
const fromTimeRef = useRef<HTMLInputElement | undefined>(undefined)
const toTimeRef = useRef<HTMLInputElement | undefined>(undefined)
const [_value, setValue] = useState(value ?? defaultValue)
useDidUpdate(() => {

View file

@ -1,9 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useEffect, useCallback } from 'react'
import { findDOMNode } from 'react-dom'
import type { RefObject, ReactEventHandler } from 'react'
type TargetType = RefObject<Element> | Element | null | undefined
type TargetType = RefObject<Element | null> | Element | null | undefined
type Options = {
disabled: boolean
@ -35,10 +34,18 @@ const domContains = (context: Element, node: (Node & ParentNode) | null) => {
return false
}
const getRefTarget = (ref: RefObject<Element> | Element | null | undefined) => {
const getRefTarget = (
ref: RefObject<Element | null> | Element | null | undefined
) => {
return ref && ('current' in ref ? ref.current : ref)
}
/**
* React 19 `findDOMNode`'u kaldırdı. Eski fallback yalnızca sınıf bileşeni
* örneği verildiğinde devreye giriyordu; React 19'da böyle bir hedef zaten
* oluşamıyor (bu hook'a ref ya da DOM elemanı geliyor). DOM düğümü
* çözülemezse null dönüp çıran taraf sessizce atlıyor.
*/
function getDOMNode(elementOrRef: any) {
const element =
elementOrRef?.root || elementOrRef?.child || getRefTarget(elementOrRef)
@ -47,8 +54,7 @@ function getDOMNode(elementOrRef: any) {
return element
}
// eslint-disable-next-line react/no-find-dom-node
return findDOMNode(element)
return null
}
function isLeftClickEvent(e: MouseEvent) {

View file

@ -10,7 +10,7 @@ function useTimeout(
ms = 0,
enabled = true
): UseTimeoutReturn {
const timeout = useRef<ReturnType<typeof setTimeout>>()
const timeout = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
const callback = useRef(fn)
const clear = useCallback(() => {

View file

@ -3,7 +3,7 @@ import uniqueId from 'lodash/uniqueId'
import createUID from '../utils/createUid'
export default function useUniqueId(prefix = '', len = 10) {
const idRef = useRef<string>()
const idRef = useRef<string | undefined>(undefined)
if (!idRef.current) {
idRef.current = `${uniqueId(prefix)}-${createUID(len)}`

View file

@ -1,8 +1,7 @@
// DynamicRouter.tsx
import React from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { Routes, Route, Navigate, useLocation } from 'react-router-dom'
import { APP_NAME } from '@/constants/app.constant'
import { mapCustomComponentRoutes, mapDynamicRoutes } from './dynamicRouteLoader'
import { useDynamicRoutes } from './dynamicRoutesContext'
import { useComponents } from '@/contexts/ComponentContext'
@ -19,13 +18,12 @@ const DatabaseSetup = React.lazy(() => import('@/views/setup/DatabaseSetup'))
/**
* Physical views set their own title; this only fills the gap for routes whose
* component is compiled at runtime. It renders before the page, so a component
* that declares its own Helmet still wins.
* component is compiled at runtime. `fallback` sayesinde kendi başlığını
* bildiren bir bileşen buradaki başlığı her zaman yener eskiden bu, Helmet'in
* "sonra mount olan kazanır" davranışından geliyordu.
*/
const RouteTitle = ({ title }: { title?: string }) =>
title ? (
<Helmet defaultTitle={APP_NAME} title={title} titleTemplate={`%s | ${APP_NAME}`}></Helmet>
) : null
title ? <PageTitle fallback title={title} /> : null
const RootRedirect = () => {
const location = useLocation()

View file

@ -0,0 +1,59 @@
import { useLayoutEffect } from 'react'
/**
* `<head>` içinde tek bir `<link rel="stylesheet">` etiketini yönetir.
*
* Daha önce bu `react-helmet`'e veriliyordu. React 19 `<link rel="stylesheet">`
* etiketini yalnızca `precedence` verilirse hoist ediyor; o modda da yüklenene
* kadar render'ı askıya alıyor ve href değişince eskisini head'de bırakıyor
* (yeniden kullanım için, tasarım gereği). Tema CSS'i gibi href'i değişen
* durumlarda bu, iki temanın üst üste binmesi demek bu yüzden etiket `id`
* üzerinden elle yönetiliyor.
*
* Aynı `id`'yi birden fazla bileşen isteyebildiği için referans sayımı var:
* etiket ancak son kullanıcı da bıraktığında kaldırılır. (Gantt CSS'i hem ana
* görünümden hem alt formdan istenebiliyor.)
*
* @param href Yüklenecek stylesheet. `null`/`undefined` ise bu bileşen talebini bırakır.
* @param id Etiketin sabit DOM id'si; href değişimlerinde aynı etiket güncellenir.
*/
type Slot = {
count: number
link: HTMLLinkElement
}
const slots = new Map<string, Slot>()
export const useStylesheet = (href: string | null | undefined, id: string) => {
useLayoutEffect(() => {
if (!href) return
let slot = slots.get(id)
if (!slot) {
const existing = document.getElementById(id) as HTMLLinkElement | null
const link = existing ?? document.createElement('link')
if (!existing) {
link.id = id
link.rel = 'stylesheet'
document.head.appendChild(link)
}
slot = { count: 0, link }
slots.set(id, slot)
}
slot.count += 1
if (slot.link.getAttribute('href') !== href) slot.link.href = href
return () => {
const current = slots.get(id)
if (!current) return
current.count -= 1
if (current.count <= 0) {
current.link.remove()
slots.delete(id)
}
}
}, [href, id])
}
export default useStylesheet

View file

@ -1,10 +1,9 @@
import Container from '@/components/shared/Container'
import DoubleSidedImage from '@/components/shared/DoubleSidedImage'
import { Button } from '@/components/ui'
import { APP_NAME } from '@/constants/app.constant'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { FaArrowLeft } from 'react-icons/fa';
import { useNavigate } from 'react-router-dom'
@ -17,11 +16,7 @@ const AccessDenied = () => {
return (
<Container className="h-full">
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'Access Denied')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::' + 'Access Denied')} />
<div className="h-full flex flex-col items-center justify-center p-28">
<DoubleSidedImage
src="/img/others/img-2.png"

View file

@ -1,6 +1,5 @@
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Helmet } from 'react-helmet'
import { APP_NAME } from '@/constants/app.constant'
import PageTitle from '@/components/shared/PageTitle'
import IntranetDashboard from './intranet/Dashboard'
const Dashboard = () => {
@ -8,11 +7,7 @@ const Dashboard = () => {
return (
<>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'Dashboard')}
defaultTitle={APP_NAME}
/>
<PageTitle title={translate('::' + 'Dashboard')} />
<IntranetDashboard />
</>
)

View file

@ -1,8 +1,7 @@
import { Button } from '@/components/ui'
import { APP_NAME } from '@/constants/app.constant'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { useNavigate } from 'react-router-dom'
const NotFoundPage = () => {
@ -14,11 +13,7 @@ const NotFoundPage = () => {
return (
<div className="p-28">
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'Not Found')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::' + 'Not Found')} />
<div className="flex items-center justify-center font-inter">
<div className="text-[8rem] sm:text-[10rem] md:text-[12rem] font-bold bg-gradient-to-br from-primary to-secondary bg-clip-text animate-pulse">
404

View file

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'
import classNames from 'classnames'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { Button } from '@/components/ui'
import { APP_NAME } from '@/constants/app.constant'
import { ROUTES_ENUM } from '@/routes/route.constant'
@ -35,7 +35,7 @@ const ServiceUnavailable = () => {
isDark ? 'bg-gray-900 text-gray-100' : 'bg-gray-100 text-gray-900',
)}
>
<Helmet title={`${APP_NAME} — Service unavailable`} />
<PageTitle title="Service unavailable" />
<div
className={classNames(

View file

@ -4,6 +4,7 @@ import { Alert, Button } from '@/components/ui'
import { store, useStoreState } from '@/store'
import { Suspense, useEffect } from 'react'
import { ErrorBoundary } from 'react-error-boundary'
import type { FallbackProps } from 'react-error-boundary'
import { FaArrowLeft } from 'react-icons/fa'
import { Navigate, useLocation } from 'react-router-dom'
import DialogProvider from './shared/DialogContext'
@ -17,11 +18,16 @@ interface ViewsProps {
layout?: LayoutType
}
function fallbackRender({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: any }) {
// react-error-boundary v6'dan itibaren `error` `unknown` tipinde geliyor;
// throw edilen her şey Error olmayabilir, o yüzden daraltıyoruz.
function fallbackRender({ error, resetErrorBoundary }: FallbackProps) {
const { name, message } =
error instanceof Error ? error : { name: 'Hata!', message: String(error) }
return (
<Alert showIcon className="mb-4" type="danger">
<h5>{error.name ?? 'Hata!'}</h5>
<div>{error.message}</div>
<h5>{name ?? 'Hata!'}</h5>
<div>{message}</div>
<Button
icon={<FaArrowLeft />}
size="sm"

View file

@ -11,8 +11,7 @@ import groupBy from 'lodash/groupBy'
import { useCallback, useEffect, useState } from 'react'
import Log from './components/Log'
import LogFilter from './components/LogFilter'
import { Helmet } from 'react-helmet'
import { APP_NAME } from '@/constants/app.constant'
import PageTitle from '@/components/shared/PageTitle'
import { DIR_RTL } from '@/constants/theme.constant'
import { useStoreState } from '@/store'
@ -86,11 +85,7 @@ const ActivityLog = () => {
return (
<Container>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'Abp.Identity.ActivityLogs')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::' + 'Abp.Identity.ActivityLogs')} />
<AdaptableCard className="overflow-hidden bg-white dark:bg-gray-800">
<div className="w-full">

View file

@ -1,5 +1,5 @@
import { useState, useEffect, useCallback, useMemo, useRef, type MouseEvent } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { Button, Input, Select, toast, Notification, Spinner } from '@/components/ui'
import { useStoreActions, useStoreState } from '@/store'
import {
@ -39,7 +39,6 @@ import type {
FileManagerFilters,
} from '@/types/fileManagement'
import classNames from 'classnames'
import { APP_NAME } from '@/constants/app.constant'
const { VITE_CDN_URL } = import.meta.env
@ -800,11 +799,7 @@ const FileManager = () => {
return (
<Container className="px-1">
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::App.Files')}
defaultTitle={APP_NAME}
/>
<PageTitle title={translate('::App.Files')} />
{/* Toolbar */}
<div className="dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-2 mb-4 mt-2">

View file

@ -21,7 +21,7 @@ import {
FaUndo,
FaFileImage,
} from 'react-icons/fa'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { AnimatePresence } from 'framer-motion'
import {
getOrgChartDepartments,
@ -30,7 +30,7 @@ import {
OrgChartUserDto,
} from '@/services/orgChart.service'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { AVATAR_URL, APP_NAME } from '@/constants/app.constant'
import { AVATAR_URL } from '@/constants/app.constant'
import Loading from '@/components/shared/Loading'
import { useCurrentMenuIcon } from '@/utils/hooks/useCurrentMenuIcon'
import { Avatar, Button } from '@/components/ui'
@ -655,7 +655,7 @@ function OrgChartTree({
const nodeRefs = useRef<NodeRefMap>({})
const cardRefs = useRef<CardRefMap>({})
const [paths, setPaths] = useState<string[]>([])
const repaintRafRef = useRef<number>()
const repaintRafRef = useRef<number | undefined>(undefined)
const recomputePaths = useCallback(() => {
const container = containerRef.current
@ -874,9 +874,7 @@ const OrgChart = () => {
return (
<>
<Helmet>
<title>{APP_NAME} Organizasyon Şeması</title>
</Helmet>
<PageTitle title="Organizasyon Şeması" />
<div className="flex flex-col h-full">
{/* Toolbar */}

View file

@ -8,7 +8,7 @@ import { useStoreActions, useStoreState } from '@/store'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { FormikHelpers } from 'formik'
import { useCallback, useEffect, useState } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { Link, useParams } from 'react-router-dom'
import FormCustomization from './FormCustomization'
import FormTabColumns from './FormTabColumns'
@ -59,7 +59,6 @@ import FormTabRow from './FormTabRow'
import FormTabGantt from './FormTabGantt'
import FormTabScheduler from './FormTabScheduler'
import FormTabTodo from './FormTabTodo'
import { APP_NAME } from '@/constants/app.constant'
import { FormTabWorkflow } from './FormTabWorkflow'
export interface FormEditProps {
@ -228,11 +227,7 @@ const FormEdit = () => {
return listFormCode && listFormValues && customizations && roleList && userList ? (
<Container>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={`${listFormCode} - ${translate(`::${listFormValues.title}`)}`}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={`${listFormCode} - ${translate(`::${listFormValues.title}`)}`} />
<div className="flex items-center justify-between mb-2 gap-3">
{/* SOL TARAF */}
<h4 className="mb-0">

View file

@ -219,7 +219,6 @@ export const columnEditorTypeListOptions = [
export const listFormTypeOptions = [
{ value: 'Form', label: 'Form' },
{ value: 'List', label: 'List' },
{ value: 'Chart', label: 'Chart' },
]
export const listFormDefaultLayoutOptions = [

View file

@ -5,7 +5,7 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
import { Form, Formik, FormikProps } from 'formik'
import type { KeyboardEvent } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { useLocation, useNavigate } from 'react-router-dom'
import * as Yup from 'yup'
import { FaFolderOpen } from 'react-icons/fa'
@ -14,7 +14,6 @@ import { getPermissions } from '@/services/identity.service'
import { getDataSources } from '@/services/data-source.service'
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
import type { SqlObjectExplorerDto, DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
import { APP_NAME } from '@/constants/app.constant'
import { MenuItem } from '@/proxy/menus/menu'
import WizardStep1, {
MenuTreeNode,
@ -862,11 +861,7 @@ const Wizard = () => {
return (
<Container>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + (isEditMode ? 'App.Listforms.WizardEdit' : 'App.Listforms.Wizard'))}
defaultTitle={APP_NAME}
/>
<PageTitle title={translate('::' + (isEditMode ? 'App.Listforms.WizardEdit' : 'App.Listforms.Wizard'))} />
{isLoadingEditData && (
<p className="text-xs text-gray-400 text-center py-4 animate-pulse">

View file

@ -61,7 +61,7 @@ export function WorkflowCanvas({
activeNodeId?: string
selectedId: string
pendingLink: PendingLink
canvasRef: RefObject<HTMLDivElement>
canvasRef: RefObject<HTMLDivElement | null>
onSelect: (id: string) => void
onOpenDetails: (id: string) => void
onClearSelection: () => void

View file

@ -13,7 +13,7 @@ import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
type WorkflowDesignerProps = {
busy: boolean
canvasRef: RefObject<HTMLDivElement>
canvasRef: RefObject<HTMLDivElement | null>
canvasZoom: number
criteriaForm: any
currentCriteria: WorkflowCriteriaDto[]

View file

@ -51,7 +51,7 @@ import isEmpty from 'lodash/isEmpty'
import { memo, Suspense, useEffect, useMemo, useState } from 'react'
import { NodeApi, NodeRendererProps } from 'react-arborist'
import { Tree } from 'react-arborist/dist/module/components/tree'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import {
FaCheckCircle,
FaUser,
@ -67,7 +67,6 @@ import {
} from 'react-icons/fa'
import { object, string } from 'yup'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { APP_NAME } from '@/constants/app.constant'
const schema = object().shape({
id: string(),
@ -463,11 +462,7 @@ const OrganizationUnits = () => {
return (
<>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::Abp.Identity.OrganizationUnits')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::Abp.Identity.OrganizationUnits')} />
<Loading type="cover" className="h-full" loading={loading}>
<Container className="h-full">
<div className="flex flex-col lg:flex-row gap-4 h-full">

View file

@ -1,10 +1,9 @@
import AdaptableCard from '@/components/shared/AdaptableCard'
import Container from '@/components/shared/Container'
import Tabs from '@/components/ui/Tabs'
import { APP_NAME } from '@/constants/app.constant'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Suspense, lazy, type ReactNode } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { FaCheckCircle, FaCompressAlt, FaUser } from 'react-icons/fa'
import { useLocation, useNavigate } from 'react-router-dom'
@ -56,11 +55,7 @@ const Profile = () => {
return (
<Container>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'Abp.Identity.Profile')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::' + 'Abp.Identity.Profile')} />
<AdaptableCard>
<Tabs value={currentTab} onChange={(val) => onTabChange(val)}>
<TabList>

View file

@ -5,11 +5,10 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
import useThemeClass from '@/utils/hooks/useThemeClass'
import isEmpty from 'lodash/isEmpty'
import { useEffect, useState } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import RolesPermission from './RolesPermission'
import { IdentityRoleDto } from '@/proxy/admin/models'
import { getRoles } from '@/services/identity.service'
import { APP_NAME } from '@/constants/app.constant'
const { Tr, Th, Td, THead, TBody } = Table
@ -37,11 +36,7 @@ const Roles = () => {
return (
<>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::AbpIdentity.Roles')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::AbpIdentity.Roles')} />
<Button
onClick={() => {
toast.push(

View file

@ -7,10 +7,9 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
import useThemeClass from '@/utils/hooks/useThemeClass'
import isEmpty from 'lodash/isEmpty'
import { useEffect, useState } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import TenantsConnectionString from './TenantsConnectionString'
import { getTenants } from '@/services/tenant.service'
import { APP_NAME } from '@/constants/app.constant'
const { Tr, Th, Td, THead, TBody } = Table
@ -43,11 +42,7 @@ const Tenants = () => {
return (
<>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::AbpTenantManagement.Tenants')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::AbpTenantManagement.Tenants')} />
<Loading type="cover" loading={loading}>
<Container>
<AdaptableCard>

View file

@ -36,7 +36,7 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
import dayjs from 'dayjs'
import { Field, FieldArray, FieldProps, Form, Formik, FormikHelpers } from 'formik'
import { useEffect, useRef, useState } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import {
FaBuilding,
FaLockOpen,
@ -83,7 +83,7 @@ import {
} from '@/types/shared'
import { AdaptableCard, ConfirmDialog } from '@/components/shared'
import { AssignedClaimViewModel, UserInfoViewModel } from '@/proxy/admin/models'
import { APP_NAME, AVATAR_URL } from '@/constants/app.constant'
import { AVATAR_URL } from '@/constants/app.constant'
import { useStoreActions, useStoreState } from '@/store'
import { useSetting } from '@/utils/hooks/useSetting'
@ -230,11 +230,7 @@ function UserDetails() {
return userDetails ? (
<>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={userDetails.email}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={userDetails.email} />
<AdaptableCard>
<Tabs defaultValue="user">
<TabList>

View file

@ -6,11 +6,10 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
import useThemeClass from '@/utils/hooks/useThemeClass'
import isEmpty from 'lodash/isEmpty'
import { useEffect, useState } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import UsersPermission from './UsersPermission'
import { IdentityUserDto } from '@/proxy/admin/models'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { APP_NAME } from '@/constants/app.constant'
import ActionLink from '@/components/shared/ActionLink'
const { Tr, Th, Td, THead, TBody } = Table
@ -44,11 +43,7 @@ const Users = () => {
return (
<>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::AbpIdentity.Users')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::AbpIdentity.Users')} />
<Container>
<AdaptableCard>
<Table>

View file

@ -3,7 +3,7 @@ import { FaEye, FaUsers, FaCrown } from 'react-icons/fa'
import { useStoreActions, useStoreState } from '@/store/store'
import { useNavigate } from 'react-router-dom'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Role } from '@/proxy/videoroom/models'
@ -24,11 +24,7 @@ const Dashboard = () => {
return (
<>
<Helmet
titleTemplate="%s | Erp Platform"
title={translate('::' + 'App.Videoroom.Dashboard')}
defaultTitle="Erp Platform"
></Helmet>
<PageTitle appName="Erp Platform" title={translate('::' + 'App.Videoroom.Dashboard')} />
<div className="flex items-center justify-center sm:mt-2 lg:mt-24 dark:bg-gray-900">
<motion.div
initial={{ opacity: 0, y: 20 }}

View file

@ -36,7 +36,7 @@ import {
import { showDbDateAsIs } from '@/utils/dateUtils'
import { useNavigate, useParams } from 'react-router-dom'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { useLocalization } from '@/utils/hooks/useLocalization'
import ChatPanel from '@/views/admin/videoroom/ChatPanel'
import ParticipantsPanel from '@/views/admin/videoroom/ParticipantsPanel'
@ -132,8 +132,8 @@ const RoomDetail = () => {
autoMuteNewParticipants: true,
})
const signalRServiceRef = useRef<SignalRService>()
const webRTCServiceRef = useRef<WebRTCService>()
const signalRServiceRef = useRef<SignalRService | undefined>(undefined)
const webRTCServiceRef = useRef<WebRTCService | undefined>(undefined)
const [teacherDisconnected, setTeacherDisconnected] = useState(false)
const layouts: VideoroomLayoutDto[] = [
@ -849,11 +849,7 @@ const RoomDetail = () => {
return (
<>
<Helmet
titleTemplate="%s | Erp Platform"
title={translate('::' + 'App.Videoroom.RoomDetail')}
defaultTitle="Erp Platform"
></Helmet>
<PageTitle appName="Erp Platform" title={translate('::' + 'App.Videoroom.RoomDetail')} />
{teacherDisconnected && (
<div className="bg-red-600 text-white p-2 text-center">

View file

@ -25,7 +25,7 @@ import {
startVideoroom,
updateVideoroom,
} from '@/services/videoroom.service'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { VideoroomDto } from '@/proxy/videoroom/models'
import classNames from 'classnames'
@ -253,11 +253,7 @@ const RoomList = () => {
return (
<>
<Helmet
titleTemplate="%s | Erp Platform"
title={translate('::' + 'App.Videoroom.List')}
defaultTitle="Erp Platform"
></Helmet>
<PageTitle appName="Erp Platform" title={translate('::' + 'App.Videoroom.List')} />
<Container>
{/* ── Header ─────────────────────────────────────────────── */}

View file

@ -1,12 +1,11 @@
import { FormEvent, useCallback, useEffect, useRef, useState } from 'react'
import { FaComment, FaPaperPlane, FaPlus, FaRobot } from 'react-icons/fa'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { useStoreState } from '@/store'
import { Button, Drawer, Dropdown, ScrollBar, Spinner } from '@/components/ui'
import { Container } from '@/components/shared'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { aiService } from '@/services/ai.service'
import { APP_NAME } from '@/constants/app.constant'
import { AI_ASSISTANT } from '@/constants/permission.constant'
import ConversationList from './components/ConversationList'
import MessageBubble from './components/MessageBubble'
@ -143,11 +142,7 @@ const Assistant = () => {
return (
<Container>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + AI_ASSISTANT)}
defaultTitle={APP_NAME}
/>
<PageTitle title={translate('::' + AI_ASSISTANT)} />
<Drawer
isOpen={isConversationDrawerOpen}
@ -199,23 +194,10 @@ const Assistant = () => {
</Button>
</div>
<ScrollBar
autoHide
hideTracksWhenNotNeeded
autoHideTimeout={700}
autoHideDuration={200}
className="flex-1 min-h-0 min-w-0"
renderView={({ style, ...props }) => (
<div {...props} style={{ ...style, overflowX: 'hidden' }} />
)}
renderThumbVertical={({ style, ...props }) => (
<div
{...props}
className="rounded-full bg-gray-300/80 dark:bg-gray-600/80"
style={{ ...style, width: 6, right: 3 }}
/>
)}
>
{/* Eski renderView/renderThumbVertical prop'ları react-custom-scrollbars-2'ye
aitti; yatay taşma artık utility sınıfıyla, thumb görünümü ise global
--app-scrollbar-* stilleriyle sağlanıyor. */}
<ScrollBar autoHide className="flex-1 min-h-0 min-w-0 overflow-x-hidden">
<div className="p-4 space-y-4">
{messages.length === 0 && (
<div className="text-center text-gray-500 dark:text-gray-400 mt-8">

View file

@ -5,7 +5,6 @@ import Alert from '@/components/ui/Alert'
import Button from '@/components/ui/Button'
import { FormContainer, FormItem } from '@/components/ui/Form'
import Input from '@/components/ui/Input'
import { APP_NAME } from '@/constants/app.constant'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { sendExtendLoginRequest } from '@/services/account.service'
import { store, useStoreActions } from '@/store'
@ -14,7 +13,7 @@ import useTimeOutMessage from '@/utils/hooks/useTimeOutMessage'
import { TurnstileInstance } from '@marsidev/react-turnstile'
import { Field, Form, Formik } from 'formik'
import { useEffect, useRef, useState } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import * as Yup from 'yup'
type ExtendLoginFormSchema = {
@ -76,11 +75,7 @@ const ExtendLogin = () => {
return emailSent ? (
<>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('AbpAccount::' + 'Abp.Account.ExtendLogin')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('AbpAccount::' + 'Abp.Account.ExtendLogin')} />
<div>
<h3 className="mb-1">{translate('::Abp.Account.ExtendLogin.Title')}</h3>
<Alert showIcon className="mt-4 mb-4" type="success">
@ -94,11 +89,7 @@ const ExtendLogin = () => {
</>
) : (
<div>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'Abp.Account.ExtendLogin')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::' + 'Abp.Account.ExtendLogin')} />
<div className="mb-6">
<h3 className="mb-1">{translate('::Abp.Account.ExtendLogin')}</h3>
</div>

View file

@ -5,7 +5,6 @@ import Alert from '@/components/ui/Alert'
import Button from '@/components/ui/Button'
import { FormContainer, FormItem } from '@/components/ui/Form'
import Input from '@/components/ui/Input'
import { APP_NAME } from '@/constants/app.constant'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { sendPasswordResetCode } from '@/services/account.service'
import { store } from '@/store'
@ -16,7 +15,7 @@ import type { AxiosError } from 'axios'
import { Field, Form, Formik } from 'formik'
import { motion } from 'framer-motion'
import { useRef, useState } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import * as Yup from 'yup'
type ForgotPasswordFormSchema = {
@ -63,15 +62,11 @@ const ForgotPassword = () => {
return (
<>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'Abp.Account.ForgotPassword')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::' + 'Abp.Account.ForgotPassword')} />
<motion.div
initial={{ opacity: 0, x: 100 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, origin: 1 }}
transition={{ duration: 0.5 }}
>
<div className="mb-6">
{emailSent ? (

View file

@ -20,8 +20,7 @@ import { motion } from 'framer-motion'
import { useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import * as Yup from 'yup'
import { Helmet } from 'react-helmet'
import { APP_NAME } from '@/constants/app.constant'
import PageTitle from '@/components/shared/PageTitle'
import { useSetting } from '@/utils/hooks/useSetting'
type SignInFormSchema = {
@ -161,15 +160,11 @@ const Login = () => {
return (
<>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('AbpAccount::' + 'Login')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('AbpAccount::' + 'Login')} />
<motion.div
initial={{ opacity: 0, x: 100 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, origin: 1 }}
transition={{ duration: 0.5 }}
>
<div className="mb-8 relative text-center">
<h3 className="mb-1 m-5">{translate('::Abp.Account.WelcomeBack')}</h3>

View file

@ -14,8 +14,7 @@ import { Field, Form, Formik } from 'formik'
import { useRef, useState } from 'react'
import * as Yup from 'yup'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Helmet } from 'react-helmet'
import { APP_NAME } from '@/constants/app.constant'
import PageTitle from '@/components/shared/PageTitle'
type SignUpFormSchema = {
password: string
@ -71,11 +70,7 @@ const Register = () => {
return (
<>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('AbpAccount::' + 'Register')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('AbpAccount::' + 'Register')} />
<div className="mb-4">
<h3 className="mb-1">{translate('::Abp.Account.SignUp')}</h3>

View file

@ -3,7 +3,6 @@ import PasswordInput from '@/components/shared/PasswordInput'
import Alert from '@/components/ui/Alert'
import Button from '@/components/ui/Button'
import { FormContainer, FormItem } from '@/components/ui/Form'
import { APP_NAME } from '@/constants/app.constant'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { resetPassword } from '@/services/account.service'
import { useLocalization } from '@/utils/hooks/useLocalization'
@ -11,7 +10,7 @@ import useTimeOutMessage from '@/utils/hooks/useTimeOutMessage'
import type { AxiosError } from 'axios'
import { Field, Form, Formik } from 'formik'
import { useState } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { useNavigate, useSearchParams } from 'react-router-dom'
import * as Yup from 'yup'
@ -77,11 +76,7 @@ const ResetPassword = () => {
return (
<div>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('AbpAccount::' + 'ResetPassword')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('AbpAccount::' + 'ResetPassword')} />
<div className="mb-6">
{resetComplete ? (
<>

View file

@ -7,8 +7,7 @@ import { ROUTES_ENUM } from '@/routes/route.constant'
import { store, useStoreActions } from '@/store'
import Captcha from '@/components/shared/Captcha'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Helmet } from 'react-helmet'
import { APP_NAME } from '@/constants/app.constant'
import PageTitle from '@/components/shared/PageTitle'
import { TurnstileInstance } from '@marsidev/react-turnstile'
import { useEffect, useRef, useState } from 'react'
import ActionLink from '@/components/shared/ActionLink'
@ -58,11 +57,7 @@ const SendConfirmationCode = () => {
return (
<>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'Abp.Account.SendConfirmationCode')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::' + 'Abp.Account.SendConfirmationCode')} />
{!message && (
<div className="mb-8">

View file

@ -3,9 +3,8 @@ import { Alert, Button } from '@/components/ui'
import { useNavigate, useParams } from 'react-router-dom'
import useAccount from '@/utils/hooks/useAccount'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { APP_NAME } from '@/constants/app.constant'
const VerifyConfirmationCode = () => {
const { userId, token } = useParams()
@ -22,11 +21,7 @@ const VerifyConfirmationCode = () => {
return (
<>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'Abp.Account.VerifyConfirmationCode')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::' + 'Abp.Account.VerifyConfirmationCode')} />
<div>
{message && (

View file

@ -17,8 +17,7 @@ import Widget from '@/components/common/Widget'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Loading } from '../../components/shared'
import { APP_NAME } from '@/constants/app.constant'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { Button } from '@/components/ui'
import Dialog from '@/components/ui/Dialog'
import ComponentEditor from './ComponentEditor'
@ -93,11 +92,7 @@ const ComponentManager: React.FC = () => {
return (
<div className="space-y-4">
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'App.DeveloperKit.Components')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::' + 'App.DeveloperKit.Components')} />
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-2 mb-4">
<Widget

View file

@ -24,8 +24,7 @@ import { developerKitService } from '@/services/developerKit.service'
import type { DataSourceDto } from '@/proxy/data-source'
import type { DatabaseTableDto } from '@/proxy/sql-query-manager/models'
import type { CrudEndpoint } from '@/proxy/developerKit/models'
import { Helmet } from 'react-helmet'
import { APP_NAME } from '@/constants/app.constant'
import PageTitle from '@/components/shared/PageTitle'
import Button from '@/components/ui/Button'
interface TestResult {
@ -317,11 +316,7 @@ const CrudEndpointManager: React.FC = () => {
return (
<div className="flex flex-col h-full gap-4 bg-gray-50 dark:bg-gray-950">
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'App.DeveloperKit.CrudEndpoints')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::' + 'App.DeveloperKit.CrudEndpoints')} />
{/* Stats Row */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mt-2">

View file

@ -20,8 +20,7 @@ import {
postTestCompile,
type TestCompileDto,
} from '@/services/dynamicService.service'
import { Helmet } from 'react-helmet'
import { APP_NAME } from '@/constants/app.constant'
import PageTitle from '@/components/shared/PageTitle'
import { ROUTES_ENUM } from '@/routes/route.constant'
import Button from '@/components/ui/Button'
@ -195,7 +194,7 @@ const DynamicServiceEditor: React.FC = () => {
return (
<div className="space-y-4">
<Helmet titleTemplate={`%s | ${APP_NAME}`} title={pageTitle} defaultTitle={APP_NAME} />
<PageTitle title={pageTitle} />
{/* Header */}
<div className="bg-white dark:bg-gray-900 shadow-lg border-b border-slate-200 dark:border-gray-700 sticky top-0 z-10">

View file

@ -14,8 +14,7 @@ import {
import Widget from '@/components/common/Widget'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { dynamicServiceService, type DynamicServiceDto } from '@/services/dynamicService.service'
import { Helmet } from 'react-helmet'
import { APP_NAME } from '@/constants/app.constant'
import PageTitle from '@/components/shared/PageTitle'
import { ROUTES_ENUM } from '@/routes/route.constant'
import Button from '@/components/ui/Button'
@ -79,11 +78,7 @@ const DynamicServiceManager: React.FC = () => {
return (
<div className="space-y-4">
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'App.DeveloperKit.DynamicServices')}
defaultTitle={APP_NAME}
/>
<PageTitle title={translate('::' + 'App.DeveloperKit.DynamicServices')} />
{/* Stats */}
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-5 gap-4 mt-2 mb-4">

View file

@ -21,9 +21,8 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
import SqlObjectExplorer, { type SqlExplorerSelectedObject } from './SqlObjectExplorer'
import SqlEditor, { SqlEditorRef } from './SqlEditor'
import { Splitter } from '@/components/codeLayout/Splitter'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { useStoreState } from '@/store/store'
import { APP_NAME } from '@/constants/app.constant'
import DbMigrateButton from '@/components/shared/DbMigrateButton'
const SqlResultsGrid = lazy(() => import('./SqlResultsGrid'))
@ -934,11 +933,7 @@ GO`,
className="flex flex-col overflow-x-hidden overflow-y-auto lg:overflow-hidden"
style={{ height: 'calc(100vh - 130px)' }}
>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'App.SqlQueryManager')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::' + 'App.SqlQueryManager')} />
<div className="flex flex-col flex-1 min-h-0 p-1">
{/* Toolbar */}

View file

@ -1,7 +1,7 @@
import Editor from '@monaco-editor/react'
import axios from 'axios'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { Link, useParams } from 'react-router-dom'
import {
FaArrowLeft,
@ -33,7 +33,6 @@ import {
} from 'react-icons/fa'
import { useComponents } from '@/contexts/ComponentContext'
import type { CrudEndpoint, CustomComponent } from '@/proxy/developerKit/models'
import { APP_NAME } from '@/constants/app.constant'
import { ROUTES_ENUM } from '@/routes/route.constant'
import apiService from '@/services/api.service'
import { developerKitService } from '@/services/developerKit.service'
@ -3684,7 +3683,7 @@ const VisualComponentDesigner = () => {
))
const pageHelmet = (
<Helmet defaultTitle={APP_NAME} title={pageTitle} titleTemplate={`%s | ${APP_NAME}`}></Helmet>
<PageTitle title={pageTitle} />
)
if (!component) {

View file

@ -36,7 +36,7 @@ const FormDevExpress = (props: {
listFormCode: string
isSubForm?: boolean
mode: RowMode
refForm: RefObject<FormRef>
refForm: RefObject<FormRef | null>
formData: any
formItems: GroupItem[]
setFormData: Dispatch<any>
@ -46,8 +46,8 @@ const FormDevExpress = (props: {
const formDataRef = useRef(formData)
const formItemsRef = useRef(formItems)
const formInstanceRef = useRef<any>()
const lastContentReadyScriptKeyRef = useRef<string>()
const formInstanceRef = useRef<any | undefined>(undefined)
const lastContentReadyScriptKeyRef = useRef<string | undefined>(undefined)
const didAutoFocusRef = useRef(false)
const [runtimeReadOnlyFields, setRuntimeReadOnlyFields] = useState<Record<string, boolean>>({})
const runtimeReadOnlyFieldsRef = useRef<Record<string, boolean>>({})

View file

@ -18,7 +18,7 @@ export const FormNewContent = (props: FormProps = { isSubForm: false }) => {
const params = useParams()
const listFormCode = props?.listFormCode ?? params?.listFormCode ?? ''
const [searchParams] = useSearchParams()
const initializedListFormCodeRef = useRef<string>()
const initializedListFormCodeRef = useRef<string | undefined>(undefined)
const {
setFormData,

View file

@ -8,14 +8,13 @@ import type { ReactNode, RefObject } from 'react'
import { useState } from 'react'
import { Container, Loading } from '@/components/shared'
import { Badge } from '@/components/ui'
import { APP_NAME } from '@/constants/app.constant'
import type { GridDto } from '@/proxy/form/models'
import { useCurrentMenuIcon } from '@/utils/hooks/useCurrentMenuIcon'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { usePermission } from '@/utils/hooks/usePermission'
import type { FormRef } from 'devextreme-react/form'
import type { GroupItem } from 'devextreme/ui/form'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import FormDevExpress from '../FormDevExpress'
import SubForms from '../SubForms'
import { NotePanel } from '../notes/NotePanel'
@ -30,7 +29,7 @@ type FormPageShellProps = {
gridDto?: GridDto
formData: any
formItems: GroupItem[]
refForm: RefObject<FormRef>
refForm: RefObject<FormRef | null>
setFormData: (data: any) => void
/** Başlığın sağındaki aksiyon butonları (FormButtons). */
actions?: ReactNode
@ -96,7 +95,7 @@ export const FormPageShell = ({
} transition-all duration-300`}
>
{!isSubForm && (
<Helmet titleTemplate={`%s | ${APP_NAME}`} title={title} defaultTitle={APP_NAME} />
<PageTitle title={title} />
)}
<div

View file

@ -45,7 +45,7 @@ const useGridData = (props: {
const [permissionResults, setPermissionResults] = useState<PermissionResults>()
const refForm = useRef<FormRef>(null)
const previousFormDataRef = useRef<any>()
const previousFormDataRef = useRef<any | undefined>(undefined)
const [searchParams] = useSearchParams()
const location = useLocation()
const navigate = useNavigate()

View file

@ -3,9 +3,8 @@ import { useState, useEffect } from 'react'
import { useForumData } from './useForumData'
import { ForumView } from './forum/ForumView'
import { Container } from '@/components/shared'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { APP_NAME } from '@/constants/app.constant'
import { Button } from '@/components/ui'
export function Forum() {
@ -51,11 +50,7 @@ export function Forum() {
return (
<Container>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'App.Forum')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::' + 'App.Forum')} />
{error && (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">

View file

@ -2,9 +2,8 @@ import { useEffect } from 'react'
import { useForumData } from './useForumData'
import { AdminView } from './admin/AdminView'
import { Container } from '@/components/shared'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { APP_NAME } from '@/constants/app.constant'
import { Button } from '@/components/ui'
export function Management() {
@ -60,11 +59,7 @@ export function Management() {
return (
<Container>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'App.ForumManagement')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::' + 'App.ForumManagement')} />
{error && (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">

View file

@ -2,7 +2,7 @@ import Container from '@/components/shared/Container'
import { Loading } from '@/components/shared'
import { ImportDashboard } from '@/components/importManager/ImportDashboard'
import { Dialog, Notification, toast } from '@/components/ui'
import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant'
import { DX_CLASSNAMES } from '@/constants/app.constant'
import {
ColumnFormatDto,
DbTypeEnum,
@ -36,7 +36,7 @@ import CustomStore from 'devextreme/data/custom_store'
import type { DataType } from 'devextreme/common'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { createRoot } from 'react-dom/client'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { NotePanel } from '../form/notes/NotePanel'
import SubForms from '../form/SubForms'
import GridFilterDialogs from './GridFilterDialogs'
@ -359,10 +359,10 @@ const CardView = (props: CardViewProps) => {
)
const localizationTexts = useStoreState((state) => state.abpConfig.texts)
const useMobileEditPopup = shouldUseMobileEditPopup()
const cardViewRef = useRef<CardViewRef>()
const cardViewRef = useRef<CardViewRef | undefined>(undefined)
const searchParamsRef = useRef(searchParams)
const defaultSearchFilterRef = useRef(searchParams?.get('filter') ?? null)
const dataSourceSearchParamsRef = useRef<URLSearchParams>()
const dataSourceSearchParamsRef = useRef<URLSearchParams | undefined>(undefined)
const extraFilterMountedRef = useRef(false)
const [gridDtoState, setGridDtoState] = useState<{
listFormCode: string
@ -376,7 +376,7 @@ const CardView = (props: CardViewProps) => {
const [isPopupFullScreen, setIsPopupFullScreen] = useState(false)
const [formData, setFormData] = useState<Record<string, any>>()
const widgetGroupRef = useRef<HTMLDivElement>(null)
const formDataRef = useRef<Record<string, any>>()
const formDataRef = useRef<Record<string, any> | undefined>(undefined)
useEffect(() => {
formDataRef.current = formData
}, [formData])
@ -647,7 +647,7 @@ const CardView = (props: CardViewProps) => {
// Grid/Tree/Scheduler ile aynı editorScript altyapısı; CardView'ın New ve Edit
// popup formlarında da script'ler çalışsın diye bağlanır.
const editingFormDataRef = useRef<Record<string, any>>({})
const editingFormInstanceRef = useRef<any>()
const editingFormInstanceRef = useRef<any | undefined>(undefined)
const editorScriptHandlers = useMemo(
() =>
createEditingFormHandlers({
@ -1109,11 +1109,7 @@ const CardView = (props: CardViewProps) => {
<Container className={DX_CLASSNAMES}>
{!isSubForm && (
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + gridDto?.gridOptions.title)}
defaultTitle={APP_NAME}
/>
<PageTitle title={translate('::' + gridDto?.gridOptions.title)} />
)}
{!gridDto ||
!dataSource ||

View file

@ -1,11 +1,11 @@
import { CommonProps } from '@/proxy/common'
import { Meta } from '@/proxy/routes/routes'
import { Container } from '@/components/shared'
import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant'
import { DX_CLASSNAMES } from '@/constants/app.constant'
import { useLocalization } from '@/utils/hooks/useLocalization'
import DxChart from 'devextreme-react/chart'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { useParams, useSearchParams } from 'react-router-dom'
import { GridDto } from '@/proxy/form/models'
import { usePermission } from '@/utils/hooks/usePermission'
@ -357,11 +357,7 @@ const Chart = (props: ChartProps) => {
return (
<Container className={DX_CLASSNAMES}>
{!isSubForm && gridDto && (
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + gridDto?.gridOptions?.title)}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::' + gridDto?.gridOptions?.title)} />
)}
{_listFormCode && chartOptions && (
<div className="p-1">

View file

@ -1,5 +1,5 @@
import Container from '@/components/shared/Container'
import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant'
import { DX_CLASSNAMES } from '@/constants/app.constant'
import { GridDto, ListFormCustomizationTypeEnum } from '@/proxy/form/models'
import { useLocalization } from '@/utils/hooks/useLocalization'
import Gantt, {
@ -16,7 +16,8 @@ import Gantt, {
Validation,
} from 'devextreme-react/gantt'
import { useCallback, useEffect, useRef, useState } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { useStylesheet } from '@/utils/hooks/useStylesheet'
import { useListFormCustomDataSource } from './useListFormCustomDataSource'
import { layoutTypes } from '../admin/listForm/edit/types'
import WidgetGroup from '@/components/ui/Widget/WidgetGroup'
@ -70,7 +71,10 @@ const GanttView = (props: GanttViewProps) => {
const { translate } = useLocalization()
const isPwaMode = usePWA()
const gridRef = useRef<GanttRef>()
// Eskiden Helmet'in <link> çocuğuydu; koşul aynen korunuyor.
useStylesheet(isSubForm ? null : '/css/gantt/dx-gantt.min.css', 'dx-gantt-css')
const gridRef = useRef<GanttRef | undefined>(undefined)
const widgetGroupRef = useRef<HTMLDivElement>(null)
const { checkPermission } = usePermission()
@ -80,7 +84,7 @@ const GanttView = (props: GanttViewProps) => {
const [scaleType, setScaleType] = useState<GanttScaleType>('weeks')
const [taskListWidth, setTaskListWidth] = useState(500)
const layout = layoutTypes.gantt || 'gantt'
const pendingStateRef = useRef<Record<string, any>>()
const pendingStateRef = useRef<Record<string, any> | undefined>(undefined)
const loadedStateKeyRef = useRef('')
const widgetGroupHeight = useWidgetGroupHeight(widgetGroupRef, gridDto?.widgets)
@ -297,15 +301,7 @@ const GanttView = (props: GanttViewProps) => {
</div>
<Container className={DX_CLASSNAMES}>
{!isSubForm && (
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + gridDto?.gridOptions.title)}
defaultTitle={APP_NAME}
>
<link rel="stylesheet" href="/css/gantt/dx-gantt.min.css" />
</Helmet>
)}
{!isSubForm && <PageTitle title={translate('::' + gridDto?.gridOptions.title)} />}
{!gridDto && (
<div className="p-4">
<Loading loading>Loading gantt configuration...</Loading>

View file

@ -1,7 +1,7 @@
import Container from '@/components/shared/Container'
import '@/utils/registerDevExtremeEditors'
import { Dialog, Notification, toast } from '@/components/ui'
import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant'
import { DX_CLASSNAMES } from '@/constants/app.constant'
import { GridDto, SubFormTabTypeEnum } from '@/proxy/form/models'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Template } from 'devextreme-react/core/template'
@ -32,7 +32,7 @@ import DataGrid, {
import { Item } from 'devextreme-react/toolbar'
import CustomStore from 'devextreme/data/custom_store'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import SubForms from '../form/SubForms'
import { RowMode } from '../form/types'
import { GridColumnData } from './GridColumnData'
@ -112,10 +112,10 @@ const Grid = (props: GridProps) => {
const localizationTexts = useStoreState((state) => state.abpConfig.texts)
const useMobileEditPopup = shouldUseMobileEditPopup()
const gridRef = useRef<DataGridRef>()
const gridRef = useRef<DataGridRef | undefined>(undefined)
const widgetGroupRef = useRef<HTMLDivElement>(null)
const editingFormDataRef = useRef<Record<string, any>>({})
const editingFormInstanceRef = useRef<any>()
const editingFormInstanceRef = useRef<any | undefined>(undefined)
// Edit popup açıkken grid state'inin kaydedilmesini engeller.
const isEditingRef = useRef(false)
@ -566,11 +566,7 @@ const Grid = (props: GridProps) => {
<Container className={DX_CLASSNAMES}>
{!isSubForm && (
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + gridOptions?.title)}
defaultTitle={APP_NAME}
/>
<PageTitle title={translate('::' + gridOptions?.title)} />
)}
{loadingMessage && (

View file

@ -1,5 +1,5 @@
import Container from '@/components/shared/Container'
import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant'
import { DX_CLASSNAMES } from '@/constants/app.constant'
import { GridDto, ListFormCustomizationTypeEnum } from '@/proxy/form/models'
import { postListFormCustomization } from '@/services/list-form-customization.service'
import { useLocalization } from '@/utils/hooks/useLocalization'
@ -23,7 +23,7 @@ import PivotGrid, {
import CustomStore from 'devextreme/data/custom_store'
import type { Field } from 'devextreme/ui/pivot_grid/data_source'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { GridColumnData } from './GridColumnData'
import { pivotFieldConvertDataType, setGridPanelColor } from './Utils'
import { createConditionalCellStyleHandler } from './shared/cellStyles'
@ -62,7 +62,7 @@ const Pivot = (props: PivotProps) => {
const { checkPermission } = usePermission()
const isPwaMode = usePWA()
const gridRef = useRef<PivotGridRef>()
const gridRef = useRef<PivotGridRef | undefined>(undefined)
const chartRef = useRef<ChartRef>(null)
const widgetGroupRef = useRef<HTMLDivElement>(null)
const config = useStoreState((state) => state.abpConfig.config)
@ -360,11 +360,7 @@ const Pivot = (props: PivotProps) => {
<Container className={DX_CLASSNAMES}>
{!isSubForm && (
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + gridDto?.gridOptions.title)}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::' + gridDto?.gridOptions.title)} />
)}
{gridDto && columnData && (
<div className="p-1">

View file

@ -1,6 +1,6 @@
import Container from '@/components/shared/Container'
import '@/utils/registerDevExtremeEditors'
import { APP_NAME, AVATAR_URL, DX_CLASSNAMES } from '@/constants/app.constant'
import { AVATAR_URL, DX_CLASSNAMES } from '@/constants/app.constant'
import {
ColumnFormatDto,
DbTypeEnum,
@ -17,7 +17,7 @@ import Scheduler, {
View,
} from 'devextreme-react/scheduler'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { getNextSequenceValue } from '@/services/form.service'
import { useListFormCustomDataSource } from './useListFormCustomDataSource'
import { autoNumber, getEditingFormGroups } from './Utils'
@ -66,7 +66,7 @@ const SchedulerView = (props: SchedulerViewProps) => {
const { translate } = useLocalization()
const isPwaMode = usePWA()
const useMobileEditPopup = shouldUseMobileEditPopup()
const schedulerRef = useRef<SchedulerRef>()
const schedulerRef = useRef<SchedulerRef | undefined>(undefined)
const widgetGroupRef = useRef<HTMLDivElement>(null)
const { checkPermission } = usePermission()
@ -120,7 +120,7 @@ const SchedulerView = (props: SchedulerViewProps) => {
// Appointment formundaki editorScript'leri Grid/Tree ile aynı altyapıdan çalıştırıyoruz.
const appointmentFormDataRef = useRef<Record<string, any>>({})
const appointmentFormInstanceRef = useRef<any>()
const appointmentFormInstanceRef = useRef<any | undefined>(undefined)
const editorScriptHandlers = useMemo(
() =>
createEditingFormHandlers({
@ -783,11 +783,7 @@ const SchedulerView = (props: SchedulerViewProps) => {
<Container className={DX_CLASSNAMES}>
{!isSubForm && (
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + gridDto?.gridOptions.title)}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::' + gridDto?.gridOptions.title)} />
)}
{!gridDto && (
<div className="p-4">

View file

@ -192,7 +192,7 @@ const TodoBoardContent = ({
?.fieldName ?? 'UserName'
const stateStoring = gridDto.gridOptions.stateStoringDto
const currentUserName = useStoreState((state) => state.auth.user.userName)
const gridRef = useRef<DataGridRef<any, any>>()
const gridRef = useRef<DataGridRef<any, any> | undefined>(undefined)
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef })
const { getBandedColumns, loadLookupDisplayValues } = useListFormColumns({
gridDto,
@ -208,7 +208,7 @@ const TodoBoardContent = ({
gridDto?: GridDto
listFormCode: string
columns: GridColumnData[]
}>()
} | undefined>(undefined)
if (
!listFormColumnsCacheRef.current ||
listFormColumnsCacheRef.current.gridDto !== gridDto ||

View file

@ -1,7 +1,7 @@
import Container from '@/components/shared/Container'
import '@/utils/registerDevExtremeEditors'
import { Dialog, Notification, toast } from '@/components/ui'
import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant'
import { DX_CLASSNAMES } from '@/constants/app.constant'
import { GridDto, SubFormTabTypeEnum } from '@/proxy/form/models'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Template } from 'devextreme-react/core/template'
@ -27,7 +27,7 @@ import TreeListDx, {
import { Item } from 'devextreme-react/toolbar'
import CustomStore from 'devextreme/data/custom_store'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { RowMode } from '../form/types'
import { GridColumnData } from './GridColumnData'
import GridFilterDialogs from './GridFilterDialogs'
@ -95,10 +95,10 @@ const Tree = (props: TreeProps) => {
const localizationTexts = useStoreState((state) => state.abpConfig.texts)
const useMobileEditPopup = shouldUseMobileEditPopup()
const gridRef = useRef<TreeListRef>()
const gridRef = useRef<TreeListRef | undefined>(undefined)
const widgetGroupRef = useRef<HTMLDivElement>(null)
const editingFormDataRef = useRef<Record<string, any>>({})
const editingFormInstanceRef = useRef<any>()
const editingFormInstanceRef = useRef<any | undefined>(undefined)
// Edit popup açıkken grid state'inin kaydedilmesini engeller.
const isEditingRef = useRef(false)
@ -532,11 +532,7 @@ const Tree = (props: TreeProps) => {
<Container className={DX_CLASSNAMES}>
{!isSubForm && (
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + gridOptions?.title)}
defaultTitle={APP_NAME}
/>
<PageTitle title={translate('::' + gridOptions?.title)} />
)}
{gridDto && gridOptions && columnData && (

View file

@ -49,6 +49,53 @@ const exportCsv = async (component: any, fileName: string) => {
saveAs(new Blob([buffer], { type: 'text/csv' }), `${fileName}_export.csv`)
}
/**
* jsPDF'in gömülü standart fontları (Helvetica vb.) WinAnsi/Latin-1 kodlamalı;
* `ü ö ç` içerirler ama `ı ş ğ İ` içermezler bunlar Latin-5'e özgüdür. Font
* kaydedilmezse o karakterler PDF'te `1 _ 0 boşluk` olarak çıkar. Bu yüzden
* Türkçeyi tam kapsayan Roboto gömülüyor (zaten `public/css/fonts` altında,
* DevExtreme temaları için duruyor).
*
* Normal ve bold ayrı ayrı kaydediliyor: PDF exporter başlık satırında bold'a
* geçiyor, o stil kayıtlı olmazsa jsPDF varsayılan fonta düşer ve başlıklar
* yeniden bozulur.
*/
const PDF_FONT_FAMILY = 'Roboto'
const PDF_FONT_FILES = [
{ url: 'css/fonts/Roboto-400.ttf', vfsName: 'Roboto-400.ttf', style: 'normal' },
{ url: 'css/fonts/Roboto-700.ttf', vfsName: 'Roboto-700.ttf', style: 'bold' },
]
const toBase64 = (buffer: ArrayBuffer) => {
const bytes = new Uint8Array(buffer)
// String.fromCharCode'a tek seferde ~170 KB geçmek call stack'i taşırıyor.
const CHUNK = 0x8000
let binary = ''
for (let i = 0; i < bytes.length; i += CHUNK) {
binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK))
}
return btoa(binary)
}
/** Font verisi bir kez indirilip sonraki export'lar için saklanır. */
let pdfFontsPromise: Promise<{ vfsName: string; style: string; data: string }[]> | undefined
const loadPdfFonts = () => {
pdfFontsPromise ??= Promise.all(
PDF_FONT_FILES.map(async ({ url, vfsName, style }) => {
const response = await fetch(`${import.meta.env.BASE_URL}${url}`)
if (!response.ok) throw new Error(`PDF fontu yüklenemedi: ${url} (${response.status})`)
return { vfsName, style, data: toBase64(await response.arrayBuffer()) }
}),
).catch((error) => {
// Tekrar denenebilsin diye başarısız sonucu cache'te bırakma.
pdfFontsPromise = undefined
throw error
})
return pdfFontsPromise
}
const exportPdf = async (component: any, fileName: string) => {
const [jspdfModule, { exportDataGrid }] = await Promise.all([
import('jspdf'),
@ -59,6 +106,18 @@ const exportPdf = async (component: any, fileName: string) => {
const JsPDFCtor = (jspdfModule as any).default ?? (jspdfModule as any).jsPDF
const doc = new JsPDFCtor({})
try {
for (const font of await loadPdfFonts()) {
doc.addFileToVFS(font.vfsName, font.data)
doc.addFont(font.vfsName, PDF_FONT_FAMILY, font.style)
}
doc.setFont(PDF_FONT_FAMILY, 'normal')
} catch (error) {
// Font indirilemezse export'u tamamen düşürmektense standart fontla devam et;
// yalnızca Türkçe karakterler bozuk çıkar.
console.warn('PDF fontu gömülemedi, standart fontla devam ediliyor:', error)
}
await exportDataGrid({ jsPDFDocument: doc, component, indent: 5 })
doc.save(`${fileName}_export.pdf`)
}

View file

@ -53,7 +53,7 @@ export const useListFormGridDto = (listFormCode: string, externalGridDto?: GridD
* `calc(100vh - …)` hesabında kullanılır.
*/
export const useWidgetGroupHeight = (
widgetGroupRef: RefObject<HTMLElement>,
widgetGroupRef: RefObject<HTMLElement | null>,
widgets: unknown,
) => {
const [widgetGroupHeight, setWidgetGroupHeight] = useState(0)

View file

@ -41,9 +41,9 @@ export const useListFormStateStoring = ({
[filterPrefix, normalizedStorageKey],
)
const saveTimerRef = useRef<ReturnType<typeof setTimeout>>()
const pendingStateRef = useRef<any>()
const pendingSaveRef = useRef<PendingSave>()
const saveTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
const pendingStateRef = useRef<any | undefined>(undefined)
const pendingSaveRef = useRef<PendingSave | undefined>(undefined)
useEffect(() => {
return () => {

View file

@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import {
FaBars,
FaChevronDown,
@ -15,7 +15,6 @@ import { SortableMenuTree } from './SortableMenuTree'
import { collectBranchIds, countMenuItems, filterMenuTree } from './menuTree.utils'
import { Container } from '@/components/shared'
import { Button, Input, Notification, toast } from '@/components/ui'
import { APP_NAME } from '@/constants/app.constant'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { useMenuData } from '@/utils/hooks/useMenuData'
@ -115,11 +114,7 @@ export const MenuManager = () => {
return (
<Container>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::App.Menus.Manager')}
defaultTitle={APP_NAME}
/>
<PageTitle title={translate('::App.Menus.Manager')} />
<div className="bg-white dark:bg-gray-800 rounded px-2 lg:px-3 py-3">
<div className="flex items-center justify-between mb-2 flex-wrap gap-3">

View file

@ -1,10 +1,9 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import type { FC } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { AboutDto } from '@/proxy/about/models'
import { getAbout, saveAboutPage } from '@/services/about'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { APP_NAME } from '@/constants/app.constant'
import { PUBLIC_PAGE_DESIGN } from '@/constants/permission.constant'
import {
DesignerLayer,
@ -559,11 +558,7 @@ const About: FC = () => {
return (
<>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::App.About')}
defaultTitle={APP_NAME}
/>
<PageTitle title={translate('::App.About')} />
<div className={designerPageClass(isDesignMode, isPanelVisible)}>
{/* Hero */}

View file

@ -1,12 +1,11 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { Link } from 'react-router-dom'
import { FaCalendarAlt, FaUser, FaTag, FaSearch } from 'react-icons/fa'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { showDbDateAsIs } from '@/utils/dateUtils'
import { BlogCategory, BlogPost } from '@/proxy/blog/blog'
import { blogService } from '@/services/blog.service'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { APP_NAME } from '@/constants/app.constant'
import { Button } from '@/components/ui'
import { translateLabel } from './designer'
import { PublicPageLoader } from './shared'
@ -105,11 +104,7 @@ const Blog: React.FC = () => {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-950">
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::App.BlogManagement')}
defaultTitle={APP_NAME}
/>
<PageTitle title={translate('::App.BlogManagement')} />
{/* Hero */}
<div className="relative bg-blue-900 py-12 text-white dark:bg-gray-950">

View file

@ -1,13 +1,12 @@
import React, { useEffect, useMemo, useState } from 'react'
import { Link, useParams } from 'react-router-dom'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { showDbDateAsIs } from '@/utils/dateUtils'
import { BlogPost } from '@/proxy/blog/blog'
import { blogService } from '@/services/blog.service'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { useStoreState } from '@/store/store'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { APP_NAME } from '@/constants/app.constant'
import { translateLabel } from './designer'
import { PublicPageLoader } from './shared'
@ -79,11 +78,7 @@ const BlogDetail: React.FC = () => {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-950">
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={title || translate('::App.BlogManagement')}
defaultTitle={APP_NAME}
/>
<PageTitle title={title || translate('::App.BlogManagement')} />
<div className="relative bg-blue-900 py-12 text-white dark:bg-gray-950" />
<div className="container mx-auto px-4">

View file

@ -1,6 +1,5 @@
import { Basket } from '@/components/orders/Basket'
import { TenantForm } from '@/components/orders/TenantForm'
import { APP_NAME } from '@/constants/app.constant'
import { CustomTenantDto } from '@/proxy/config/models'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { useStoreActions, useStoreState } from '@/store/store'
@ -12,7 +11,7 @@ import {
} from '@/utils/basketUtils'
import { useLocalization } from '@/utils/hooks/useLocalization'
import React, { useState, useEffect } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { useNavigate } from 'react-router-dom'
const Checkout: React.FC = () => {
@ -57,11 +56,7 @@ const Checkout: React.FC = () => {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-950">
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'Public.nav.checkout')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::' + 'Public.nav.checkout')} />
{/* Hero Section */}
<div className="relative bg-blue-900 py-12 text-white dark:bg-gray-950" />

View file

@ -9,11 +9,10 @@ import {
FaEnvelope,
FaIdCard,
} from 'react-icons/fa'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { ContactDto } from '@/proxy/contact/models'
import { getContact, saveContactPage } from '@/services/contact.service'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { APP_NAME } from '@/constants/app.constant'
import { PUBLIC_PAGE_DESIGN } from '@/constants/permission.constant'
import {
DesignerLayer,
@ -429,11 +428,7 @@ const Contact: React.FC = () => {
return (
<div className={designerPageClass(isDesignMode, isPanelVisible)}>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::App.Contact')}
defaultTitle={APP_NAME}
/>
<PageTitle title={translate('::App.Contact')} />
<SelectableBlock
id="hero"

View file

@ -1,10 +1,9 @@
import React, { useEffect, useMemo, useState } from 'react'
import { Link } from 'react-router-dom'
import { FaArrowRight, FaChevronLeft, FaChevronRight } from 'react-icons/fa'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { APP_NAME } from '@/constants/app.constant'
import { PUBLIC_PAGE_DESIGN } from '@/constants/permission.constant'
import { Button } from '@/components/ui'
import { useOptionalDemo } from '@/contexts/DemoContext'
@ -570,11 +569,7 @@ const Home: React.FC = () => {
return (
<div className={designerPageClass(isDesignMode, isPanelVisible)}>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::App.Home')}
defaultTitle={APP_NAME}
/>
<PageTitle title={translate('::App.Home')} />
{/* Hero carousel */}
<div className="relative">

View file

@ -1,6 +1,5 @@
import { Basket } from '@/components/orders/Basket'
import { PaymentForm } from '@/components/orders/PaymentForm'
import { APP_NAME } from '@/constants/app.constant'
import { CustomTenantDto } from '@/proxy/config/models'
import { OrderDto } from '@/proxy/order/models'
import { ROUTES_ENUM } from '@/routes/route.constant'
@ -14,7 +13,7 @@ import {
} from '@/utils/basketUtils'
import { useLocalization } from '@/utils/hooks/useLocalization'
import React, { useState, useEffect } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { useNavigate } from 'react-router-dom'
import { PublicPageLoader } from './shared'
@ -120,11 +119,7 @@ const Payment: React.FC = () => {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-950">
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'Public.nav.payment')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::' + 'Public.nav.payment')} />
{/* Hero Section */}
<div className="relative bg-blue-900 py-12 text-white dark:bg-gray-950" />

View file

@ -1,7 +1,6 @@
import { BillingControls } from '@/components/orders/BillingControls'
import { Basket } from '@/components/orders/Basket'
import { ProductCatalog } from '@/components/orders/ProductCatalog'
import { APP_NAME } from '@/constants/app.constant'
import { BillingCycle } from '@/proxy/order/models'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { useStoreActions, useStoreState } from '@/store/store'
@ -15,7 +14,7 @@ import {
} from '@/utils/basketUtils'
import { useLocalization } from '@/utils/hooks/useLocalization'
import React, { useState, useEffect } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { useNavigate } from 'react-router-dom'
const Products: React.FC = () => {
@ -73,11 +72,7 @@ const Products: React.FC = () => {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-950">
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::App.Orders')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::App.Orders')} />
{/* Hero Section */}
<div className="relative bg-blue-900 py-14 text-white dark:bg-gray-950"></div>

View file

@ -1,12 +1,11 @@
import React, { useEffect, useMemo, useState } from 'react'
import { FaCheckCircle } from 'react-icons/fa'
import { Link } from 'react-router-dom'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { ServiceDto } from '@/proxy/services/models'
import { getServices, saveServicesPage } from '@/services/service.service'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { APP_NAME } from '@/constants/app.constant'
import { PUBLIC_PAGE_DESIGN } from '@/constants/permission.constant'
import {
DesignerLayer,
@ -567,11 +566,7 @@ const Services: React.FC = () => {
return (
<div className={designerPageClass(isDesignMode, isPanelVisible)}>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::App.Services')}
defaultTitle={APP_NAME}
/>
<PageTitle title={translate('::App.Services')} />
{/* Hero */}
<SelectableBlock

View file

@ -1,10 +1,9 @@
import { OrderSuccess } from '@/components/orders/OrderSuccess'
import { APP_NAME } from '@/constants/app.constant'
import { PublicPageLoader } from './shared'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { useLocalization } from '@/utils/hooks/useLocalization'
import React, { useState, useEffect } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { useNavigate } from 'react-router-dom'
import { useStoreActions, useStoreState } from '@/store/store'
@ -35,11 +34,7 @@ const Success: React.FC = () => {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-950">
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'App.Platform.Success')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::' + 'App.Platform.Success')} />
<div className="relative bg-blue-900 py-12 text-white dark:bg-gray-950" />
<div className="container mx-auto px-4 pt-12">

View file

@ -1,11 +1,10 @@
import { lazy, Suspense } from 'react'
import type { FC } from 'react'
import { Container } from '@/components/shared'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { useLocation, useParams } from 'react-router-dom'
import { RequestOptions } from 'devexpress-reporting-react/dx-report-designer'
import { APP_NAME } from '@/constants/app.constant'
import { useReportRequestSetup, useReportShellTheme, useReportUrl } from './shared/reportShell'
const ReportDesigner = lazy(() => import('devexpress-reporting-react/dx-report-designer'))
@ -31,11 +30,7 @@ const DevexpressReportDesigner: FC = () => {
return (
<Container className={shellClassName}>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::App.Reports')}
defaultTitle={APP_NAME}
/>
<PageTitle title={translate('::App.Reports')} />
{isRequestConfigured && (
<Suspense fallback={<div>{translate('::App.Loading')}</div>}>

View file

@ -1,7 +1,7 @@
import { useCallback, useRef } from 'react'
import type { FC } from 'react'
import { Container } from '@/components/shared'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { useParams, useLocation } from 'react-router-dom'
import ReportViewer, {
@ -9,7 +9,6 @@ import ReportViewer, {
RequestOptions,
} from 'devexpress-reporting-react/dx-report-viewer'
import { ZoomAutoBy } from 'devexpress-reporting/viewer/constants'
import { APP_NAME } from '@/constants/app.constant'
import { useReportRequestSetup, useReportShellTheme, useReportUrl } from './shared/reportShell'
const DEFAULT_REPORT_ZOOM = 1
@ -38,7 +37,7 @@ const DevexpressReportViewer: FC = () => {
id: string
}>()
const location = useLocation()
const previewModelRef = useRef<ReportPreviewModel>()
const previewModelRef = useRef<ReportPreviewModel | undefined>(undefined)
const { shellClassName } = useReportShellTheme()
const { cultureName, isRequestConfigured } = useReportRequestSetup()
const reportUrlWithParams = useReportUrl(
@ -92,11 +91,7 @@ const DevexpressReportViewer: FC = () => {
return (
<Container className={shellClassName}>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::App.Reports')}
defaultTitle={APP_NAME}
/>
<PageTitle title={translate('::App.Reports')} />
{isRequestConfigured && (
<ReportViewer key={reportUrlWithParams} reportUrl={reportUrlWithParams}>

View file

@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from 'react'
import { ajaxSetup } from '@devexpress/analytics-core/analytics-utils-native'
import { fetchSetup } from '@devexpress/analytics-core/analytics-utils-native'
import { MODE_DARK } from '@/constants/theme.constant'
import { useStoreState } from '@/store'
import {
@ -95,9 +95,18 @@ export const useReportCultureName = () => {
}
/**
* DevExpress istekleri global ajax ayarları üzerinden gittiği için oturum ve dil
* DevExpress istekleri global istek yöneticisi üzerinden gittiği için oturum ve dil
* başlıkları bileşen render edilmeden önce yazılır. `isRequestConfigured` false iken
* rapor bileşeni render edilmemelidir, aksi hâlde ilk istek yetkisiz gider.
*
* Neden `ajaxSetup` değil de `fetchSetup`:
* Export'un son adımı (`getExportResult`) `_doExportSync` içinde ikiye ayrılıyor
* `_isFetchConfigured()` true ise fetch ile gidiyor ve buradaki başlıkları taşıyor,
* false ise gizli bir `<form>` POST'u ile gidiyor. Form POST'u `Authorization`
* başlığı taşıyamaz, `[Authorize]` olan DXXRDV controller'ı 401 döner ve istek bir
* iframe içinde olduğu için konsola hiçbir hata düşmez: export sessizce ölür.
* `fetchSetup` kullanmak `useFetch`'i açar ve indirmeyi de yetkili yola sokar.
* İkisi aynı anda yapılandırılamaz; DevExpress bu durumda hata fırlatır.
*/
export const useReportRequestSetup = () => {
const token = useStoreState((state) => state.auth.session.token)
@ -106,11 +115,11 @@ export const useReportRequestSetup = () => {
const [configuredRequestKey, setConfiguredRequestKey] = useState<string>()
useEffect(() => {
const nextHeaders = { ...(ajaxSetup.ajaxSettings.headers ?? {}) }
const nextHeaders = { ...(fetchSetup.fetchSettings.headers ?? {}) }
delete nextHeaders.Authorization
delete nextHeaders['Accept-Language']
ajaxSetup.ajaxSettings.headers = {
fetchSetup.fetchSettings.headers = {
...nextHeaders,
...(token ? { Authorization: `Bearer ${token}` } : {}),
...(cultureName ? { 'Accept-Language': cultureName } : {}),

View file

@ -13,7 +13,7 @@ import {
toast,
} from '@/components/ui'
import Notification from '@/components/ui/Notification'
import { APP_NAME, MULTIVALUE_DELIMITER } from '@/constants/app.constant'
import { MULTIVALUE_DELIMITER } from '@/constants/app.constant'
import { MainGroupedSettingDto } from '@/proxy/settings/models'
import { getList, updateSettingValues } from '@/services/setting-ui.service'
import { useStoreActions, useStoreState } from '@/store'
@ -22,7 +22,7 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
import { Field, FieldProps, Form, Formik } from 'formik'
import isEmpty from 'lodash/isEmpty'
import { useEffect, useMemo, useState } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { FaQuestionCircle } from 'react-icons/fa'
function getOptions(selectOptions?: Record<string, string>) {
@ -116,11 +116,7 @@ const Settings = () => {
return (
<Container className="h-full">
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::' + 'App.Settings')}
defaultTitle={APP_NAME}
></Helmet>
<PageTitle title={translate('::' + 'App.Settings')} />
<div className="flex flex-col md:flex-row gap-4">
<div className="md:w-2/12 min-w-fit">

View file

@ -1,10 +1,9 @@
import AdaptableCard from '@/components/shared/AdaptableCard'
import Container from '@/components/shared/Container'
import { Button, Notification, toast } from '@/components/ui'
import { APP_NAME } from '@/constants/app.constant'
import { useLocalization } from '@/utils/hooks/useLocalization'
import React, { useEffect, useState } from 'react'
import { Helmet } from 'react-helmet'
import PageTitle from '@/components/shared/PageTitle'
import { FaRegCheckCircle, FaSyncAlt, FaTag } from 'react-icons/fa'
import { releaseNoteService, type ReleaseNoteDto } from '@/services/releaseNote.service'
import { checkForAppUpdate, clearPendingChangelog } from './swRegistration'
@ -125,11 +124,7 @@ const Changelog = () => {
return (
<Container>
<Helmet
titleTemplate={`%s | ${APP_NAME}`}
title={translate('::App.ChangeLog')}
defaultTitle={APP_NAME}
/>
<PageTitle title={translate('::App.ChangeLog')} />
<AdaptableCard>
<div className="p-2">
<div className="mb-6 flex flex-col gap-3 border-b border-gray-200 pb-5 dark:border-gray-700 sm:flex-row sm:items-center sm:justify-between">