fix: fixed formgenerator layout and design
This commit is contained in:
parent
2b96ab7b66
commit
54ba020c45
4 changed files with 316 additions and 111 deletions
|
|
@ -24,6 +24,8 @@
|
|||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
flex-wrap: wrap;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.activeFiltersCount {
|
||||
|
|
@ -72,7 +74,7 @@
|
|||
|
||||
.floatingLabelInput {
|
||||
position: relative;
|
||||
width: 250px;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.label {
|
||||
|
|
@ -280,3 +282,167 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* Pagination Controls */
|
||||
.paginationControls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.pageSizeSelector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.pageSizeSelector label {
|
||||
white-space: nowrap;
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
.pageSizeSelect {
|
||||
height: 32px;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--color-primary);
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
font-family: var(--font-family);
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.pageSizeSelect:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.paginationButton {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: var(--color-secondary);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
font-family: var(--font-family);
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.paginationButton:hover:not(:disabled) {
|
||||
background: var(--color-secondary-hover);
|
||||
}
|
||||
|
||||
.paginationButton:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.paginationInfo {
|
||||
font-size: 14px;
|
||||
color: var(--color-text);
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Page numbers container */
|
||||
.pageNumbers {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
max-width: 40vw;
|
||||
max-height: 120px;
|
||||
overflow-y: auto;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
/* Individual page number button */
|
||||
.pageNumber {
|
||||
min-width: 28px;
|
||||
height: 28px;
|
||||
padding: 0 6px;
|
||||
border: 1px solid var(--color-border, #ddd);
|
||||
background: var(--color-bg, #fff);
|
||||
color: var(--color-text);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-family: var(--font-family);
|
||||
font-size: 12px;
|
||||
transition: all 0.15s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pageNumber:hover:not(:disabled) {
|
||||
background: var(--color-secondary);
|
||||
color: white;
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.pageNumber:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Active/current page number */
|
||||
.pageNumberActive {
|
||||
background: var(--color-secondary);
|
||||
color: white;
|
||||
border-color: var(--color-secondary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Ellipsis indicator */
|
||||
.pageEllipsis {
|
||||
padding: 0 8px;
|
||||
color: var(--color-text-secondary, #666);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Responsive Design for Pagination */
|
||||
@media (max-width: 768px) {
|
||||
.paginationControls {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pageSizeSelector {
|
||||
order: -1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.paginationInfo {
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.pageNumbers {
|
||||
max-width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pageNumber {
|
||||
min-width: 24px;
|
||||
height: 24px;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import React from 'react';
|
||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||
import styles from './FormGeneratorControls.module.css';
|
||||
import { Button } from '../../UiComponents/Button';
|
||||
|
|
@ -49,6 +50,18 @@ export interface FormGeneratorControlsProps {
|
|||
|
||||
// Active filters count for display
|
||||
activeFiltersCount?: number;
|
||||
|
||||
// Pagination props
|
||||
pagination?: boolean;
|
||||
currentPage?: number;
|
||||
totalPages?: number;
|
||||
currentPageSize?: number;
|
||||
pageSizeOptions?: number[];
|
||||
showPageSizeSelector?: boolean;
|
||||
onPageChange?: (page: number) => void;
|
||||
onPageSizeChange?: (pageSize: number) => void;
|
||||
supportsBackendPagination?: boolean;
|
||||
hookData?: any;
|
||||
}
|
||||
|
||||
export function FormGeneratorControls({
|
||||
|
|
@ -64,7 +77,17 @@ export function FormGeneratorControls({
|
|||
searchable = true,
|
||||
selectable = true,
|
||||
loading = false,
|
||||
activeFiltersCount = 0
|
||||
activeFiltersCount = 0,
|
||||
pagination = false,
|
||||
currentPage = 1,
|
||||
totalPages = 1,
|
||||
currentPageSize = 10,
|
||||
pageSizeOptions = [10, 25, 50, 100, 500],
|
||||
showPageSizeSelector = true,
|
||||
onPageChange,
|
||||
onPageSizeChange,
|
||||
supportsBackendPagination = false,
|
||||
hookData
|
||||
}: FormGeneratorControlsProps) {
|
||||
const { t } = useLanguage();
|
||||
|
||||
|
|
@ -101,7 +124,7 @@ export function FormGeneratorControls({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Search Controls - Hide when items are selected */}
|
||||
{/* Search Controls with Pagination - Hide when items are selected */}
|
||||
{searchable && selectedCount === 0 && (
|
||||
<div className={styles.searchContainer}>
|
||||
<div className={styles.floatingLabelInput}>
|
||||
|
|
@ -133,6 +156,109 @@ export function FormGeneratorControls({
|
|||
<span className={styles.refreshIcon}><IoIosRefresh /></span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Pagination Controls */}
|
||||
{pagination && supportsBackendPagination && onPageChange && (
|
||||
<div className={styles.paginationControls}>
|
||||
{showPageSizeSelector && onPageSizeChange && (
|
||||
<div className={styles.pageSizeSelector}>
|
||||
<label htmlFor="pageSize">{t('formgen.pagination.pageSize', 'Items per page:')}</label>
|
||||
<select
|
||||
id="pageSize"
|
||||
value={currentPageSize}
|
||||
onChange={(e) => onPageSizeChange(Number(e.target.value))}
|
||||
className={styles.pageSizeSelect}
|
||||
>
|
||||
{pageSizeOptions.map(size => (
|
||||
<option key={size} value={size}>{size}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={() => onPageChange(1)}
|
||||
disabled={currentPage === 1}
|
||||
className={styles.paginationButton}
|
||||
title={t('formgen.pagination.first')}
|
||||
>
|
||||
««
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onPageChange(currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
className={styles.paginationButton}
|
||||
title={t('formgen.pagination.prev')}
|
||||
>
|
||||
«
|
||||
</button>
|
||||
|
||||
{/* Page number buttons - show up to 100 pages before and after current */}
|
||||
<div className={styles.pageNumbers}>
|
||||
{(() => {
|
||||
const maxPagesVisible = 100; // Max pages to show on each side
|
||||
const startPage = Math.max(1, currentPage - maxPagesVisible);
|
||||
const endPage = Math.min(totalPages, currentPage + maxPagesVisible);
|
||||
const pages: React.ReactNode[] = [];
|
||||
|
||||
// Show ellipsis at start if we're not showing page 1
|
||||
if (startPage > 1) {
|
||||
pages.push(
|
||||
<span key="start-ellipsis" className={styles.pageEllipsis}>...</span>
|
||||
);
|
||||
}
|
||||
|
||||
// Generate page buttons
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
pages.push(
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => onPageChange(i)}
|
||||
disabled={i === currentPage}
|
||||
className={`${styles.pageNumber} ${i === currentPage ? styles.pageNumberActive : ''}`}
|
||||
title={`${t('formgen.pagination.page', 'Page')} ${i}`}
|
||||
>
|
||||
{i}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// Show ellipsis at end if we're not showing last page
|
||||
if (endPage < totalPages) {
|
||||
pages.push(
|
||||
<span key="end-ellipsis" className={styles.pageEllipsis}>...</span>
|
||||
);
|
||||
}
|
||||
|
||||
return pages;
|
||||
})()}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => onPageChange(currentPage + 1)}
|
||||
disabled={currentPage >= totalPages}
|
||||
className={styles.paginationButton}
|
||||
title={t('formgen.pagination.next')}
|
||||
>
|
||||
»
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onPageChange(totalPages)}
|
||||
disabled={currentPage >= totalPages}
|
||||
className={styles.paginationButton}
|
||||
title={t('formgen.pagination.last')}
|
||||
>
|
||||
»»
|
||||
</button>
|
||||
|
||||
{/* Total items count */}
|
||||
<span className={styles.paginationInfo}>
|
||||
({hookData?.pagination?.totalItems != null
|
||||
? hookData.pagination.totalItems.toString()
|
||||
: (loading ? '...' : displayData.length.toString())} {t('formgen.pagination.items', 'items')})
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@
|
|||
font-size: 14px;
|
||||
background: var(--color-bg);
|
||||
table-layout: fixed;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Disabled user row styling */
|
||||
|
|
@ -104,9 +105,13 @@
|
|||
text-align: left;
|
||||
font-weight: 400;
|
||||
color: var(--color-text);
|
||||
white-space: nowrap;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
user-select: none;
|
||||
z-index: 10;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.th.actionsColumn {
|
||||
|
|
@ -286,6 +291,11 @@
|
|||
border-top: 1px solid var(--color-primary);
|
||||
color: var(--color-text);
|
||||
vertical-align: middle;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
white-space: normal;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1150,116 +1150,19 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
|||
selectable={selectable}
|
||||
loading={loading}
|
||||
activeFiltersCount={activeFiltersCount}
|
||||
pagination={pagination}
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
currentPageSize={currentPageSize}
|
||||
pageSizeOptions={pageSizeOptions}
|
||||
showPageSizeSelector={showPageSizeSelector}
|
||||
onPageChange={setCurrentPage}
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
supportsBackendPagination={supportsBackendPagination}
|
||||
hookData={hookData}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Pagination - Above Table */}
|
||||
{pagination && (
|
||||
<div className={styles.pagination}>
|
||||
{showPageSizeSelector && (
|
||||
<div className={styles.pageSizeSelector}>
|
||||
<label htmlFor="pageSize">{t('formgen.pagination.pageSize', 'Items per page:')}</label>
|
||||
<select
|
||||
id="pageSize"
|
||||
value={currentPageSize}
|
||||
onChange={(e) => handlePageSizeChange(Number(e.target.value))}
|
||||
className={styles.pageSizeSelect}
|
||||
>
|
||||
{pageSizeOptions.map(size => (
|
||||
<option key={size} value={size}>{size}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{pagination && supportsBackendPagination && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => setCurrentPage(1)}
|
||||
disabled={currentPage === 1}
|
||||
className={styles.paginationButton}
|
||||
title={t('formgen.pagination.first')}
|
||||
>
|
||||
««
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentPage(currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
className={styles.paginationButton}
|
||||
title={t('formgen.pagination.prev')}
|
||||
>
|
||||
«
|
||||
</button>
|
||||
|
||||
{/* Page number buttons - show up to 100 pages before and after current */}
|
||||
<div className={styles.pageNumbers}>
|
||||
{(() => {
|
||||
const maxPagesVisible = 100; // Max pages to show on each side
|
||||
const startPage = Math.max(1, currentPage - maxPagesVisible);
|
||||
const endPage = Math.min(totalPages, currentPage + maxPagesVisible);
|
||||
const pages: React.ReactNode[] = [];
|
||||
|
||||
// Show ellipsis at start if we're not showing page 1
|
||||
if (startPage > 1) {
|
||||
pages.push(
|
||||
<span key="start-ellipsis" className={styles.pageEllipsis}>...</span>
|
||||
);
|
||||
}
|
||||
|
||||
// Generate page buttons
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
pages.push(
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCurrentPage(i)}
|
||||
disabled={i === currentPage}
|
||||
className={`${styles.pageNumber} ${i === currentPage ? styles.pageNumberActive : ''}`}
|
||||
title={`${t('formgen.pagination.page', 'Page')} ${i}`}
|
||||
>
|
||||
{i}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// Show ellipsis at end if we're not showing last page
|
||||
if (endPage < totalPages) {
|
||||
pages.push(
|
||||
<span key="end-ellipsis" className={styles.pageEllipsis}>...</span>
|
||||
);
|
||||
}
|
||||
|
||||
return pages;
|
||||
})()}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setCurrentPage(currentPage + 1)}
|
||||
disabled={currentPage >= totalPages}
|
||||
className={styles.paginationButton}
|
||||
title={t('formgen.pagination.next')}
|
||||
>
|
||||
»
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentPage(totalPages)}
|
||||
disabled={currentPage >= totalPages}
|
||||
className={styles.paginationButton}
|
||||
title={t('formgen.pagination.last')}
|
||||
>
|
||||
»»
|
||||
</button>
|
||||
|
||||
{/* Total items count */}
|
||||
<span className={styles.paginationInfo}>
|
||||
({hookData?.pagination?.totalItems != null
|
||||
? hookData.pagination.totalItems.toString()
|
||||
: (loading ? '...' : displayData.length.toString())} {t('formgen.pagination.items', 'items')})
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Table */}
|
||||
<div className={`${styles.tableContainer} ${displayData.length === 0 && !loading ? styles.emptyTable : ''}`}>
|
||||
{/* Loading overlay - shown while loading */}
|
||||
|
|
|
|||
Loading…
Reference in a new issue