Ölü kodlar kaldırıldı. Optimizasyon

This commit is contained in:
Sedat ÖZTÜRK 2026-08-21 16:35:57 +03:00
parent b13f0b6a57
commit f744d18f83
42 changed files with 100 additions and 2182 deletions

File diff suppressed because one or more lines are too long

View file

@ -1,62 +0,0 @@
import React from "react";
import classNames from "classnames";
interface InfoSectionProps {
title: string;
children: React.ReactNode;
}
export const InfoSection: React.FC<InfoSectionProps> = ({
title,
children,
}) => (
<div className="space-y-2">
<h3 className="text-sm font-medium text-gray-900 border-b pb-1 mb-2">
{title}
</h3>
<div className="space-y-2.5 text-sm">{children}</div>
</div>
);
interface InfoItemProps {
label: string;
value: string | number;
isMono?: boolean;
isBold?: boolean;
isBadge?: boolean;
badgeColor?: string;
}
export const InfoItem: React.FC<InfoItemProps> = ({
label,
value,
isMono,
isBold,
isBadge,
badgeColor,
}) => (
<div>
<label className="text-xs font-medium text-gray-500">{label}</label>
{isBadge ? (
<p>
<span
className={classNames(
"px-2 py-0.5 rounded-full text-xs font-medium",
badgeColor || "bg-gray-100 text-gray-800"
)}
>
{value}
</span>
</p>
) : (
<p
className={classNames("text-gray-900", {
"font-mono": isMono,
"font-semibold": isBold,
})}
>
{value}
</p>
)}
</div>
);

View file

@ -1,41 +0,0 @@
import React from 'react';
import classNames from 'classnames';
interface LoadingSpinnerProps {
size?: 'sm' | 'md' | 'lg';
color?: 'blue' | 'white' | 'gray';
text?: string;
}
const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({
size = 'md',
color = 'blue',
text
}) => {
const sizeClasses = {
sm: 'h-4 w-4',
md: 'h-6 w-6',
lg: 'h-8 w-8'
};
const colorClasses = {
blue: 'border-blue-600',
white: 'border-white',
gray: 'border-gray-600'
};
return (
<div className="flex items-center justify-center">
<div
className={classNames(
'animate-spin rounded-full border-b-2',
sizeClasses[size],
colorClasses[color]
)}
/>
{text && <span className="ml-3 text-gray-600">{text}</span>}
</div>
);
};
export default LoadingSpinner;

View file

@ -1,64 +0,0 @@
import React from "react";
interface ModuleHeaderProps {
title: string;
breadcrumbs?: Array<{
name: string;
href?: string;
}>;
}
const ModuleHeader: React.FC<ModuleHeaderProps> = ({
title,
breadcrumbs = [],
}) => {
return (
<div className="bg-white border-b border-gray-200 px-4 py-3">
{/* Breadcrumb Navigation */}
<nav className="flex" aria-label="Breadcrumb">
<ol className="flex items-center space-x-1 text-sm">
<li>
<div className="flex items-center">
<svg
className="h-4 w-4 text-gray-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
/>
</svg>
<span className="ml-1 font-medium text-gray-500">Anasayfa</span>
</div>
</li>
{breadcrumbs.map((breadcrumb, index) => (
<li key={index}>
<div className="flex items-center">
<span className="text-gray-400 mx-2">/</span>
<span className="font-medium text-gray-500">
{breadcrumb.name}
</span>
</div>
</li>
))}
<li>
<div className="flex items-center">
<span className="text-gray-400 mx-2">/</span>
<span className="font-medium text-gray-900" aria-current="page">
{title}
</span>
</div>
</li>
</ol>
</nav>
</div>
);
};
export default ModuleHeader;

View file

@ -1,80 +0,0 @@
import React from 'react'
import classNames from 'classnames'
import { FaCheckCircle, FaExclamationTriangle, FaClock, FaTimes } from 'react-icons/fa'
interface StatusBadgeProps {
status: string
size?: 'sm' | 'md'
showIcon?: boolean
}
const StatusBadge: React.FC<StatusBadgeProps> = ({ status, size = 'md', showIcon = true }) => {
const sizeClasses = {
sm: 'px-1.5 py-0.5 text-xs',
md: 'px-2 py-0.5 text-xs',
}
const getStatusConfig = (status: string) => {
switch (status) {
case 'active':
return {
color: 'bg-green-100 text-green-800',
icon: <FaCheckCircle size={14} />,
text: 'Aktif',
}
case 'inactive':
return {
color: 'bg-red-100 text-red-800',
icon: <FaTimes size={14} />,
text: 'Pasif',
}
case 'pending':
return {
color: 'bg-yellow-100 text-yellow-800',
icon: <FaClock size={14} />,
text: 'Beklemede',
}
case 'critical':
return {
color: 'bg-red-100 text-red-800',
icon: <FaExclamationTriangle size={14} />,
text: 'Kritik',
}
case 'low':
return {
color: 'bg-yellow-100 text-yellow-800',
icon: <FaExclamationTriangle size={14} />,
text: 'App.ListFormTodoBoard.PriorityLow',
}
case 'normal':
return {
color: 'bg-green-100 text-green-800',
icon: <FaCheckCircle size={14} />,
text: 'Normal',
}
default:
return {
color: 'bg-gray-100 text-gray-800',
icon: <FaCheckCircle size={14} />,
text: 'Bilinmiyor',
}
}
}
const config = getStatusConfig(status)
return (
<span
className={classNames(
'inline-flex items-center rounded-full font-medium',
config.color,
sizeClasses[size],
)}
>
{showIcon && <span className="mr-1">{config.icon}</span>}
{config.text}
</span>
)
}
export default StatusBadge

View file

@ -1,44 +0,0 @@
import classNames from "classnames";
import Widget, { type colorType } from "./Widget";
import { WidgetEditDto, WidgetGroupDto } from "../../types/common";
export default function WidgetGroup({
widgetGroups,
}: {
widgetGroups: WidgetGroupDto[];
}) {
return (
<div>
{widgetGroups.map((group, gIdx) => (
<div
key={gIdx}
className={classNames(
`grid grid-cols-12 gap-${group.colGap} ${group.className || ""}`
)}
>
{group.items.map((item: WidgetEditDto, order: number) => (
<div
key={`${gIdx}-${order}`}
className={classNames(`col-span-${group.colSpan}`)}
>
<Widget
title={item.title}
value={item.value}
color={item.color as colorType}
icon={item.icon}
subTitle={item.subTitle}
valueClassName={item.valueClassName}
onClick={() => {
if (item.onClick) {
// eslint-disable-next-line no-eval
eval(item.onClick);
}
}}
/>
</div>
))}
</div>
))}
</div>
);
}

View file

@ -1,48 +0,0 @@
import React, { useMemo } from 'react'
import DynamicRenderer from './DynamicRenderer'
import { useComponents } from '@/contexts/ComponentContext'
import { parseComponentDependencies } from '@/contexts/componentRuntime'
import { Loading } from '../shared'
import { useLocalization } from '@/utils/hooks/useLocalization'
export interface ComponentPreviewProps {
componentName?: string
className?: string
}
const ComponentPreview: React.FC<ComponentPreviewProps> = ({ componentName, className = '' }) => {
const { translate } = useLocalization()
const { components, loading } = useComponents()
// Referans olarak sabit tutulmalı: DynamicRenderer bunu effect bağımlılığı olarak kullanıyor.
const dependencies = useMemo(() => {
const component = components?.find((item) => item.name === componentName && item.isActive)
return parseComponentDependencies(component?.dependencies)
}, [components, componentName])
if (!componentName) {
return (
<div className="text-sm text-gray-500 dark:text-gray-400">
{translate('::App.DeveloperKitComponentPreview.NoComponentName')}
</div>
)
}
if (loading || !Array.isArray(components)) {
return (
<div className="flex items-center justify-center min-h-screen bg-gray-50 dark:bg-gray-900">
<div className="text-center">
<Loading loading={true} />
</div>
</div>
)
}
return (
<div className={`bg-white dark:bg-gray-900 ${className}`}>
<DynamicRenderer componentName={componentName} dependencies={dependencies} />
</div>
)
}
export default ComponentPreview

View file

@ -1,211 +0,0 @@
import React, { useCallback, useEffect, useRef, useState } from 'react'
import axios from 'axios'
import DOMPurify from 'dompurify'
import apiService from '@/services/api.service'
import * as UiKit from '@/components/ui'
import {
getComponentRuntimeCode,
parseComponentDependencies,
toErrorMessage,
type BabelLike,
} from '@/contexts/componentRuntime'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { usePermission } from '@/utils/hooks/usePermission'
import { useComponents } from '../../contexts/ComponentContext'
import ErrorBoundary from './ErrorBoundary'
import PlatformIcon from './PlatformIcon'
import PlatformSelectComponent from './selectComponents'
import PlatformViewHost from './PlatformViewHost'
import { toast } from '../ui'
import { Loading } from '../shared'
type RuntimeModule = { exports: { default?: React.ComponentType<any> } }
const compileComponent = (
code: string,
scope: Record<string, any>,
Babel: BabelLike,
): React.ComponentType<any> | undefined => {
const transpiled = Babel.transform(code, {
filename: 'component.tsx',
presets: ['typescript', 'react'],
plugins: ['transform-modules-commonjs'],
}).code
if (!transpiled) throw new Error('Boş derleme çıktısı')
const module: RuntimeModule = { exports: {} }
const require = (moduleName: string) => {
if (moduleName === 'react') return React
if (moduleName === 'axios') return axios
if (moduleName === 'dompurify') return DOMPurify
if (moduleName === '@/services/api.service') return apiService
throw new Error(`Modül bulunamadı: ${moduleName}`)
}
const scopedEval = new Function('module', 'exports', 'require', ...Object.keys(scope), transpiled)
scopedEval(module, module.exports, require, ...Object.values(scope))
return module.exports.default
}
// The UI kit is spread as-is: every exported component is available to runtime
// code without an extra per-component entry here.
const staticComponents: Record<string, any> = {
...UiKit,
// Icon props travel as names, so generated code needs the same name resolver
// the designer canvas draws with.
PlatformIcon,
// Select.componentAs is stored as a name, so generated code needs the same
// resolver the designer canvas renders with.
PlatformSelectComponent,
PlatformViewHost,
toast,
apiService,
DOMPurify,
UiKit,
}
interface DynamicRendererProps {
componentName: string
dependencies?: string[]
}
const DynamicRenderer: React.FC<DynamicRendererProps> = ({
componentName,
dependencies: externalDeps,
}) => {
const [Component, setComponent] = useState<React.ComponentType<any> | null>(null)
const [error, setError] = useState<string | null>(null)
const { getComponentByName, components } = useComponents()
const { checkPermission } = usePermission()
const { translate } = useLocalization()
// Read through refs so the granted policies and the active language can change
// without recompiling the component; generated code calls both on every render.
const checkPermissionRef = useRef(checkPermission)
checkPermissionRef.current = checkPermission
const stableCheckPermission = useCallback(
(permission?: string) => checkPermissionRef.current(permission),
[],
)
const translateRef = useRef(translate)
translateRef.current = translate
const stableTranslate = useCallback(
(...args: Parameters<typeof translate>) => translateRef.current(...args),
[],
)
useEffect(() => {
let cancelled = false
setComponent(null)
setError(null)
const storedComponent = getComponentByName(componentName)
if (!storedComponent) {
setError(`Component ${componentName} not found`)
return
}
const build = async () => {
// Babel is several megabytes; load it only when something must be compiled.
const Babel = (await import('@babel/standalone')) as unknown as BabelLike
if (cancelled) return
const map = new Map(
components.map((component) => [
component.name,
{
code: getComponentRuntimeCode(component),
dependencies: parseComponentDependencies(component.dependencies),
},
]),
)
const compiled: Record<string, any> = {}
const compileWithDependencies = (name: string): any => {
if (compiled[name]) return compiled[name]
const entry = map.get(name)
if (!entry) {
if (staticComponents[name]) {
compiled[name] = staticComponents[name]
return staticComponents[name]
}
throw new Error(`Component ${name} not found`)
}
const depNames = name === componentName && externalDeps ? externalDeps : entry.dependencies
const deps: Record<string, any> = {}
for (const dep of depNames) {
deps[dep] = compileWithDependencies(dep)
}
const component = compileComponent(
entry.code,
{
React,
...staticComponents,
checkPermission: stableCheckPermission,
translate: stableTranslate,
...deps,
},
Babel,
)
if (!component) throw new Error(`Component ${name} bir default export döndürmüyor`)
compiled[name] = component
return component
}
try {
const RootComponent = compileWithDependencies(componentName)
if (!cancelled) setComponent(() => RootComponent)
} catch (err) {
console.error('Compilation error:', err)
if (!cancelled) setError(toErrorMessage(err))
}
}
void build()
return () => {
cancelled = true
}
}, [
componentName,
externalDeps,
components,
getComponentByName,
stableCheckPermission,
stableTranslate,
])
if (error) {
return (
<div className="p-4 m-4 border-2 border-red-300 rounded-lg bg-red-50 text-red-700 dark:bg-red-950 dark:border-red-800 dark:text-red-300">
<div className="font-semibold text-sm">{componentName}</div>
<div className="text-sm whitespace-pre-wrap">{error}</div>
</div>
)
}
if (!Component)
return (
<div className="flex items-center justify-center min-h-screen bg-gray-50 dark:bg-gray-900">
<div className="text-center">
<Loading loading={true} />
</div>
</div>
)
return (
<ErrorBoundary key={componentName}>
<Component />
</ErrorBoundary>
)
}
export default DynamicRenderer

View file

@ -1,60 +0,0 @@
import { cloneElement } from 'react'
import Logo from '@/components/template/Logo'
import { APP_NAME } from '@/constants/app.constant'
import type { CommonProps } from '@/proxy/common'
import type { ReactNode, ReactElement } from 'react'
import { Avatar } from '@/components/ui'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { ROUTES_ENUM } from '@/routes/route.constant'
import useDarkMode from '@/utils/hooks/useDarkmode'
interface CoverProps extends CommonProps {
content?: ReactNode
}
const Cover = ({ children, content, ...rest }: CoverProps) => {
const { translate } = useLocalization()
const [isDarkMode] = useDarkMode()
return (
<div className="grid lg:grid-cols-3 h-full">
<div
className="col-span-2 bg-no-repeat bg-cover py-6 px-16 flex-col justify-between dark:bg-gray-800 hidden lg:flex relative"
style={{
backgroundImage: `url('/img/others/auth-cover-bg.jpg')`,
}}
>
{/* Koyulaştırıcı katman */}
<div className="absolute inset-0 bg-black bg-opacity-50 z-0"></div>
<div className="relative z-10 flex flex-col h-full justify-between">
<Logo mode={isDarkMode ? 'dark' : 'light'} url={ROUTES_ENUM.authenticated.login} />
<div>
<div className="mb-6 flex items-center gap-4">
<Avatar className="border-2 border-white" shape="circle" src="/img/others/cto.png" />
<div className="text-white">
<div className="font-semibold text-base">Sedat ÖZTÜRK</div>
<span className="opacity-80">{translate('::App.Listform.ListformField.Founder')}</span>
</div>
</div>
<p className="text-lg text-white opacity-80">{translate('::App.LoginPanel.Message')}</p>
</div>
<span className="text-white">
Copyright &copy; {`${new Date().getFullYear()}`}{' '}
<span className="font-semibold">{`${APP_NAME}`}</span>{' '}
</span>
</div>
</div>
<div className="flex flex-col justify-center items-center bg-white dark:bg-gray-800">
<div className="w-full xl:max-w-[450px] px-8 max-w-[380px]">
<div className="mb-8">{content}</div>
{children ? cloneElement(children as ReactElement, { ...rest }) : null}
</div>
</div>
</div>
)
}
export default Cover

View file

@ -1,61 +0,0 @@
import React, { cloneElement } from 'react'
import Avatar from '@/components/ui/Avatar'
import Logo from '@/components/template/Logo'
import { APP_NAME } from '@/constants/app.constant'
import type { CommonProps } from '@/proxy/common'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { ROUTES_ENUM } from '@/routes/route.constant'
import useDarkMode from '@/utils/hooks/useDarkmode'
interface SideProps extends CommonProps {
content?: React.ReactNode
}
const Side = ({ children, content, ...rest }: SideProps) => {
const { translate } = useLocalization()
const [isDarkMode] = useDarkMode()
return (
<div className="grid lg:grid-cols-3 h-full">
<div
className="relative bg-no-repeat bg-cover py-6 px-16 flex-col justify-between hidden lg:flex"
style={{
backgroundImage: `url('/img/others/auth-side-bg.jpg')`,
}}
>
<div className="absolute inset-0 bg-black bg-opacity-50 z-0"></div>
<div className="relative z-10 flex flex-col h-full justify-between">
<Logo mode={isDarkMode ? 'dark' : 'light'} className="drop-shadow-md" url={ROUTES_ENUM.authenticated.login} />
<div>
<div className="mb-6 flex items-center gap-4">
<Avatar className="border-2 border-white" shape="circle" src="/img/others/cto.png" />
<div className="text-white">
<div className="font-semibold text-base">Sedat ÖZTÜRK</div>
<span className="opacity-80">{ translate('::App.Listform.ListformField.Founder')}</span>
</div>
</div>
<p className="text-lg text-white opacity-80">
{ translate('::App.LoginPanel.Message')}
</p>
</div>
<span className="text-white">
Copyright &copy; {`${new Date().getFullYear()}`}{' '}
<span className="font-semibold">{`${APP_NAME}`}</span>{' '}
</span>
</div>
</div>
<div className="col-span-2 flex flex-col justify-start sm:justify-center items-center bg-white dark:bg-gray-800">
<div className="w-full xl:max-w-[450px] px-8 max-w-[380px]">
<div className="mb-8">{content}</div>
{children
? cloneElement(children as React.ReactElement, {
...rest,
})
: null}
</div>
</div>
</div>
)
}
export default Side

View file

@ -1,55 +0,0 @@
import { useEffect, useCallback } from 'react'
import { useStoreState, useStoreActions } from '@/store'
import { useLocation } from 'react-router-dom'
import type { LayoutType } from '@/proxy/theme/models'
import type { ComponentType } from 'react'
export type AppRouteProps<T> = {
component: ComponentType<T>
routeKey: string
layout?: LayoutType
}
const AppRoute = <T extends Record<string, unknown>>({
component: Component,
routeKey,
...props
}: AppRouteProps<T>) => {
const location = useLocation()
const { setCurrentRouteKey } = useStoreActions((actions) => actions.base.common)
const { setLayout, setPreviousLayout } = useStoreActions((actions) => actions.theme)
const layoutType = useStoreState((state) => state.theme.layout.type)
const previousLayout = useStoreState((state) => state.theme.layout.previousType)
const handleLayoutChange = useCallback(() => {
setCurrentRouteKey(routeKey)
if (props.layout && props.layout !== layoutType) {
setPreviousLayout(layoutType)
setLayout(props.layout)
}
if (!props.layout && previousLayout && layoutType !== previousLayout) {
setLayout(previousLayout)
setPreviousLayout('')
}
}, [
layoutType,
previousLayout,
props.layout,
routeKey,
setCurrentRouteKey,
setLayout,
setPreviousLayout,
])
useEffect(() => {
handleLayoutChange()
}, [location, handleLayoutChange])
return <Component {...(props as T)} />
}
export default AppRoute

View file

@ -1,25 +0,0 @@
// AuthorityGuard.tsx
import { PropsWithChildren } from 'react'
import { Navigate, useLocation } from 'react-router-dom'
import useAuthority from '@/utils/hooks/useAuthority'
import { getAccessDeniedPath } from '@/utils/routing'
type AuthorityGuardProps = PropsWithChildren<{
userAuthority?: string[]
authority?: string[]
}>
const AuthorityGuard = (props: AuthorityGuardProps) => {
const { userAuthority = [], authority = [], children } = props
const roleMatched = useAuthority(userAuthority, authority)
const location = useLocation()
if (!roleMatched) {
const to = getAccessDeniedPath(location.pathname)
return <Navigate to={to} replace state={{ from: location }} />
}
return <>{children}</>
}
export default AuthorityGuard

View file

@ -1,13 +0,0 @@
import { Navigate, Outlet } from 'react-router-dom'
import appConfig from '@/proxy/configs/app.config'
import useAuth from '@/utils/hooks/useAuth'
const { authenticatedEntryPath } = appConfig
const PublicRoute = () => {
const { authenticated } = useAuth()
return authenticated ? <Navigate to={authenticatedEntryPath} /> : <Outlet />
}
export default PublicRoute

View file

@ -1,73 +0,0 @@
import React, { useState } from 'react'
import Dialog from './Dialog'
const DialogExample: React.FC = () => {
const [isOpen, setIsOpen] = useState(false)
const handleOpen = () => setIsOpen(true)
const handleClose = () => setIsOpen(false)
const handleMaximize = () => {
console.log('Dialog maximized - should cover full screen')
}
const handleRestore = () => {
console.log('Dialog restored - should return to normal size')
}
return (
<div className="p-4">
<button
onClick={handleOpen}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Open Dialog with Window Controls
</button>
<Dialog
isOpen={isOpen}
onRequestClose={handleClose}
onClose={handleClose}
onMaximize={handleMaximize}
onRestore={handleRestore}
width={600}
height={400}
showWindowControls={true}
contentClassName="p-6"
>
<div className="h-full flex flex-col">
<h2 className="text-xl font-bold mb-4">Dialog with Window Controls</h2>
<div className="flex-1 overflow-auto">
<p className="mb-4">
This dialog has maximize and restore buttons in the top-right corner.
</p>
<ul className="list-disc ml-6 space-y-2 mb-6">
<li>Click the maximize button () to make the dialog fullscreen</li>
<li>Click the restore button () to return to normal size</li>
<li>Click the close button (×) to close the dialog</li>
</ul>
<div className="space-y-4">
<p className="text-gray-600">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris.
</p>
<p className="text-gray-600">
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
<p className="text-gray-600">
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium
doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore
veritatis et quasi architecto beatae vitae dicta sunt explicabo.
</p>
</div>
</div>
</div>
</Dialog>
</div>
)
}
export default DialogExample

View file

@ -37,6 +37,7 @@ import {
isFormNode,
normalizeDesignerKeyList,
readFormField,
DESIGNER_DROPDOWN_PLACEHOLDER_KEY,
resolveDesignerDropdownTitle,
resolveDesignerPreviewFilterValue,
resolveDesignerTabValue,
@ -1334,6 +1335,7 @@ const renderElement = (
options,
dropdownProps.activeKey,
dropdownProps.title,
translate(DESIGNER_DROPDOWN_PLACEHOLDER_KEY),
)
return (
<UiKit.Dropdown {...(dropdownProps as React.ComponentProps<typeof UiKit.Dropdown>)}>

View file

@ -30,7 +30,7 @@ import {
DESIGNER_FILTER_DX_OPERATORS,
toDesignerFilterParamName,
normalizeDesignerKeyList,
DESIGNER_DROPDOWN_PLACEHOLDER,
DESIGNER_DROPDOWN_PLACEHOLDER_KEY,
FORM_SLOTS,
SQL_DEFAULT_VALUE_PROP,
type DesignerDataSource,
@ -1656,6 +1656,29 @@ export const generateDesignerCode = (name: string, document: DesignerDocument) =
const hasDataSourceFilters = dataSources.some(
(source) => getDesignerDataSourceFilters(source).length > 0,
)
// Two designer sources can address the very same request — a Form Select and
// the Grid bound to the same list is the usual pair. They are grouped by
// request identity so the page issues one call instead of one per source; the
// duplicates read the canonical state through an alias, which keeps every
// `data_<sourceId>` binding in the document working untouched.
const firstSourceIdByRequest = new Map<string, string>()
const canonicalSourceId = new Map<string, string>()
dataSources.forEach((source) => {
// Filters belong to the identity: the same URL with different filters is a
// different request and must keep a state of its own.
const requestKey = [
source.method,
source.url.trim(),
source.responsePath || '',
filterEntriesExpression(source, sqlRecordByRef),
].join('|')
if (!firstSourceIdByRequest.has(requestKey)) firstSourceIdByRequest.set(requestKey, source.id)
canonicalSourceId.set(source.id, firstSourceIdByRequest.get(requestKey) as string)
})
/** Canonical id of the group a Form loads itself; a mount fetch would duplicate it. */
const formManagedCanonicalIds = new Set(
[...sqlManagedSelectSourceIds].map((id) => canonicalSourceId.get(id) ?? id),
)
const hasFilters = hasDataSourceFilters || platformFilterNodes.length > 0
const selectHelpers = hasSelect
? ` const toSelectOptions = (value, labelPath = "", valuePath = "") => {
@ -1686,7 +1709,7 @@ export const generateDesignerCode = (name: string, document: DesignerDocument) =
const key = activeKey === undefined || activeKey === null ? "" : String(activeKey)
const selected = key ? options.find((option) => String(option?.value ?? "") === key) : undefined
if (selected) return String(selected.label ?? selected.value ?? key)
return String(fallbackTitle ?? "") || ${JSON.stringify(DESIGNER_DROPDOWN_PLACEHOLDER)}
return String(fallbackTitle ?? "") || ${translateExpression(DESIGNER_DROPDOWN_PLACEHOLDER_KEY)}
}`
: ''
// Falls back to the first tab while an endpoint driven tab list is still empty
@ -1715,19 +1738,29 @@ export const generateDesignerCode = (name: string, document: DesignerDocument) =
}`
: ''
// Grid cells print raw endpoint columns, so dates and decimals are localised
// here — the culture is published on `<html lang>` because a runtime compiled
// component has neither hooks nor imports to reach the store.
// here. The culture is injected by the component runtime; `<html lang>` is the
// fallback for an older host, and an empty result hands the browser default to
// Intl rather than pinning the page to a language chosen in this file.
const localeHelpers = hasDataTable
? ` const localeCulture = () => (typeof document !== "undefined" && document.documentElement.lang) || "en"
const localeDateOptions = { year: "numeric", month: "2-digit", day: "2-digit" }
const localeTimeOptions = { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" }
? ` const localeCulture = () => {
const injected = typeof getCulture === "function" ? getCulture() : ""
if (injected) return injected
if (typeof document !== "undefined" && document.documentElement.lang) return document.documentElement.lang
return undefined
}
// Patterns come from the culture itself; a fixed day/month/year order would
// print the same layout in every language.
const localeDateOptions = { dateStyle: "short" }
const localeTimeOptions = { dateStyle: "short", timeStyle: "short" }
const localeIsoPattern = /^(\\d{4})-(\\d{2})-(\\d{2})(?:[T ](\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2})?)?$/
const formatLocaleValue = (value) => {
if (value === null || value === undefined) return ""
const culture = localeCulture()
if (typeof value === "number") {
if (!Number.isFinite(value)) return String(value)
return new Intl.NumberFormat(culture, { useGrouping: false, maximumFractionDigits: 20 }).format(value)
// Grouping and the decimal separator are the culture's decision; only the
// precision guard stays, so a long decimal is not rounded away.
return new Intl.NumberFormat(culture, { maximumFractionDigits: 20 }).format(value)
}
if (value instanceof Date) {
return Number.isNaN(value.getTime()) ? "" : new Intl.DateTimeFormat(culture, localeTimeOptions).format(value)
@ -1992,7 +2025,18 @@ export const generateDesignerCode = (name: string, document: DesignerDocument) =
const identifier = safeIdentifier(source.id)
// POST/PUT/DELETE sources are only ever invoked by a Form command;
// they hold no readable state and must never be written to.
if (source.method !== 'GET' || !source.url.trim().startsWith('/api/')) {
const isReadable = source.method === 'GET' && source.url.trim().startsWith('/api/')
const canonicalId = canonicalSourceId.get(source.id) ?? source.id
// A duplicate declares no state of its own: it aliases the canonical
// source, so both bindings always read the one response that was fetched.
if (canonicalId !== source.id) {
const canonical = safeIdentifier(canonicalId)
return isReadable
? ` const data_${identifier} = data_${canonical}
const setData_${identifier} = setData_${canonical}`
: ` const data_${identifier} = data_${canonical}`
}
if (!isReadable) {
return ` const [data_${identifier}] = React.useState(null)`
}
return ` const [data_${identifier}, setData_${identifier}] = React.useState(null)`
@ -2002,10 +2046,13 @@ export const generateDesignerCode = (name: string, document: DesignerDocument) =
.map((source) => {
const identifier = safeIdentifier(source.id)
if (source.method !== 'GET' || !source.url.trim().startsWith('/api/')) return ''
// A duplicate is only an alias of an earlier source; the canonical one
// already owns the single fetch for this request.
if ((canonicalSourceId.get(source.id) ?? source.id) !== source.id) return ''
// A GetById style URL is not fetched on mount — requesting a literal `{id}`
// is a guaranteed 400 — and neither is a source the owning Form
// loads itself, through the setter declared above.
if (hasFormUrlParams(source.url) || sqlManagedSelectSourceIds.has(source.id)) {
if (hasFormUrlParams(source.url) || formManagedCanonicalIds.has(source.id)) {
return ''
}
const entries = filterEntriesExpression(source, sqlRecordByRef)

View file

@ -504,7 +504,8 @@ export const resolveDesignerTabValue = (
return options.length ? String(options[0]?.value ?? '') : ''
}
export const DESIGNER_DROPDOWN_PLACEHOLDER = 'Seçiniz'
/** Localization key of the toggle label; the text itself never lives in the code. */
export const DESIGNER_DROPDOWN_PLACEHOLDER_KEY = '::App.Platform.Select'
/**
* Dropdown renders its toggle from `title` only; `activeKey` merely highlights an
@ -515,12 +516,13 @@ export const resolveDesignerDropdownTitle = (
options: Array<Record<string, unknown>>,
activeKey: unknown,
fallbackTitle: unknown,
placeholder: string,
) => {
const key = activeKey === undefined || activeKey === null ? '' : String(activeKey)
const selected = key ? options.find((option) => String(option?.value ?? '') === key) : undefined
if (selected) return String(selected.label ?? selected.value ?? key)
const title = fallbackTitle === undefined || fallbackTitle === null ? '' : String(fallbackTitle)
return title || DESIGNER_DROPDOWN_PLACEHOLDER
return title || placeholder
}
export interface DesignerPropertyInfo extends PropertyInfo {

View file

@ -1,38 +0,0 @@
import { TW_COLORS } from '@/utils/tailwind'
const C = TW_COLORS as any
export const COLOR_1 = C.indigo[600]
export const COLOR_2 = C.blue[500]
export const COLOR_3 = C.emerald[500]
export const COLOR_4 = C.amber[500]
export const COLOR_5 = C.red[500]
export const COLOR_6 = C.purple[500]
export const COLOR_7 = C.cyan[500]
export const COLOR_1_LIGHT = C.indigo[100]
export const COLOR_2_LIGHT = C.blue[100]
export const COLOR_3_LIGHT = C.emerald[100]
export const COLOR_4_LIGHT = C.amber[100]
export const COLOR_5_LIGHT = C.red[100]
export const COLOR_6_LIGHT = C.purple[100]
export const COLOR_7_LIGHT = C.cyan[100]
export const COLORS = [
COLOR_1,
COLOR_2,
COLOR_3,
COLOR_4,
COLOR_5,
COLOR_6,
COLOR_7,
]
export const COLORS_LIGHT = [
COLOR_1_LIGHT,
COLOR_2_LIGHT,
COLOR_3_LIGHT,
COLOR_4_LIGHT,
COLOR_5_LIGHT,
COLOR_6_LIGHT,
COLOR_7_LIGHT,
]

View file

@ -99,6 +99,7 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
const { translate } = useLocalization()
const { checkPermission } = usePermission()
const applicationConfig = useStoreState((state) => state.abpConfig?.config)
const currentLang = useStoreState((state) => state.locale.currentLang)
const extraProperties = applicationConfig?.extraProperties
const [components, setComponents] = useState<CustomComponent[]>([])
@ -130,6 +131,16 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
[],
)
// Number and date formatting inside a runtime component follows the active
// culture. It is read through a ref for the same reason as the two above: a
// language change must reach the compiled bundle without recompiling it.
const culture =
useStoreState((state) => state.abpConfig?.config?.localization?.currentCulture?.cultureName) ??
currentLang
const cultureRef = useRef(culture)
cultureRef.current = culture
const stableGetCulture = useCallback(() => cultureRef.current, [])
const refreshComponents = useCallback(async () => {
if (!applicationConfig) return
@ -309,6 +320,7 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
axios,
translate: stableTranslate,
checkPermission: stableCheckPermission,
getCulture: stableGetCulture,
})
const { registry, errors } = compileComponentBundle(
@ -336,7 +348,7 @@ export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ chi
return () => {
cancelled = true
}
}, [compilationSignature, stableCheckPermission, stableTranslate])
}, [compilationSignature, stableCheckPermission, stableTranslate, stableGetCulture])
const renderComponent = useCallback(
(name: string, props: ComponentProps = {}) => {

View file

@ -1,21 +0,0 @@
export interface DashboardWidgetsRequestDto {
tarih1: string
tarih2: string
}
export interface DashboardWidgetsDto {
id: number
label: string
dataValue: number
dataGrowShrink: number
valuePrefix: string
date: Date
}
export interface WidgetsData {
label: string
datavalue: number
datagrowShrink: number
valuePrefix: string
date: Date
}

View file

@ -1,9 +0,0 @@
import apiService from '../../services/api.service'
import { DashboardWidgetsRequestDto, DashboardWidgetsDto } from './models'
export const getDashboardWidgetList = (input: DashboardWidgetsRequestDto) => {
return apiService.fetchData<DashboardWidgetsDto[]>({
method: 'GET',
url: `/api/app/dashboard/widgets?Tarih1=${input.tarih1}&Tarih2=${input.tarih2}`,
})
}

View file

@ -1,106 +0,0 @@
export type ReportParameterType = 'text' | 'number' | 'date' | 'select' | 'checkbox'
export interface ReportCategoryDto {
id: string
name: string
description?: string
icon?: string
}
export interface ReportParameterDto {
id: string
templateId: string
name: string
placeholder?: string
type: ReportParameterType
defaultValue?: string
required: boolean
description?: string
}
export interface ReportTemplateDto {
id: string
name: string
description?: string
htmlContent: string
categoryId?: string
tags: string[]
parameters: ReportParameterDto[]
// FullAuditedEntityDto alanları
creationTime: string // ISO
lastModificationTime?: string // ISO | undefined
creatorId?: string
lastModifierId?: string
}
export interface ReportGeneratedDto {
id: string
templateId?: string | null
templateName: string
generatedContent: string
parameters: Record<string, string>
// FullAuditedEntityDto alanları
creationTime: string // ISO
lastModificationTime?: string // ISO | undefined
creatorId?: string
lastModifierId?: string
template?: ReportTemplateDto // dolu gelebilir
}
/** Create / Update inputları */
export interface CreateReportParameterDto {
name: string
placeholder?: string
type: ReportParameterType
defaultValue?: string
required: boolean
description?: string
}
export interface UpdateReportParameterDto extends CreateReportParameterDto {
id?: string // opsiyonel
}
export interface CreateReportTemplateDto {
name: string
description?: string
htmlContent: string
categoryId?: string
tags?: string[]
parameters: CreateReportParameterDto[]
}
export interface UpdateReportTemplateDto {
name: string
description?: string
htmlContent: string
categoryId?: string
tags?: string[]
parameters: UpdateReportParameterDto[]
}
/** Generate inputu */
export interface ReportGenerateDto {
templateId: string
parameters: Record<string, string>
}
/** List inputları (query string) */
export interface GetReportTemplatesInput {
skipCount?: number
maxResultCount?: number
sorting?: string
filter?: string
categoryId?: string
}
export interface GetReportsGeneratedInput {
skipCount?: number
maxResultCount?: number
sorting?: string
filter?: string
templateId?: string
}

View file

@ -13,6 +13,18 @@ const physicalComponentCache = new Map<
React.LazyExoticComponent<React.ComponentType<any>>
>()
// Runtime component wrappers are cached by name so their identity survives every
// re-render of the router. Without this, each render produced a brand new
// function type and React remounted the whole page — every data hook inside the
// component fired again (twice on a language change, once per configuration
// refresh).
const runtimeComponentCache = new Map<string, React.ComponentType<any>>()
// The wrapper must not close over `renderComponent`: it changes after the
// asynchronous bundle compilation finishes, and an old closure would render an
// empty page. The latest renderer is kept beside the wrapper and read at render
// time instead.
const runtimeRenderers = new Map<string, (name: string, props?: any) => React.ReactNode>()
// Fiziksel komponent yükleme (mevcut mantık)
function loadPhysicalComponent(componentPath: string) {
const cleanedPath = componentPath.replace(/^@\//, '')
@ -50,19 +62,21 @@ function loadDynamicComponent(
renderComponent &&
isComponentRegistered(componentPath)
) {
DynamicComponent = (props: any) => renderComponent(componentPath, props) as React.ReactElement
runtimeRenderers.set(componentPath, renderComponent)
let cached = runtimeComponentCache.get(componentPath)
if (!cached) {
cached = (props: any) =>
runtimeRenderers.get(componentPath)?.(componentPath, props) as React.ReactElement
cached.displayName = `RuntimeComponent(${componentPath})`
runtimeComponentCache.set(componentPath, cached)
}
DynamicComponent = cached
}
if (!DynamicComponent) {
if (isComponentRegistered) {
console.log('Database component registry available - checking...')
}
throw new Error(`Dynamic component not found: ${componentPath}`)
}
// Do not put this wrapper in a module-level cache. renderComponent changes
// after the asynchronous database bundle compilation finishes. Caching this
// function would keep the pre-compilation closure and render an empty page.
return DynamicComponent as React.ComponentType<any>
}

View file

@ -1,48 +0,0 @@
import { PagedResultDto } from '@/proxy'
import apiService from './api.service'
import { QuestionDto } from '@/types/coordinator'
class QuestionService {
async getQuestions(): Promise<PagedResultDto<QuestionDto>> {
const response = await apiService.fetchData<PagedResultDto<QuestionDto>>({
url: '/api/app/question',
method: 'GET',
})
return response.data
}
async getQuestion(id: string): Promise<QuestionDto> {
const response = await apiService.fetchData<QuestionDto>({
url: `/api/app/question/${id}`,
method: 'GET',
})
return response.data
}
async updateQuestion(id: string, input: QuestionDto) {
const response = await apiService.fetchData<QuestionDto>({
url: `/api/app/question/${id}`,
method: 'PUT',
data: input as any,
})
return response.data
}
async createQuestion(input: QuestionDto) {
const response = await apiService.fetchData<QuestionDto>({
method: 'POST',
url: '/api/app/question',
data: input as any,
})
return response.data
}
async deleteQuestion(id: string) {
await apiService.fetchData<void>({
method: 'DELETE',
url: `/api/app/question/${id}`,
})
}
}
export const questionService = new QuestionService()

View file

@ -1,139 +0,0 @@
import {
ReportTemplateDto,
ReportGeneratedDto,
CreateReportTemplateDto,
UpdateReportTemplateDto,
ReportGenerateDto,
ReportCategoryDto,
GetReportTemplatesInput, // backend'deki GenerateReportDto (templateId + parameters)
} from '@/proxy/reports/models'
import apiService from './api.service'
import { PagedAndSortedResultRequestDto, PagedResultDto } from '@/proxy'
export interface ReportsData {
templates: ReportTemplateDto[]
generatedReports: ReportGeneratedDto[]
}
export class ReportsService {
apiName = 'Default'
getCategories = () =>
apiService.fetchData<ReportCategoryDto[]>(
{
method: 'GET',
url: '/api/app/report/categories',
},
{ apiName: this.apiName },
)
// TEMPLATES
getTemplates = (input: GetReportTemplatesInput) =>
apiService.fetchData<PagedResultDto<ReportTemplateDto>, PagedAndSortedResultRequestDto>(
{
method: 'GET',
url: '/api/app/report/templates', // ✔ Swagger: GET /api/app/report/templates
params: {
sorting: input.sorting,
skipCount: input.skipCount,
maxResultCount: input.maxResultCount,
filter: input.filter,
categoryId: input.categoryId,
},
},
{ apiName: this.apiName },
)
getTemplateById = (id: string) =>
apiService.fetchData<ReportTemplateDto>(
{
method: 'GET',
url: `/api/app/report/${id}/template`, // ✔ Swagger: GET /api/app/report/{id}/template
},
{ apiName: this.apiName },
)
createTemplate = (input: CreateReportTemplateDto) =>
apiService.fetchData<ReportTemplateDto, CreateReportTemplateDto>(
{
method: 'POST',
url: '/api/app/report/template', // ✔ Swagger: POST /api/app/report/template
data: input,
},
{ apiName: this.apiName },
)
updateTemplate = (id: string, input: UpdateReportTemplateDto) =>
apiService.fetchData<ReportTemplateDto, UpdateReportTemplateDto>(
{
method: 'PUT',
url: `/api/app/report/${id}/template`, // ✔ Swagger: PUT /api/app/report/{id}/template
data: input,
},
{ apiName: this.apiName },
)
deleteTemplate = (id: string) =>
apiService.fetchData(
{
method: 'DELETE',
url: `/api/app/report/${id}/template`, // ✔ Swagger: DELETE /api/app/report/{id}/template
},
{ apiName: this.apiName },
)
// GENERATED REPORTS
getGeneratedReports = (input: PagedAndSortedResultRequestDto) =>
apiService.fetchData<PagedResultDto<ReportGeneratedDto>, PagedAndSortedResultRequestDto>(
{
method: 'GET',
url: '/api/app/report/generated-reports', // ✔ Swagger: GET /api/app/report/generated-reports
params: {
sorting: input.sorting,
skipCount: input.skipCount,
maxResultCount: input.maxResultCount,
},
},
{ apiName: this.apiName },
)
getGeneratedReportById = (id: string) =>
apiService.fetchData<ReportGeneratedDto>(
{
method: 'GET',
url: `/api/app/report/${id}/generated-report`, // ✔ Swagger: GET /api/app/report/{id}/generated-report
},
{ apiName: this.apiName },
)
generateReport = (input: ReportGenerateDto) =>
apiService.fetchData<ReportGeneratedDto, ReportGenerateDto>(
{
method: 'POST',
url: '/api/app/report/generate-report', // ✔ Swagger: POST /api/app/report/generate-report
data: input,
},
{ apiName: this.apiName },
)
deleteGeneratedReport = (id: string) =>
apiService.fetchData(
{
method: 'DELETE',
url: `/api/app/report/${id}/generated-report`, // ✔ Swagger: DELETE /api/app/report/{id}/generated-report
},
{ apiName: this.apiName },
)
// BULK
getAllData = () =>
apiService.fetchData<ReportsData>(
{
method: 'GET',
url: '/api/app/report/data', // ✔ Swagger: GET /api/app/report/data
},
{ apiName: this.apiName },
)
}
export default ReportsService

View file

@ -1,18 +0,0 @@
import { RouteDto } from '@/proxy/routes/models'
import apiService, { Config } from '@/services/api.service'
export class RouteService {
apiName = 'Default'
getRoutes = (config?: Partial<Config>) =>
apiService.fetchData<RouteDto[]>(
{
method: 'GET',
url: '/api/app/route',
},
{ apiName: this.apiName, ...config },
)
}
const routeService = new RouteService()
export default routeService

View file

@ -1,22 +0,0 @@
export interface WidgetGroupDto {
// Widget Grubu
colGap?: number
colSpan?: number
className?: string
items: WidgetEditDto[]
}
export interface WidgetEditDto {
// Widget Düzenleme
colGap: number
colSpan: number
sqlQuery?: string
className?: string
title: string
value: string
valueClassName: string
color: string
icon: string
subTitle: string
onClick: string
}

View file

@ -1,134 +0,0 @@
import { FullAuditedEntityDto } from "@/proxy";
export type QuestionType =
| 'multiple-choice'
| 'fill-blank'
| 'multiple-answer'
| 'matching'
| 'ordering'
| 'open-ended'
| 'true-false'
| 'calculation'
export const QUESTION_TYPE_LABELS: Record<QuestionType, string> = {
'multiple-choice': 'Multiple Choice',
'fill-blank': 'Fill in the Blank',
'multiple-answer': 'Multiple Answer',
'matching': 'Matching',
'ordering': 'Ordering',
'open-ended': 'Open Ended',
'true-false': 'True / False',
'calculation': 'Calculation',
}
export type ExamType = "exam" | "assignment" | "test";
export type TestType = 'pdf' | 'image';
export type MediaType = 'image' | 'video';
export type QuestionDifficulty = 'easy' | 'medium' | 'hard';
export type ExamSessionStatus = 'in-progress' | 'completed' | 'submitted';
export interface QuestionPoolDto extends FullAuditedEntityDto {
name: string;
description: string;
questions: QuestionDto[];
tags: string[];
}
export interface QuestionDto extends FullAuditedEntityDto {
questionType: QuestionType;
title: string;
content: string;
mediaUrl?: string;
mediaType?: MediaType;
options?: QuestionOptionDto[];
correctAnswer?: string | string[];
points: number;
timeLimit?: number;
explanation?: string;
difficulty: QuestionDifficulty;
}
export interface QuestionOptionDto extends FullAuditedEntityDto {
text: string;
isCorrect: boolean;
}
export interface Exam {
id: string;
title: string;
description: string;
type: ExamType;
testDocument?: {
url: string;
type: TestType;
name: string;
};
answerKeyTemplate?: AnswerKeyItem[];
questions: QuestionDto[];
timeLimit: number;
totalPoints: number;
passingScore: number;
allowReview: boolean;
randomizeQuestions: boolean;
showResults: boolean;
maxAttempts: number;
startTime?: Date;
endTime?: Date;
isActive: boolean;
creationTime: Date;
lastModificationTime: Date;
}
export interface AnswerKeyItem {
id: string;
questionNumber: number;
type: QuestionType;
options?: string[];
points: number;
correctAnswer?: string | string[];
}
export interface StudentAnswer {
questionId: string;
answer: string | string[];
timeSpent: number;
isCorrect?: boolean;
points?: number;
}
export interface ExamSession {
id: string;
examId: string;
studentId: string;
startTime: Date;
endTime?: Date;
answers: StudentAnswer[];
totalScore?: number;
status: ExamSessionStatus;
timeRemaining: number;
}
export interface TagItem {
id: string;
name: string;
description: string;
color: string;
usageCount: number;
creationTime: Date;
}
export interface NavigationItem {
id: string;
title: string;
icon: string;
path: string;
description?: string;
badge?: number;
}
export interface AdminRoute {
path: string;
component: string;
title: string;
breadcrumb: string[];
}

View file

@ -1,36 +0,0 @@
export enum LeaveStatusEnum {
// İzin Durumu
Pending = 'Pending', // Beklemede
Approved = 'Approved', // Onaylandı
Rejected = 'Rejected', // Reddedildi
Cancelled = 'Cancelled', // İptal edildi
}
export enum LeaveTypeEnum {
// İzin Türü
Annual = 'Annual', // Yıllık
Sick = 'Sick', // Hastalık
Maternity = 'Maternity', // Doğum
Paternity = 'Paternity', // Babalık
Personal = 'Personal', // Kişisel
Emergency = 'Emergency', // Acil
Study = 'Study', // Eğitim
Unpaid = 'Unpaid', // Ücretsiz
}
export enum PriorityEnum {
// Öncelik
Low = 'LOW', // Düşük
Normal = 'NORMAL', // Normal
High = 'HIGH', // Yüksek
Urgent = 'URGENT', // Acil
}
export enum TaskStatusEnum {
// Görev Durumu
NotStarted = 'NOT_STARTED', // Başlanmadı
InProgress = 'IN_PROGRESS', // Devam Ediyor
Completed = 'COMPLETED', // Tamamlandı
OnHold = 'ON_HOLD', // Beklemede
Cancelled = 'CANCELLED', // İptal Edildi
}

View file

@ -1,32 +0,0 @@
const isNumString = (str: string): boolean => !isNaN(Number(str))
type JsonObject = { [key: string]: unknown }
type JsonArray = Array<unknown>
type Json = JsonObject | JsonArray | string | number | boolean | null
function deepParseJson(jsonString: Json): Json {
if (typeof jsonString === 'string') {
if (isNumString(jsonString)) {
return jsonString
}
try {
return deepParseJson(JSON.parse(jsonString))
} catch {
return jsonString
}
} else if (Array.isArray(jsonString)) {
return jsonString.map((val) => deepParseJson(val as JsonArray))
} else if (typeof jsonString === 'object' && jsonString !== null) {
return Object.keys(jsonString).reduce<JsonObject>((obj, key) => {
const val = jsonString[key]
obj[key] = isNumString(val as string)
? val
: deepParseJson(val as number)
return obj
}, {})
} else {
return jsonString
}
}
export default deepParseJson

View file

@ -1,54 +0,0 @@
import { Role, RoleState, VideoroomDto } from '@/proxy/videoroom/models'
import { useStoreActions, useStoreState } from '@/store/store'
import { useState } from 'react'
export function useVideoroomLogic() {
const { user } = useStoreState((state) => state.auth)
const { setUser } = useStoreActions((actions) => actions.auth.user)
const [roleState, setRoleState] = useState<RoleState>('role-selection')
const [currentClass, setCurrentClass] = useState<VideoroomDto | null>(null)
const [allClasses, setAllClasses] = useState<VideoroomDto[]>([])
const handleRoleSelect = (role: Role) => {
setUser({
...user,
role,
})
setRoleState('dashboard')
}
const handleCreateClass = (classData: Partial<VideoroomDto>) => {
const newClass = {
...classData,
id: crypto.randomUUID(),
teacherId: '',
teacherName: '',
isActive: false,
isScheduled: true,
participantCount: 0,
} as VideoroomDto
setAllClasses((prev) => [...prev, newClass])
}
const handleEditClass = (classId: string, classData: Partial<VideoroomDto>) => {
setAllClasses((prev) => prev.map((c) => (c.id === classId ? { ...c, ...classData } : c)))
}
const handleDeleteClass = (classId: string) => {
setAllClasses((prev) => prev.filter((c) => c.id !== classId))
}
return {
roleState,
setRoleState,
currentClass,
setCurrentClass,
allClasses,
setAllClasses,
handleRoleSelect,
handleCreateClass,
handleEditClass,
handleDeleteClass,
}
}

View file

@ -1,130 +0,0 @@
import { QuestionPoolDto, Exam, TagItem, ExamSession } from "@/types/coordinator";
import { useState } from "react";
export function useCoordinator() {
const [currentPath, setCurrentPath] = useState("/admin/dashboard");
const [pools, setPools] = useState<QuestionPoolDto[]>([]);
const [exams, setExams] = useState<Exam[]>([]);
const [tags, setTags] = useState<TagItem[]>([]);
const [currentExam, setCurrentExam] = useState<Exam | null>(null);
const handleUpdateTest = (updatedTest: Exam) => {
setExams((prev) =>
prev.map((exam) => (exam.id === updatedTest.id ? updatedTest : exam))
);
};
const handleDeleteTest = (testId: string) => {
setExams((prev) => prev.filter((exam) => exam.id !== testId));
};
const handleCreatePool = (
poolData: Omit<QuestionPoolDto, "id" | "creationTime">
) => {
const newPool: QuestionPoolDto = {
...poolData,
id: `pool-${Date.now()}`,
creationTime: new Date(),
};
setPools((prev) => [...prev, newPool]);
};
const handleUpdatePool = (updatedPool: QuestionPoolDto) => {
setPools((prev) =>
prev.map((pool) => (pool.id === updatedPool.id ? updatedPool : pool))
);
};
const handleDeletePool = (poolId: string) => {
setPools((prev) => prev.filter((pool) => pool.id !== poolId));
};
const handleCreateExam = (
examData: Omit<Exam, "id" | "creationTime" | "lastModificationTime">
) => {
const newExam: Exam = {
...examData,
id: `exam-${Date.now()}`,
creationTime: new Date(),
lastModificationTime: new Date(),
};
setExams((prev) => [...prev, newExam]);
setCurrentPath("/admin/exams");
};
const handleSaveExam = (exam: Exam) => {
setExams((prev) => prev.map((e) => (e.id === exam.id ? exam : e)));
setCurrentPath("/admin/exams");
};
const handleCreateTest = (
testData: Omit<Exam, "id" | "creationTime" | "lastModificationTime">
) => {
const newTest: Exam = {
...testData,
id: `test-${Date.now()}`,
creationTime: new Date(),
lastModificationTime: new Date(),
};
setExams((prev) => [...prev, newTest]);
setCurrentPath("/admin/tests");
};
const handleCreateTag = (
tagData: Omit<TagItem, "id" | "usageCount" | "creationTime">
) => {
const newTag: TagItem = {
...tagData,
id: `tag-${Date.now()}`,
usageCount: 0,
creationTime: new Date(),
};
setTags((prev) => [...prev, newTag]);
};
const handleUpdateTag = (updatedTag: TagItem) => {
setTags((prev) =>
prev.map((tag) => (tag.id === updatedTag.id ? updatedTag : tag))
);
};
const handleDeleteTag = (tagId: string) => {
setTags((prev) => prev.filter((tag) => tag.id !== tagId));
};
const handleExamComplete = (session: ExamSession) => {
alert("Assessment completed successfully!");
setCurrentPath("/admin/dashboard");
setCurrentExam(null);
};
const startExam = (exam: Exam) => {
setCurrentExam(exam);
};
return {
currentPath,
setCurrentPath,
pools,
setPools,
exams,
setExams,
tags,
setTags,
currentExam,
setCurrentExam,
handleUpdateTest,
handleDeleteTest,
handleCreatePool,
handleUpdatePool,
handleDeletePool,
handleCreateExam,
handleSaveExam,
handleCreateTest,
handleCreateTag,
handleUpdateTag,
handleDeleteTag,
handleExamComplete,
startExam,
};
}

View file

@ -1,95 +0,0 @@
import { useEffect, useCallback } from 'react';
interface SecurityConfig {
disableRightClick: boolean;
disableCopyPaste: boolean;
disableDevTools: boolean;
fullScreenMode: boolean;
preventTabSwitch: boolean;
}
export const useExamSecurity = (config: SecurityConfig, isActive: boolean = true) => {
const handleRightClick = useCallback((e: MouseEvent) => {
if (config.disableRightClick && isActive) {
e.preventDefault();
return false;
}
}, [config.disableRightClick, isActive]);
const handleKeyDown = useCallback((e: KeyboardEvent) => {
if (!isActive) return;
// Disable copy/paste shortcuts
if (config.disableCopyPaste) {
if (e.ctrlKey && (e.key === 'c' || e.key === 'v' || e.key === 'x' || e.key === 'a')) {
e.preventDefault();
return false;
}
}
// Disable developer tools
if (config.disableDevTools) {
if (e.key === 'F12' || (e.ctrlKey && e.shiftKey && e.key === 'I')) {
e.preventDefault();
return false;
}
}
// Disable Alt+Tab for tab switching
if (config.preventTabSwitch && e.altKey && e.key === 'Tab') {
e.preventDefault();
return false;
}
}, [config, isActive]);
const handleVisibilityChange = useCallback(() => {
if (config.preventTabSwitch && isActive && document.hidden) {
// Log tab switch attempt - in real app, this would call an API
console.warn('Tab switch detected during exam');
}
}, [config.preventTabSwitch, isActive]);
const enterFullScreen = useCallback(async () => {
if (config.fullScreenMode && isActive) {
try {
await document.documentElement.requestFullscreen();
} catch (error) {
console.error('Failed to enter fullscreen:', error);
}
}
}, [config.fullScreenMode, isActive]);
const exitFullScreen = useCallback(async () => {
if (document.fullscreenElement) {
try {
await document.exitFullscreen();
} catch (error) {
console.error('Failed to exit fullscreen:', error);
}
}
}, []);
useEffect(() => {
if (isActive) {
document.addEventListener('contextmenu', handleRightClick);
document.addEventListener('keydown', handleKeyDown);
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => {
document.removeEventListener('contextmenu', handleRightClick);
document.removeEventListener('keydown', handleKeyDown);
document.removeEventListener('visibilitychange', handleVisibilityChange);
if (config.fullScreenMode) {
exitFullScreen();
}
};
}
}, [handleRightClick, handleKeyDown, handleVisibilityChange, exitFullScreen, isActive, config.fullScreenMode]);
return {
enterFullScreen,
exitFullScreen,
isFullScreen: !!document.fullscreenElement
};
};

View file

@ -1,94 +0,0 @@
import { useState, useEffect, useCallback } from 'react';
interface UseExamTimerProps {
initialTime: number; // in seconds
onTimeUp?: () => void;
onTick?: (timeRemaining: number) => void;
autoStart?: boolean;
}
export const useExamTimer = ({
initialTime,
onTimeUp,
onTick,
autoStart = false
}: UseExamTimerProps) => {
const [timeRemaining, setTimeRemaining] = useState(initialTime);
const [isRunning, setIsRunning] = useState(autoStart);
const [isPaused, setIsPaused] = useState(false);
const start = useCallback(() => {
setIsRunning(true);
setIsPaused(false);
}, []);
const pause = useCallback(() => {
setIsPaused(true);
}, []);
const resume = useCallback(() => {
setIsPaused(false);
}, []);
const reset = useCallback(() => {
setTimeRemaining(initialTime);
setIsRunning(false);
setIsPaused(false);
}, [initialTime]);
const stop = useCallback(() => {
setIsRunning(false);
setIsPaused(false);
}, []);
useEffect(() => {
let interval: ReturnType<typeof setInterval>;
if (isRunning && !isPaused && timeRemaining > 0) {
interval = setInterval(() => {
setTimeRemaining((prev) => {
const newTime = prev - 1;
onTick?.(newTime);
if (newTime <= 0) {
setIsRunning(false);
onTimeUp?.();
return 0;
}
return newTime;
});
}, 1000);
}
return () => {
if (interval) {
clearInterval(interval);
}
};
}, [isRunning, isPaused, timeRemaining, onTimeUp, onTick]);
const formatTime = useCallback((seconds: number) => {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
if (hours > 0) {
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}, []);
return {
timeRemaining,
formattedTime: formatTime(timeRemaining),
isRunning,
isPaused,
start,
pause,
resume,
reset,
stop,
progress: ((initialTime - timeRemaining) / initialTime) * 100
};
};

View file

@ -1,27 +0,0 @@
import { Product } from '@/proxy/order/models'
import { useState, useEffect } from 'react'
export const useOrders = () => {
const [products, setProducts] = useState<Product[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
fetchProducts()
}, [])
const fetchProducts = async () => {
try {
setLoading(true)
setProducts(products)
setError(null)
} catch (err) {
console.error('Products fetch error:', err)
setError('App.Orders.LoadProductsError')
} finally {
setLoading(false)
}
}
return { products, loading, error, refetch: fetchProducts }
}

View file

@ -1,227 +0,0 @@
import { ReportGeneratedDto, ReportTemplateDto, ReportCategoryDto } from '@/proxy/reports/models'
import ReportsService from '@/services/reports.service'
import { useState, useCallback, useEffect } from 'react'
const reportsService = new ReportsService()
interface ReportData {
templates: ReportTemplateDto[]
generatedReports: ReportGeneratedDto[]
categories: ReportCategoryDto[]
}
export const useReports = () => {
const [data, setData] = useState<ReportData>({
templates: [],
generatedReports: [],
categories: [],
})
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
const loadData = async () => {
setIsLoading(true)
try {
await new Promise((resolve) => setTimeout(resolve, 200))
const [templatesResponse, generatedReportsResponse, categoriesResponse] = await Promise.all(
[
reportsService.getTemplates({
sorting: '',
skipCount: 0,
maxResultCount: 1000,
}),
reportsService.getGeneratedReports({
sorting: '',
skipCount: 0,
maxResultCount: 1000,
}),
reportsService.getCategories(),
],
)
setData({
templates: templatesResponse.data.items || [],
generatedReports: generatedReportsResponse.data.items || [],
categories: categoriesResponse.data || [],
})
} catch (error) {
console.error('Error loading data:', error)
// Fallback to default data
setData({
templates: [],
generatedReports: [],
categories: [],
})
} finally {
setIsLoading(false)
}
}
loadData()
}, [])
const createTemplate = useCallback(
async (template: ReportTemplateDto) => {
setIsLoading(true)
try {
const response = await reportsService.createTemplate(template as ReportTemplateDto)
const newTemplate = response.data as ReportTemplateDto
// Update local state
setData((prevData) => ({
...prevData,
templates: [...prevData.templates, newTemplate],
}))
return newTemplate
} catch (error) {
console.error('Error creating template:', error)
throw error
} finally {
setIsLoading(false)
}
},
[data],
)
const updateTemplate = useCallback(async (id: string, updates: Partial<ReportTemplateDto>) => {
setIsLoading(true)
try {
// First get the current template to merge with updates
const currentTemplateResponse = await reportsService.getTemplateById(id)
const currentTemplate = currentTemplateResponse.data as ReportTemplateDto
const updatedTemplate = { ...currentTemplate, ...updates }
await reportsService.updateTemplate(id, updatedTemplate)
// Update local state
setData((prevData) => ({
...prevData,
templates: prevData.templates.map((template) =>
template.id === id
? { ...template, ...updates, lastModificationTime: new Date().toISOString() }
: template,
),
}))
} catch (error) {
console.error('Error updating template:', error)
throw error
} finally {
setIsLoading(false)
}
}, [])
const deleteTemplate = useCallback(async (id: string) => {
setIsLoading(true)
try {
await reportsService.deleteTemplate(id)
// Update local state
setData((prevData) => ({
...prevData,
templates: prevData.templates.filter((template) => template.id !== id),
}))
} catch (error) {
console.error('Error deleting template:', error)
throw error
} finally {
setIsLoading(false)
}
}, [])
const generateReport = useCallback(
async (templateId: string, parameterValues: Record<string, string>) => {
setIsLoading(true)
try {
const reportData = {
templateId,
parameters: parameterValues,
}
const response = await reportsService.generateReport(reportData)
const report = response.data as ReportGeneratedDto
if (report) {
// Update local state
setData((prevData) => ({
...prevData,
generatedReports: [...prevData.generatedReports, report],
}))
}
return report
} catch (error) {
console.error('Error generating report:', error)
throw error
} finally {
setIsLoading(false)
}
},
[],
)
const getReportById = useCallback(
async (reportId: string) => {
try {
const response = await reportsService.getGeneratedReportById(reportId)
return response.data as ReportGeneratedDto
} catch (error) {
console.error('Error getting report by id:', error)
// Fallback to local data
return data.generatedReports.find((report) => report.id === reportId)
}
},
[data.generatedReports],
)
const getTemplateById = useCallback(
async (templateId: string) => {
try {
const response = await reportsService.getTemplateById(templateId)
return response.data as ReportTemplateDto
} catch (error) {
console.error('Error getting template by id:', error)
// Fallback to local data
return data.templates.find((template) => template.id === templateId)
}
},
[data.templates],
)
const loadTemplatesByCategory = useCallback(async (categoryId?: string) => {
setIsLoading(true)
try {
const templatesResponse = await reportsService.getTemplates({
sorting: '',
skipCount: 0,
maxResultCount: 1000,
categoryId: categoryId && categoryId !== 'Tümü' ? categoryId : undefined,
})
setData((prevData) => ({
...prevData,
templates: templatesResponse.data.items || [],
}))
} catch (error) {
console.error('Error loading templates by category:', error)
} finally {
setIsLoading(false)
}
}, [])
return {
templates: data.templates,
generatedReports: data.generatedReports,
categories: data.categories,
isLoading,
setIsLoading,
createTemplate,
updateTemplate,
deleteTemplate,
generateReport,
getReportById,
getTemplateById,
loadTemplatesByCategory,
}
}

View file

@ -1,6 +0,0 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const paginate = (array: Array<any>, pageSize: number, pageNumber: number) => {
return array.slice((pageNumber - 1) * pageSize, pageNumber * pageSize)
}
export default paginate

View file

@ -1,11 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export default function requiredFieldValidation(
value: any,
message: string
): string {
let validationMessage = ''
if (!value) {
validationMessage = message || 'Required'
}
return validationMessage
}

View file

@ -1,20 +0,0 @@
const shadeColor = (color: string, percent: number) => {
let R = parseInt(color.substring(1, 3), 16)
let G = parseInt(color.substring(3, 5), 16)
let B = parseInt(color.substring(5, 7), 16)
R = (R * (100 + percent)) / 100
G = (G * (100 + percent)) / 100
B = (B * (100 + percent)) / 100
R = R < 255 ? R : 255
G = G < 255 ? G : 255
B = B < 255 ? B : 255
const RR =
R.toString(16).length === 1 ? `0${R.toString(16)}` : R.toString(16)
const GG =
G.toString(16).length === 1 ? `0${G.toString(16)}` : G.toString(16)
const BB =
B.toString(16).length === 1 ? `0${B.toString(16)}` : B.toString(16)
return `#${RR}${GG}${BB}`
}
export default shadeColor

View file

@ -1,28 +0,0 @@
type Primitive = string | number | boolean
export type Primer = (value: Primitive) => Primitive
const sortBy = <T extends Record<string, Primitive>>(
field: keyof T,
reverse: boolean,
primer?: (value: Primitive) => Primitive
) => {
const key = primer
? function (x: T) {
return primer(x[field])
}
: function (x: T) {
return x[field]
}
const isReverse = !reverse ? 1 : -1
return function (a: T, b: T) {
const valueA = key(a)
const valueB = key(b)
if (typeof valueA === 'string' && typeof valueB === 'string') {
return isReverse * valueA.localeCompare(valueB)
}
return isReverse * (valueA > valueB ? 1 : valueB > valueA ? -1 : 0)
}
}
export default sortBy

View file

@ -1,24 +0,0 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export default function wildCardSearch(
list: Array<Record<string, string | number>>,
input: string,
specifyKey?: string
) {
const searchText = (item: Record<string, string | number>) => {
for (const key in item) {
if (item[specifyKey ? specifyKey : key] == null) {
continue
}
if (
item[specifyKey ? specifyKey : key]
.toString()
.toUpperCase()
.indexOf(input.toString().toUpperCase()) !== -1
) {
return true
}
}
}
const result = list.filter((value) => searchText(value))
return result
}

View file

@ -1,3 +0,0 @@
import Views from './Views'
export default Views