mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Improve RSForm UI and add Modal dialog
This commit is contained in:
parent
8861ec5378
commit
c9e56e7146
|
@ -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>}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
56
rsconcept/frontend/src/components/Common/Modal.tsx
Normal file
56
rsconcept/frontend/src/components/Common/Modal.tsx
Normal 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;
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) => {
|
||||||
toast.info(`Новая конституента ${cstType || 'NEW'}`);
|
if (!cstType) {
|
||||||
|
setShowCstModal(true);
|
||||||
|
} else {
|
||||||
|
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;
|
||||||
|
|
33
rsconcept/frontend/src/pages/RSFormPage/CreateCstModal.tsx
Normal file
33
rsconcept/frontend/src/pages/RSFormPage/CreateCstModal.tsx
Normal 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;
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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]}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user