186 lines
5.8 KiB
TypeScript
186 lines
5.8 KiB
TypeScript
/**
|
||
* 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)}
|
||
/>
|
||
)}
|
||
</>
|
||
)
|
||
}
|