2025-10-22 14:58:27 +00:00
|
|
|
import { EditingFormItemDto, GridDto, PlatformEditorTypes } from "@/proxy/form/models"
|
|
|
|
|
import { useLocalization } from "@/utils/hooks/useLocalization"
|
|
|
|
|
import { usePermission } from "@/utils/hooks/usePermission"
|
|
|
|
|
import { usePWA } from "@/utils/hooks/usePWA"
|
|
|
|
|
import CustomStore from "devextreme/data/custom_store"
|
2025-11-30 16:56:36 +00:00
|
|
|
import { useMemo, useRef, useState, forwardRef } from "react"
|
2025-10-22 14:58:27 +00:00
|
|
|
import { useNavigate } from "react-router-dom"
|
|
|
|
|
import { GroupItem } from 'devextreme/ui/form'
|
|
|
|
|
import { PermissionResults, SimpleItemWithColData } from "../form/types"
|
|
|
|
|
import { captionize } from 'devextreme/core/utils/inflector'
|
|
|
|
|
import { ROUTES_ENUM } from "@/routes/route.constant"
|
|
|
|
|
import FormButtons from "../form/FormButtons"
|
|
|
|
|
import FormDevExpress from "../form/FormDevExpress"
|
2025-11-28 13:31:53 +00:00
|
|
|
import { FormRef } from "devextreme-react/cjs/form"
|
2025-11-30 16:56:36 +00:00
|
|
|
import { Checkbox } from "@/components/ui"
|
|
|
|
|
import classNames from "classnames"
|
2025-10-22 14:58:27 +00:00
|
|
|
|
2025-11-30 16:56:36 +00:00
|
|
|
interface CardItemProps {
|
2025-10-22 14:58:27 +00:00
|
|
|
isSubForm?: boolean
|
|
|
|
|
row: any
|
|
|
|
|
gridDto: GridDto
|
|
|
|
|
listFormCode: string
|
|
|
|
|
dataSource: CustomStore
|
|
|
|
|
refreshData: () => void
|
|
|
|
|
getCachedLookupDataSource: (editorOptions: any, colData: any, row?: any) => any
|
2025-11-30 16:56:36 +00:00
|
|
|
// Selection and interaction props
|
|
|
|
|
isSelected?: boolean
|
|
|
|
|
isFocused?: boolean
|
|
|
|
|
isHovered?: boolean
|
|
|
|
|
onSelectionChange?: (checked: boolean) => void
|
|
|
|
|
onClick?: (e: React.MouseEvent) => void
|
|
|
|
|
onDoubleClick?: () => void
|
|
|
|
|
onMouseEnter?: () => void
|
|
|
|
|
onMouseLeave?: () => void
|
|
|
|
|
tabIndex?: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const CardItem = forwardRef<HTMLDivElement, CardItemProps>((
|
|
|
|
|
{
|
|
|
|
|
isSubForm,
|
|
|
|
|
row,
|
|
|
|
|
gridDto,
|
|
|
|
|
listFormCode,
|
|
|
|
|
dataSource,
|
|
|
|
|
refreshData,
|
|
|
|
|
getCachedLookupDataSource,
|
|
|
|
|
isSelected = false,
|
|
|
|
|
isFocused = false,
|
|
|
|
|
isHovered = false,
|
|
|
|
|
onSelectionChange,
|
|
|
|
|
onClick,
|
|
|
|
|
onDoubleClick,
|
|
|
|
|
onMouseEnter,
|
|
|
|
|
onMouseLeave,
|
|
|
|
|
tabIndex = -1,
|
|
|
|
|
},
|
|
|
|
|
ref
|
|
|
|
|
) => {
|
|
|
|
|
// Selection mode'u gridDto'dan al
|
|
|
|
|
const selectionMode = useMemo(() => {
|
|
|
|
|
if (!gridDto?.gridOptions?.selectionDto?.mode) return 'none'
|
|
|
|
|
const mode = gridDto.gridOptions.selectionDto.mode.toLowerCase()
|
|
|
|
|
if (mode === 'single') return 'single'
|
|
|
|
|
if (mode === 'multiple') return 'multiple'
|
|
|
|
|
return 'none'
|
|
|
|
|
}, [gridDto])
|
2025-10-22 14:58:27 +00:00
|
|
|
const [formData, setFormData] = useState(row)
|
2025-11-28 13:31:53 +00:00
|
|
|
const refForm = useRef<FormRef>(null)
|
2025-10-22 14:58:27 +00:00
|
|
|
const navigate = useNavigate()
|
|
|
|
|
const { translate } = useLocalization()
|
|
|
|
|
const { checkPermission } = usePermission()
|
|
|
|
|
const isPwaMode = usePWA()
|
|
|
|
|
|
|
|
|
|
const keyField = gridDto.gridOptions.keyFieldName
|
|
|
|
|
const rowId = row[keyField!]
|
|
|
|
|
|
|
|
|
|
// Form Items
|
|
|
|
|
const formItems: GroupItem[] = useMemo(() => {
|
|
|
|
|
if (!gridDto) return []
|
|
|
|
|
|
|
|
|
|
return gridDto.gridOptions.editingFormDto
|
|
|
|
|
?.sort((a, b) => (a.order >= b.order ? 1 : -1))
|
|
|
|
|
.map((e) => {
|
|
|
|
|
return {
|
|
|
|
|
itemType: e.itemType,
|
|
|
|
|
colCount: e.colCount,
|
|
|
|
|
colSpan: e.colSpan,
|
|
|
|
|
caption: e.caption,
|
|
|
|
|
items: e.items
|
|
|
|
|
?.sort((a, b) => (a.order >= b.order ? 1 : -1))
|
|
|
|
|
.map((i: EditingFormItemDto) => {
|
|
|
|
|
let editorOptions = {}
|
|
|
|
|
const colData = gridDto.columnFormats.find((x) => x.fieldName === i.dataField)
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
editorOptions = i.editorOptions && JSON.parse(i.editorOptions)
|
|
|
|
|
} catch {}
|
|
|
|
|
|
|
|
|
|
const item: SimpleItemWithColData = {
|
|
|
|
|
canRead: colData?.canRead ?? false,
|
|
|
|
|
canUpdate: colData?.canUpdate ?? false,
|
|
|
|
|
canCreate: colData?.canCreate ?? false,
|
|
|
|
|
canExport: colData?.canExport ?? false,
|
|
|
|
|
dataField: i.dataField,
|
|
|
|
|
name: i.dataField,
|
|
|
|
|
editorType2: i.editorType2,
|
|
|
|
|
editorType:
|
|
|
|
|
i.editorType2 == PlatformEditorTypes.dxGridBox ? 'dxDropDownBox' : i.editorType2,
|
|
|
|
|
colSpan: i.colSpan,
|
|
|
|
|
isRequired: i.isRequired,
|
|
|
|
|
editorOptions: {
|
|
|
|
|
...editorOptions,
|
|
|
|
|
...(colData?.lookupDto?.dataSourceType
|
|
|
|
|
? {
|
|
|
|
|
dataSource: getCachedLookupDataSource(colData?.editorOptions, colData),
|
|
|
|
|
valueExpr: colData?.lookupDto?.valueExpr?.toLowerCase(),
|
|
|
|
|
displayExpr: colData?.lookupDto?.displayExpr?.toLowerCase(),
|
|
|
|
|
}
|
|
|
|
|
: {}),
|
|
|
|
|
},
|
|
|
|
|
colData,
|
|
|
|
|
tagBoxOptions: i.tagBoxOptions,
|
|
|
|
|
gridBoxOptions: i.gridBoxOptions,
|
|
|
|
|
editorScript: i.editorScript,
|
|
|
|
|
}
|
|
|
|
|
if (i.dataField.indexOf(':') >= 0) {
|
|
|
|
|
item.label = { text: captionize(i.dataField.split(':')[1]) }
|
|
|
|
|
}
|
|
|
|
|
item.editorOptions = {
|
|
|
|
|
...item.editorOptions,
|
|
|
|
|
readOnly: true,
|
|
|
|
|
}
|
|
|
|
|
return item
|
|
|
|
|
}),
|
|
|
|
|
} as GroupItem
|
|
|
|
|
})
|
|
|
|
|
}, [gridDto, getCachedLookupDataSource])
|
|
|
|
|
|
|
|
|
|
const permissionResults: PermissionResults = {
|
|
|
|
|
c:
|
|
|
|
|
gridDto?.gridOptions.editingOptionDto.allowAdding === true &&
|
|
|
|
|
checkPermission(gridDto?.gridOptions.permissionDto.c),
|
|
|
|
|
r: checkPermission(gridDto?.gridOptions.permissionDto.r),
|
|
|
|
|
u:
|
|
|
|
|
gridDto?.gridOptions.editingOptionDto.allowUpdating === true &&
|
|
|
|
|
checkPermission(gridDto?.gridOptions.permissionDto.u),
|
|
|
|
|
d:
|
|
|
|
|
gridDto?.gridOptions.editingOptionDto.allowDeleting === true &&
|
|
|
|
|
checkPermission(gridDto?.gridOptions.permissionDto.d),
|
|
|
|
|
e: checkPermission(gridDto?.gridOptions.permissionDto.e),
|
|
|
|
|
i: checkPermission(gridDto?.gridOptions.permissionDto.i),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const onActionEdit = () => {
|
|
|
|
|
navigate(
|
|
|
|
|
ROUTES_ENUM.protected.admin.formEdit
|
|
|
|
|
.replace(':listFormCode', listFormCode)
|
|
|
|
|
.replace(':id', rowId!),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const onActionNew = () => {
|
|
|
|
|
navigate(ROUTES_ENUM.protected.admin.formNew.replace(':listFormCode', listFormCode))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
2025-11-30 16:56:36 +00:00
|
|
|
<div
|
|
|
|
|
ref={ref}
|
|
|
|
|
tabIndex={tabIndex}
|
|
|
|
|
onClick={onClick}
|
|
|
|
|
onDoubleClick={onDoubleClick}
|
|
|
|
|
onMouseEnter={onMouseEnter}
|
|
|
|
|
onMouseLeave={onMouseLeave}
|
|
|
|
|
className={classNames(
|
|
|
|
|
"bg-white dark:bg-neutral-800 border flex flex-col transition-all duration-200 outline-none",
|
|
|
|
|
{
|
|
|
|
|
"border-blue-500 shadow-lg ring-2 ring-blue-300": isSelected,
|
|
|
|
|
"border-blue-400 shadow-md": isFocused && !isSelected,
|
|
|
|
|
"border-gray-300 dark:border-neutral-600": !isSelected && !isFocused,
|
|
|
|
|
"shadow-md transform scale-[1.02]": isHovered,
|
|
|
|
|
"cursor-pointer": selectionMode !== 'none',
|
|
|
|
|
}
|
|
|
|
|
)}
|
|
|
|
|
role="article"
|
|
|
|
|
aria-selected={isSelected}
|
|
|
|
|
aria-label={`Card ${rowId}`}
|
|
|
|
|
>
|
2025-10-22 14:58:27 +00:00
|
|
|
<div
|
2025-11-30 16:56:36 +00:00
|
|
|
className={`flex items-center pt-2 px-2 gap-2 ${isSubForm ? 'justify-between' : 'justify-between'}`}
|
2025-10-22 14:58:27 +00:00
|
|
|
>
|
2025-11-30 16:56:36 +00:00
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
{selectionMode !== 'none' && onSelectionChange && (
|
|
|
|
|
<div onClick={(e) => e.stopPropagation()}>
|
|
|
|
|
<Checkbox
|
|
|
|
|
checked={isSelected}
|
|
|
|
|
onChange={(checked: boolean) => {
|
|
|
|
|
onSelectionChange(checked)
|
|
|
|
|
}}
|
|
|
|
|
className="cursor-pointer"
|
|
|
|
|
aria-label="Select card"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{!isSubForm && (
|
|
|
|
|
<h3>{translate('::' + gridDto?.gridOptions.title)}</h3>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-10-22 14:58:27 +00:00
|
|
|
{permissionResults && (
|
|
|
|
|
<FormButtons
|
|
|
|
|
isSubForm={true}
|
|
|
|
|
mode="view"
|
|
|
|
|
listFormCode={listFormCode}
|
|
|
|
|
id={rowId}
|
|
|
|
|
gridDto={gridDto}
|
|
|
|
|
commandColumnData={{ buttons: [] }}
|
|
|
|
|
dataSource={dataSource}
|
|
|
|
|
permissions={permissionResults}
|
|
|
|
|
handleSubmit={() => ({})}
|
|
|
|
|
refreshData={refreshData}
|
|
|
|
|
getSelectedRowKeys={() => [rowId]}
|
|
|
|
|
getSelectedRowsData={() => [row]}
|
|
|
|
|
getFilter={() => null}
|
|
|
|
|
onActionEdit={onActionEdit}
|
|
|
|
|
onActionNew={onActionNew}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex-grow">
|
|
|
|
|
<FormDevExpress
|
2025-11-11 11:50:54 +00:00
|
|
|
listFormCode={listFormCode}
|
2025-10-22 14:58:27 +00:00
|
|
|
mode="view"
|
|
|
|
|
refForm={refForm}
|
|
|
|
|
formData={formData}
|
|
|
|
|
formItems={formItems}
|
|
|
|
|
setFormData={setFormData}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
2025-11-30 16:56:36 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
CardItem.displayName = 'CardItem'
|
2025-10-22 14:58:27 +00:00
|
|
|
|
|
|
|
|
export default CardItem
|