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;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.activeFiltersCount {
|
.activeFiltersCount {
|
||||||
|
|
@ -72,7 +74,7 @@
|
||||||
|
|
||||||
.floatingLabelInput {
|
.floatingLabelInput {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 250px;
|
width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.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 { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
import styles from './FormGeneratorControls.module.css';
|
import styles from './FormGeneratorControls.module.css';
|
||||||
import { Button } from '../../UiComponents/Button';
|
import { Button } from '../../UiComponents/Button';
|
||||||
|
|
@ -49,6 +50,18 @@ export interface FormGeneratorControlsProps {
|
||||||
|
|
||||||
// Active filters count for display
|
// Active filters count for display
|
||||||
activeFiltersCount?: number;
|
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({
|
export function FormGeneratorControls({
|
||||||
|
|
@ -64,7 +77,17 @@ export function FormGeneratorControls({
|
||||||
searchable = true,
|
searchable = true,
|
||||||
selectable = true,
|
selectable = true,
|
||||||
loading = false,
|
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) {
|
}: FormGeneratorControlsProps) {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
|
|
||||||
|
|
@ -101,7 +124,7 @@ export function FormGeneratorControls({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Search Controls - Hide when items are selected */}
|
{/* Search Controls with Pagination - Hide when items are selected */}
|
||||||
{searchable && selectedCount === 0 && (
|
{searchable && selectedCount === 0 && (
|
||||||
<div className={styles.searchContainer}>
|
<div className={styles.searchContainer}>
|
||||||
<div className={styles.floatingLabelInput}>
|
<div className={styles.floatingLabelInput}>
|
||||||
|
|
@ -133,6 +156,109 @@ export function FormGeneratorControls({
|
||||||
<span className={styles.refreshIcon}><IoIosRefresh /></span>
|
<span className={styles.refreshIcon}><IoIosRefresh /></span>
|
||||||
</button>
|
</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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
background: var(--color-bg);
|
background: var(--color-bg);
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Disabled user row styling */
|
/* Disabled user row styling */
|
||||||
|
|
@ -104,9 +105,13 @@
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: var(--color-text);
|
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;
|
user-select: none;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.th.actionsColumn {
|
.th.actionsColumn {
|
||||||
|
|
@ -286,6 +291,11 @@
|
||||||
border-top: 1px solid var(--color-primary);
|
border-top: 1px solid var(--color-primary);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
vertical-align: middle;
|
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}
|
selectable={selectable}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
activeFiltersCount={activeFiltersCount}
|
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 */}
|
{/* Table */}
|
||||||
<div className={`${styles.tableContainer} ${displayData.length === 0 && !loading ? styles.emptyTable : ''}`}>
|
<div className={`${styles.tableContainer} ${displayData.length === 0 && !loading ? styles.emptyTable : ''}`}>
|
||||||
{/* Loading overlay - shown while loading */}
|
{/* Loading overlay - shown while loading */}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue