Improve RSForm UI and add Modal dialog

This commit is contained in:
IRBorisov 2023-07-22 12:24:14 +03:00
parent 8861ec5378
commit c9e56e7146
9 changed files with 132 additions and 11 deletions

View File

@ -8,13 +8,15 @@ interface ButtonProps {
disabled?: boolean disabled?: boolean
dense?: boolean dense?: boolean
loading?: boolean loading?: boolean
widthClass?: string
borderClass?: string borderClass?: string
colorClass?: string
onClick?: MouseEventHandler<HTMLButtonElement> | undefined onClick?: MouseEventHandler<HTMLButtonElement> | undefined
} }
function Button({id, text, icon, tooltip, function Button({id, text, icon, tooltip,
dense, disabled, dense, disabled,
borderClass='border rounded', borderClass='border rounded', colorClass='clr-btn-default', widthClass='w-fit h-fit',
loading, onClick, loading, onClick,
...props ...props
}: ButtonProps) { }: ButtonProps) {
@ -26,9 +28,7 @@ function Button({id, text, icon, tooltip,
disabled={disabled} disabled={disabled}
onClick={onClick} onClick={onClick}
title={tooltip} title={tooltip}
className={padding + ' ' + borderClass + ' ' + className={`inline-flex items-center gap-2 align-middle justify-center ${padding} ${borderClass} ${colorClass} ${widthClass} ${cursor}`}
'inline-flex items-center gap-2 align-middle justify-center w-fit h-fit clr-btn-default ' + cursor
}
{...props} {...props}
> >
{icon && <span>{icon}</span>} {icon && <span>{icon}</span>}

View File

@ -6,7 +6,7 @@ interface CardProps {
function Card({title, widthClass='min-w-fit', children}: CardProps) { function Card({title, widthClass='min-w-fit', children}: CardProps) {
return ( return (
<div className={`border shadow-md py-2 bg-gray-50 dark:bg-gray-600 px-6 ${widthClass}`}> <div className={`border shadow-md py-2 clr-card px-6 ${widthClass}`}>
{ title && <h1 className='mb-2 text-xl font-bold'>{title}</h1> } { title && <h1 className='mb-2 text-xl font-bold'>{title}</h1> }
{children} {children}
</div> </div>

View File

@ -0,0 +1,56 @@
import { useRef } from 'react'
import Button from './Button'
import useClickedOutside from '../../hooks/useClickedOutside'
interface ModalProps {
title?: string
submitText?: string
show: boolean
canSubmit: boolean
toggle: () => void
onSubmit: () => void
onCancel?: () => void
children: React.ReactNode
}
function Modal({title, show, toggle, onSubmit, onCancel, canSubmit, children, submitText='Продолжить'}: ModalProps) {
const ref = useRef(null);
useClickedOutside({ref: ref, callback: toggle})
if (!show) {
return null;
}
const handleCancel = () => {
toggle();
if(onCancel) onCancel();
};
return (
<>
<div className='fixed top-0 left-0 w-full h-full clr-modal opacity-50 z-50'>
</div>
<div ref={ref} className='fixed bottom-1/2 left-1/2 -translate-y-1/2 -translate-x-1/2 px-6 py-4 flex flex-col w-fit z-[60] clr-card border shadow-md'>
{ title && <h1 className='mb-4 text-xl font-bold'>{title}</h1> }
<div className='py-2'>
{children}
</div>
<div className='pt-4 mt-2 border-t-4 flex justify-between w-full'>
<Button
text={submitText}
widthClass='min-w-[6rem] w-fit h-fit'
colorClass='clr-btn-primary'
disabled={!canSubmit}
onClick={onSubmit}
/>
<Button
text='Отмена'
onClick={handleCancel}
/>
</div>
</div>
</>
);
}
export default Modal;

View File

@ -35,6 +35,10 @@
@apply bg-gray-100 dark:bg-gray-600 @apply bg-gray-100 dark:bg-gray-600
} }
.clr-modal {
@apply bg-gray-300 dark:bg-gray-800
}
.clr-nav { .clr-nav {
@apply border-gray-400 dark:border-gray-300 bg-white dark:bg-gray-700 @apply border-gray-400 dark:border-gray-300 bg-white dark:bg-gray-700
} }
@ -43,6 +47,10 @@
@apply text-gray-600 bg-white border-gray-400 dark:bg-gray-700 dark:border-gray-300 dark:text-gray-300 @apply text-gray-600 bg-white border-gray-400 dark:bg-gray-700 dark:border-gray-300 dark:text-gray-300
} }
.clr-card {
@apply bg-gray-50 dark:bg-gray-600
}
.clr-tab { .clr-tab {
@apply text-gray-600 dark:text-zinc-200 hover:bg-gray-300 dark:hover:bg-gray-400 @apply text-gray-600 dark:text-zinc-200 hover:bg-gray-300 dark:hover:bg-gray-400
} }

View File

@ -129,6 +129,10 @@ function ConstituentsSideList({expression}: ConstituentsSideListProps) {
columns={columns} columns={columns}
keyField='id' keyField='id'
noContextMenu noContextMenu
noDataComponent={<span className='p-2 flex flex-col justify-center text-center'>
<p>Список конституент пуст</p>
<p>Измените параметры фильтра</p>
</span>}
striped striped
highlightOnHover highlightOnHover

View File

@ -7,6 +7,7 @@ import { ArrowDownIcon, ArrowUpIcon, ArrowsRotateIcon, DumpBinIcon, SmallPlusIco
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import Divider from '../../components/Common/Divider'; import Divider from '../../components/Common/Divider';
import { getCstTypeLabel, getCstTypePrefix, getStatusInfo, getTypeLabel } from '../../utils/staticUI'; import { getCstTypeLabel, getCstTypePrefix, getStatusInfo, getTypeLabel } from '../../utils/staticUI';
import CreateCstModal from './CreateCstModal';
interface ConstituentsTableProps { interface ConstituentsTableProps {
onOpenEdit: (cst: IConstituenta) => void onOpenEdit: (cst: IConstituenta) => void
@ -17,6 +18,8 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
const [selectedRows, setSelectedRows] = useState<IConstituenta[]>([]); const [selectedRows, setSelectedRows] = useState<IConstituenta[]>([]);
const nothingSelected = useMemo(() => selectedRows.length === 0, [selectedRows]); const nothingSelected = useMemo(() => selectedRows.length === 0, [selectedRows]);
const [showCstModal, setShowCstModal] = useState(true);
const handleRowSelected = useCallback( const handleRowSelected = useCallback(
({selectedRows} : SelectionInfo<IConstituenta>) => { ({selectedRows} : SelectionInfo<IConstituenta>) => {
setSelectedRows(selectedRows); setSelectedRows(selectedRows);
@ -46,7 +49,11 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
}, []); }, []);
const handleAddNew = useCallback((cstType?: CstType) => { const handleAddNew = useCallback((cstType?: CstType) => {
if (!cstType) {
setShowCstModal(true);
} else {
toast.info(`Новая конституента ${cstType || 'NEW'}`); toast.info(`Новая конституента ${cstType || 'NEW'}`);
}
}, []); }, []);
const columns = useMemo(() => const columns = useMemo(() =>
@ -161,9 +168,14 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
], [] ], []
); );
return ( return (<>
<CreateCstModal
show={showCstModal}
toggle={() => setShowCstModal(!showCstModal)}
onCreate={handleAddNew}
/>
<div className='w-full'> <div className='w-full'>
<div className='flex justify-start w-full gap-1 px-2 py-1 border-y items-center h-[2.2rem]'> <div className='sticky top-[4rem] z-10 flex justify-start w-full gap-1 px-2 py-1 border-y items-center h-[2.2rem] clr-app'>
<div className='mr-3 whitespace-nowrap'>Выбраны <span className='ml-2'><b>{selectedRows.length}</b> из {schema?.stats?.count_all || 0}</span></div> <div className='mr-3 whitespace-nowrap'>Выбраны <span className='ml-2'><b>{selectedRows.length}</b> из {schema?.stats?.count_all || 0}</span></div>
{isEditable && <div className='flex justify-start w-full gap-1'> {isEditable && <div className='flex justify-start w-full gap-1'>
<Button <Button
@ -216,6 +228,10 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
data={schema!.items!} data={schema!.items!}
columns={columns} columns={columns}
keyField='id' keyField='id'
noDataComponent={<span className='p-2 flex flex-col justify-center text-center'>
<p>Список пуст</p>
<p>Создайте новую конституенту</p>
</span>}
striped striped
highlightOnHover highlightOnHover
@ -230,7 +246,7 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
dense dense
/> />
</div> </div>
); </>);
} }
export default ConstituentsTable; export default ConstituentsTable;

View File

@ -0,0 +1,33 @@
import { toast } from 'react-toastify';
import Modal from '../../components/Common/Modal';
import { CstType } from '../../utils/models';
import { useState } from 'react';
interface CreateCstModalProps {
show: boolean
toggle: () => void
onCreate: (type: CstType) => void
}
function CreateCstModal({show, toggle, onCreate}: CreateCstModalProps) {
const [validated, setValidated] = useState(false);
const handleSubmit = () => {
toast.info('Создание конституент');
};
return (
<Modal
title='Создание конституенты'
show={show}
toggle={toggle}
canSubmit={validated}
onSubmit={handleSubmit}
>
<p>Выбор типа конституенты</p>
<p>Добавить после выбранной или в конец</p>
</Modal>
)
}
export default CreateCstModal;

View File

@ -10,8 +10,6 @@ import { CrownIcon, DownloadIcon, DumpBinIcon, SaveIcon, ShareIcon } from '../..
import { useUsers } from '../../context/UsersContext'; import { useUsers } from '../../context/UsersContext';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import fileDownload from 'js-file-download';
import { AxiosResponse } from 'axios';
import { useAuth } from '../../context/AuthContext'; import { useAuth } from '../../context/AuthContext';
import { claimOwnershipProc, deleteRSFormProc, downloadRSFormProc, shareCurrentURLProc } from '../../utils/procedures'; import { claimOwnershipProc, deleteRSFormProc, downloadRSFormProc, shareCurrentURLProc } from '../../utils/procedures';

View File

@ -67,6 +67,12 @@ function RSFormsTable({schemas}: RSFormsTableProps) {
striped striped
highlightOnHover highlightOnHover
pointerOnHover pointerOnHover
noDataComponent={<span className='p-2 flex flex-col justify-center text-center'>
<p>Список схем пуст</p>
<p>Измените фильтр</p>
</span>}
pagination pagination
paginationPerPage={50} paginationPerPage={50}
paginationRowsPerPageOptions={[10, 20, 30, 50, 100]} paginationRowsPerPageOptions={[10, 20, 30, 50, 100]}