From 95ac72e06de1afbf99c2854e3d7cf78d3d420775 Mon Sep 17 00:00:00 2001 From: Norbert Csaba Herczeg Date: Fri, 8 Nov 2024 20:18:24 +0100 Subject: [PATCH] save state --- .../components/widgets/CardsContainer.tsx.hbs | 180 +++++++----------- .../components/widgets/CardsFilter.tsx.hbs | 70 +++++++ .../components/widgets/DefaultCard.tsx.hbs | 64 +++++++ .../src/components/widgets/index.tsx.hbs | 2 + .../src/main/resources/ui-react.yaml | 8 + 5 files changed, 217 insertions(+), 107 deletions(-) create mode 100644 judo-ui-react/src/main/resources/actor/src/components/widgets/CardsFilter.tsx.hbs create mode 100644 judo-ui-react/src/main/resources/actor/src/components/widgets/DefaultCard.tsx.hbs diff --git a/judo-ui-react/src/main/resources/actor/src/components/widgets/CardsContainer.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/components/widgets/CardsContainer.tsx.hbs index 56de04ff..83382fff 100644 --- a/judo-ui-react/src/main/resources/actor/src/components/widgets/CardsContainer.tsx.hbs +++ b/judo-ui-react/src/main/resources/actor/src/components/widgets/CardsContainer.tsx.hbs @@ -1,30 +1,27 @@ {{> fragment.header.hbs }} -import { useState, useMemo, useCallback, useEffect, type ChangeEvent } from 'react'; import Box from '@mui/material/Box'; -import Button from '@mui/material/Button'; import Grid from '@mui/material/Grid'; -import Card from '@mui/material/Card'; -import CardContent from '@mui/material/CardContent'; -import CardActions from '@mui/material/CardActions'; -import Pagination from '@mui/material/Pagination'; -import Typography from '@mui/material/Typography'; +import TablePagination from '@mui/material/TablePagination'; import type { GridColDef, GridEventListener, - GridSortModel, GridFilterModel, - GridValidRowModel, GridRowModel, + GridSortModel, + GridValidRowModel, } from '@mui/x-data-grid{{ getMUIDataGridPlanSuffix }}'; -import { useL10N } from '~/l10n/l10n-context'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import type { FiltersSerializer } from '~/utilities'; -import { mapAllFiltersToQueryCustomizerProperties, processQueryCustomizer, useErrorHandler } from '~/utilities'; -import { basePageSizeOptions, baseTableConfig, filterDebounceMs } from '~/config'; import type { Filter, FilterOption } from '~/components-api'; -import { X_JUDO_COUNT } from '~/services/data-api/rest/headers'; +import { basePageSizeOptions } from '~/config'; +import { useL10N } from '~/l10n/l10n-context'; import type { QueryCustomizer } from '~/services/data-api/common/QueryCustomizer'; +import { X_JUDO_COUNT } from '~/services/data-api/rest/headers'; +import type { FiltersSerializer } from '~/utilities'; +import { mapAllFiltersToQueryCustomizerProperties, processQueryCustomizer, useErrorHandler } from '~/utilities'; +import { CardsFilter, type CardsFilterDefinition } from './CardsFilter'; +import { DefaultCard } from './DefaultCard'; export interface CardsContainerProps { uniqueId: string; @@ -41,9 +38,10 @@ export interface CardsContainerProps { relationName: string; filtersSerializer: FiltersSerializer; showTotalCount?: boolean; + layout?: 'horizontal' | 'vertical'; } -export const CardsContainer = (props: CardsContainerProps) => { +export const CardsContainer = (props: CardsContainerProps) => { const { uniqueId, defaultSortParams, @@ -59,6 +57,7 @@ export const CardsContainer = (props: CardsContain relationName, filtersSerializer, showTotalCount, + layout = 'horizontal' } = props; const { locale: l10nLocale } = useL10N(); @@ -122,50 +121,6 @@ export const CardsContainer = (props: CardsContain }); }; - function handleSortModelChange(newModel: GridSortModel) { - setPage(0); - setSortModel(newModel); - - const _orderBy = newModel - .filter((m: any) => m.sort) - .map((m: any) => ({ - attribute: m.field, - descending: m.sort === 'desc', - })); - - setQueryCustomizer((prevQueryCustomizer) => { - const strippedQueryCustomizer: QueryCustomizer = { - ...prevQueryCustomizer, - }; - if (!!strippedQueryCustomizer._seek) { - delete strippedQueryCustomizer._seek.lastItem; - } - // we need to reset _seek so that previous configuration is erased - return { - ...strippedQueryCustomizer, - _orderBy, - _seek: { - limit: rowsPerPage + 1, - }, - }; - }); - } - - async function handlePageChange(isNext: boolean) { - setQueryCustomizer((prevQueryCustomizer) => { - return { - ...prevQueryCustomizer, - _seek: { - limit: isNext ? rowsPerPage + 1 : rowsPerPage, - reverse: !isNext, - lastItem: isNext ? lastItem : firstItem, - }, - }; - }); - - setIsNextButtonEnabled(!isNext); - } - async function fetching() { setIsInternalLoading(true); @@ -176,9 +131,7 @@ export const CardsContainer = (props: CardsContain }; const { data: res, headers } = await fetch!(processedQueryCustomizer); - // if (typeof showTotalCount === 'boolean' ? showTotalCount : true) { - setTotalCount(headers[X_JUDO_COUNT] ? Number(headers[X_JUDO_COUNT]) : -1); - // } + setTotalCount(headers[X_JUDO_COUNT] ? Number(headers[X_JUDO_COUNT]) : -1); if (res.length > rowsPerPage) { setIsNextButtonEnabled(true); @@ -206,53 +159,66 @@ export const CardsContainer = (props: CardsContain fetchData(); }, [queryCustomizer, refreshCounter]); - const onPageChange = useCallback((event: ChangeEvent, page: number) => { - alert(page); - }, []); + async function handlePageChange(isNext: boolean) { + setQueryCustomizer((prevQueryCustomizer) => { + return { + ...prevQueryCustomizer, + _seek: { + limit: isNext ? rowsPerPage + 1 : rowsPerPage, + reverse: !isNext, + lastItem: isNext ? lastItem : firstItem, + }, + }; + }); + + setIsNextButtonEnabled(!isNext); + } - const formatValue = useCallback((value: any) => { - if (value instanceof Date) { - return new Intl.DateTimeFormat(l10nLocale, { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - hour12: false - }).format(value); - } - if (value === undefined || value === null) { - return ''; - } - return value.toString(); - }, []); + const filterDefs: CardsFilterDefinition[] = [ + { type: 'boolean', field: 'nakedEye', label: 'Naked Eye', values: [{ value: true, label: 'Yes' }, { value: false, label: 'No' }] }, + { type: 'string', field: 'constellation', label: 'Constellation', values: [{ value: 'Andromeda', label: 'Andromeda' }, { value: 'Corvus', label: 'Corvus' }, { value: 'Ursa Major', label: 'Ursa Major' }] }, + ]; - return - ID: {uniqueId} - - Filters - - - {data.map(d => ( - - - {columns.map((k, idx) => ( - - {formatValue(d[k.field])} - - ))} - - - - - - ))} + return ( + + + + + + + + {data.map((d) => ( + + ))} + + + + { + let isNext = true; + if (newPage < page) { + isNext = false; + } + setPage(newPage); + handlePageChange(isNext); + } } + rowsPerPage={rowsPerPage} + rowsPerPageOptions={pageSizeOptions} + labelDisplayedRows={({ from, to }) => + `${from}–${to}${totalCount !== undefined && totalCount > -1 ? ` / ${totalCount}` : ''}` + } + nextIconButtonProps={ { + disabled: !isNextButtonEnabled, + } } + backIconButtonProps={ { + disabled: page === 0, + } } + /> - - - - - ; + + ); }; diff --git a/judo-ui-react/src/main/resources/actor/src/components/widgets/CardsFilter.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/components/widgets/CardsFilter.tsx.hbs new file mode 100644 index 00000000..b9569352 --- /dev/null +++ b/judo-ui-react/src/main/resources/actor/src/components/widgets/CardsFilter.tsx.hbs @@ -0,0 +1,70 @@ +{{> fragment.header.hbs }} + +import { type FC, useState, useCallback } from 'react'; +import Grid from '@mui/material/Grid'; +import Typography from '@mui/material/Typography'; +import Button from '@mui/material/Button'; +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; +import { useL10N } from '~/l10n/l10n-context'; +import { useTranslation } from 'react-i18next'; + +export interface CardsFilterDefinition { + type: 'boolean' | 'string'; + field: string; + label: string; + values: { value: any, label: string }[]; +} + +export const CardsFilter: FC<{ filterDefinitions: CardsFilterDefinition[], onFiltersChanged?: (values: Record) => void }> = ({ filterDefinitions, onFiltersChanged }) => { + const { locale: l10nLocale } = useL10N(); + const { t } = useTranslation(); + + const [values, setValues] = useState>({}); + + const updateValue = useCallback((field: string, value: any) => { + const newValues = { + ...values, + [field]: values[field] === value ? null : value, + }; + setValues(newValues); + onFiltersChanged?.(newValues); + }, [values]); + + const clearFilters = useCallback(() => { + setValues({}); + onFiltersChanged?.({}); + }, [values]); + + return ( + + + + Filters + + + + {filterDefinitions.map(d => ( + + + {d.label}: + + + {d.values.map(v => ( + updateValue(d.field, v.value)} + />} + label={v.label} + /> + ))} + + + ))} + + ); +}; diff --git a/judo-ui-react/src/main/resources/actor/src/components/widgets/DefaultCard.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/components/widgets/DefaultCard.tsx.hbs new file mode 100644 index 00000000..b0e756bb --- /dev/null +++ b/judo-ui-react/src/main/resources/actor/src/components/widgets/DefaultCard.tsx.hbs @@ -0,0 +1,64 @@ +{{> fragment.header.hbs }} + +import { type FC, useCallback } from 'react'; +import Grid from '@mui/material/Grid'; +import Card from '@mui/material/Card'; +import CardContent from '@mui/material/CardContent'; +import Typography from '@mui/material/Typography'; +import CardActions from '@mui/material/CardActions'; +import Button from '@mui/material/Button'; +import type { + GridColDef, + GridEventListener, + GridValidRowModel, +} from '@mui/x-data-grid{{ getMUIDataGridPlanSuffix }}'; +import { useL10N } from '~/l10n/l10n-context'; +import { useTranslation } from 'react-i18next'; + +export interface CardProps { + row: T; + columns: GridColDef[]; + onRowClick?: (row: T) => void; +} + +export const DefaultCard: FC> = ({ onRowClick, row, columns }) => { + const { locale: l10nLocale } = useL10N(); + const { t } = useTranslation(); + + const formatValue = useCallback((value: any) => { + if (value instanceof Date) { + return new Intl.DateTimeFormat(l10nLocale, { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }).format(value); + } + if (value === undefined || value === null) { + return ''; + } + return value.toString(); + }, []); + + return ( + + + + {columns.map((k, idx) => ( + + {formatValue(row[k.field])} + + ))} + + + + + + + ); +}; diff --git a/judo-ui-react/src/main/resources/actor/src/components/widgets/index.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/components/widgets/index.tsx.hbs index ba4acb95..5cb9cc6a 100644 --- a/judo-ui-react/src/main/resources/actor/src/components/widgets/index.tsx.hbs +++ b/judo-ui-react/src/main/resources/actor/src/components/widgets/index.tsx.hbs @@ -4,7 +4,9 @@ export * from './SingleRelationInput'; export * from './Tags'; export * from './AssociationButton'; export * from './BinaryInput'; +export * from './DefaultCard'; export * from './CardsContainer'; +export * from './CardsFilter'; export * from './NumericInput'; export * from './TextWithTypeAhead'; export * from './TrinaryLogicCombobox'; diff --git a/judo-ui-react/src/main/resources/ui-react.yaml b/judo-ui-react/src/main/resources/ui-react.yaml index a97fe1b7..31cb939b 100644 --- a/judo-ui-react/src/main/resources/ui-react.yaml +++ b/judo-ui-react/src/main/resources/ui-react.yaml @@ -497,6 +497,14 @@ templates: pathExpression: "'src/components/widgets/CardsContainer.tsx'" templateName: actor/src/components/widgets/CardsContainer.tsx.hbs + - name: actor/src/components/widgets/CardsFilter.tsx + pathExpression: "'src/components/widgets/CardsFilter.tsx'" + templateName: actor/src/components/widgets/CardsFilter.tsx.hbs + + - name: actor/src/components/widgets/DefaultCard.tsx + pathExpression: "'src/components/widgets/DefaultCard.tsx'" + templateName: actor/src/components/widgets/DefaultCard.tsx.hbs + # Actor - src - components-api - name: actor/src/components-api/components/Action.ts