2026-08-06 22:54:50 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* FormView / FormEdit / FormNew ekranlarının ortak sayfa iskeleti.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Üç bileşen de aynı düzeni (Helmet + başlık + FormButtons + FormDevExpress +
|
|
|
|
|
|
* SubForms + NotePanel) satır satır kopyalıyordu. Tek noktadan yönetilir.
|
|
|
|
|
|
*/
|
|
|
|
|
|
import type { ReactNode, RefObject } from 'react'
|
|
|
|
|
|
import { useState } from 'react'
|
2026-08-28 13:44:14 +00:00
|
|
|
|
import { FaInfoCircle } from 'react-icons/fa'
|
2026-08-06 22:54:50 +00:00
|
|
|
|
import { Container, Loading } from '@/components/shared'
|
2026-08-28 13:44:14 +00:00
|
|
|
|
import { Badge, Dialog } from '@/components/ui'
|
|
|
|
|
|
import RichText from '@/components/shared/RichText'
|
2026-08-06 22:54:50 +00:00
|
|
|
|
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'
|
2026-08-13 08:41:48 +00:00
|
|
|
|
import PageTitle from '@/components/shared/PageTitle'
|
2026-08-06 22:54:50 +00:00
|
|
|
|
import FormDevExpress from '../FormDevExpress'
|
|
|
|
|
|
import SubForms from '../SubForms'
|
|
|
|
|
|
import { NotePanel } from '../notes/NotePanel'
|
|
|
|
|
|
import type { RowMode } from '../types'
|
|
|
|
|
|
|
|
|
|
|
|
type FormPageShellProps = {
|
|
|
|
|
|
mode: RowMode
|
|
|
|
|
|
listFormCode: string
|
|
|
|
|
|
isSubForm?: boolean
|
|
|
|
|
|
level?: number
|
|
|
|
|
|
loading: boolean
|
|
|
|
|
|
gridDto?: GridDto
|
|
|
|
|
|
formData: any
|
|
|
|
|
|
formItems: GroupItem[]
|
2026-08-13 08:41:48 +00:00
|
|
|
|
refForm: RefObject<FormRef | null>
|
2026-08-06 22:54:50 +00:00
|
|
|
|
setFormData: (data: any) => void
|
|
|
|
|
|
/** Başlığın sağındaki aksiyon butonları (FormButtons). */
|
|
|
|
|
|
actions?: ReactNode
|
|
|
|
|
|
/** Not paneli kimliği; verilmezse panel gösterilmez. */
|
|
|
|
|
|
noteEntityId?: string
|
|
|
|
|
|
/** Alt form sekmelerini yenileme geri çağrısı. */
|
|
|
|
|
|
refreshData?: () => Promise<void>
|
|
|
|
|
|
/** FormNew'de alt form gösterilmez. */
|
|
|
|
|
|
showSubForms?: boolean
|
|
|
|
|
|
/** Form gövdesinin sarmalayıcı sınıfı (FormView farklı dolgu kullanır). */
|
|
|
|
|
|
contentClassName?: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const FormPageShell = ({
|
|
|
|
|
|
mode,
|
|
|
|
|
|
listFormCode,
|
|
|
|
|
|
isSubForm,
|
|
|
|
|
|
level,
|
|
|
|
|
|
loading,
|
|
|
|
|
|
gridDto,
|
|
|
|
|
|
formData,
|
|
|
|
|
|
formItems,
|
|
|
|
|
|
refForm,
|
|
|
|
|
|
setFormData,
|
|
|
|
|
|
actions,
|
|
|
|
|
|
noteEntityId,
|
|
|
|
|
|
refreshData,
|
|
|
|
|
|
showSubForms = true,
|
|
|
|
|
|
contentClassName = 'px-2',
|
|
|
|
|
|
}: FormPageShellProps) => {
|
|
|
|
|
|
const { translate } = useLocalization()
|
|
|
|
|
|
const { checkPermission } = usePermission()
|
|
|
|
|
|
const MenuIcon = useCurrentMenuIcon('w-5 h-5')
|
|
|
|
|
|
const [isNotePanelVisible, setIsNotePanelVisible] = useState(false)
|
2026-08-28 13:44:14 +00:00
|
|
|
|
const [descriptionOpen, setDescriptionOpen] = useState(false)
|
2026-08-06 22:54:50 +00:00
|
|
|
|
|
|
|
|
|
|
const showNotePanel = Boolean(
|
|
|
|
|
|
listFormCode &&
|
|
|
|
|
|
noteEntityId &&
|
|
|
|
|
|
!isSubForm &&
|
|
|
|
|
|
gridDto?.gridOptions?.showNote &&
|
|
|
|
|
|
checkPermission(gridDto?.gridOptions.permissionDto.n),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if (!listFormCode) {
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (loading) {
|
|
|
|
|
|
return <Loading type="default" loading />
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!formData) {
|
2026-08-14 14:03:34 +00:00
|
|
|
|
return <>{translate('::App.Platform.NoResults2')}</>
|
2026-08-06 22:54:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const title = translate('::' + gridDto?.gridOptions.title)
|
2026-08-28 13:44:14 +00:00
|
|
|
|
// Description bir dil anahtarıdır; karşılığı yoksa translate anahtarı geri
|
|
|
|
|
|
// döndürür, o durumda kullanıcıya anahtar gösterilmez.
|
|
|
|
|
|
const descriptionKey = gridDto?.gridOptions?.description
|
|
|
|
|
|
const descriptionText = descriptionKey ? translate('::' + descriptionKey) : null
|
|
|
|
|
|
const description =
|
|
|
|
|
|
descriptionText && descriptionText !== descriptionKey ? descriptionText : null
|
2026-08-06 22:54:50 +00:00
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<Container
|
|
|
|
|
|
className={`${
|
|
|
|
|
|
isNotePanelVisible && !isSubForm ? 'lg:mr-[375px]' : ''
|
|
|
|
|
|
} transition-all duration-300`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{!isSubForm && (
|
2026-08-13 08:41:48 +00:00
|
|
|
|
<PageTitle title={title} />
|
2026-08-06 22:54:50 +00:00
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
|
className={`flex items-center pb-2 px-2 ${isSubForm ? 'justify-end' : 'justify-between'}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
{MenuIcon}
|
|
|
|
|
|
{!isSubForm && (
|
|
|
|
|
|
<>
|
2026-08-28 13:44:14 +00:00
|
|
|
|
{description ? (
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="flex items-center gap-1 leading-none hover:underline"
|
|
|
|
|
|
title={translate('::App.Platform.ShowDescription')}
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => setDescriptionOpen(true)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<h4 className="text-slate-700 text-sm font-medium leading-none">{title}</h4>
|
|
|
|
|
|
<FaInfoCircle className="text-xs opacity-60" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<h4 className="text-slate-700 text-sm font-medium leading-none">{title}</h4>
|
|
|
|
|
|
)}
|
|
|
|
|
|
●
|
2026-08-06 22:54:50 +00:00
|
|
|
|
<Badge content={mode} />
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{actions}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className={contentClassName}>
|
|
|
|
|
|
<FormDevExpress
|
|
|
|
|
|
mode={mode}
|
|
|
|
|
|
refForm={refForm}
|
|
|
|
|
|
formData={formData}
|
|
|
|
|
|
formItems={formItems}
|
|
|
|
|
|
setFormData={setFormData}
|
|
|
|
|
|
listFormCode={listFormCode}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{showSubForms && gridDto && (
|
|
|
|
|
|
<SubForms
|
|
|
|
|
|
gridDto={gridDto}
|
|
|
|
|
|
formData={formData}
|
|
|
|
|
|
level={level ?? 0}
|
|
|
|
|
|
refreshData={refreshData}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</Container>
|
|
|
|
|
|
|
2026-08-28 13:44:14 +00:00
|
|
|
|
{description && (
|
|
|
|
|
|
<Dialog
|
|
|
|
|
|
isOpen={descriptionOpen}
|
|
|
|
|
|
width={860}
|
|
|
|
|
|
onClose={() => setDescriptionOpen(false)}
|
|
|
|
|
|
onRequestClose={() => setDescriptionOpen(false)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Dialog.Header className="-mt-5 p-2 border-b border-gray-200 dark:border-gray-700">
|
|
|
|
|
|
<h2 className="text-lg font-semibold text-gray-800 dark:text-gray-100">{title}</h2>
|
|
|
|
|
|
</Dialog.Header>
|
|
|
|
|
|
<Dialog.Body className="mt-2 max-h-[70vh] select-text overflow-y-auto pr-1">
|
|
|
|
|
|
<RichText text={description} />
|
|
|
|
|
|
</Dialog.Body>
|
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-08-06 22:54:50 +00:00
|
|
|
|
{/* Not paneli yalnızca ana formda gösterilir. */}
|
|
|
|
|
|
{showNotePanel && (
|
|
|
|
|
|
<NotePanel
|
|
|
|
|
|
entityName={listFormCode}
|
|
|
|
|
|
entityId={noteEntityId!}
|
|
|
|
|
|
isVisible={isNotePanelVisible}
|
|
|
|
|
|
onToggle={() => setIsNotePanelVisible((visible) => !visible)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|