sozsoft-platform/ui/src/views/form/shared/FormPageShell.tsx

187 lines
5.8 KiB
TypeScript
Raw Normal View History

/**
* 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'
import { FaInfoCircle } from 'react-icons/fa'
import { Container, Loading } from '@/components/shared'
import { Badge, Dialog } from '@/components/ui'
import RichText from '@/components/shared/RichText'
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 PageTitle from '@/components/shared/PageTitle'
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[]
refForm: RefObject<FormRef | null>
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)
const [descriptionOpen, setDescriptionOpen] = useState(false)
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-10-19 14:35:08 +00:00
return <>{translate('::Sas.Platform.NoResults2')}</>
}
const title = translate('::' + gridDto?.gridOptions.title)
// 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
return (
<>
<Container
className={`${
isNotePanelVisible && !isSubForm ? 'lg:mr-[375px]' : ''
} transition-all duration-300`}
>
{!isSubForm && (
<PageTitle title={title} />
)}
<div
className={`flex items-center px-2 ${isSubForm ? 'justify-end' : 'justify-between'}`}
>
<div className="flex items-center gap-2">
{!isSubForm && (
<>
{MenuIcon}
{description ? (
<button
className="flex items-center gap-1 leading-none hover:underline"
2026-10-19 14:35:08 +00:00
title={translate('::Sas.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>
)}
●
<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>
{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>
)}
{/* Not paneli yalnızca ana formda gösterilir. */}
{showNotePanel && (
<NotePanel
entityName={listFormCode}
entityId={noteEntityId!}
isVisible={isNotePanelVisible}
onToggle={() => setIsNotePanelVisible((visible) => !visible)}
/>
)}
</>
)
}