sozsoft-platform/ui/src/views/form/shared/FormPageShell.tsx
2026-10-06 10:19:26 +03:00

186 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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) {
return <>{translate('::App.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"
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>
)}
●
<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)}
/>
)}
</>
)
}