diff --git a/src/sections/collection/Collection.tsx b/src/sections/collection/Collection.tsx index 3f13382b1..e8b554350 100644 --- a/src/sections/collection/Collection.tsx +++ b/src/sections/collection/Collection.tsx @@ -1,17 +1,17 @@ -import { Trans, useTranslation } from 'react-i18next' -import { Alert, Col, Row } from '@iqss/dataverse-design-system' +import { Col, Row } from '@iqss/dataverse-design-system' import { CollectionRepository } from '../../collection/domain/repositories/CollectionRepository' import { useCollection } from './useCollection' import { useScrollTop } from '../../shared/hooks/useScrollTop' import { useSession } from '../session/SessionContext' import { useGetCollectionUserPermissions } from '../../shared/hooks/useGetCollectionUserPermissions' -import { type UseCollectionQueryParamsReturnType } from './useCollectionQueryParams' +import { type UseCollectionQueryParamsReturnType } from './useGetCollectionQueryParams' import { BreadcrumbsGenerator } from '../shared/hierarchy/BreadcrumbsGenerator' import AddDataActionsButton from '../shared/add-data-actions/AddDataActionsButton' import { CollectionItemsPanel } from './collection-items-panel/CollectionItemsPanel' import { CollectionInfo } from './CollectionInfo' import { CollectionSkeleton } from './CollectionSkeleton' import { PageNotFound } from '../page-not-found/PageNotFound' +import { CreatedAlert } from './CreatedAlert' interface CollectionProps { collectionRepository: CollectionRepository @@ -41,8 +41,6 @@ export function Collection({ const showAddDataActions = Boolean(user && (canUserAddCollection || canUserAddDataset)) - const { t } = useTranslation('collection') - if (!isLoading && !collection) { return } @@ -56,23 +54,7 @@ export function Collection({ <> - {created && ( - - - ) - }} - /> - - )} + {created && } )} () const location = useLocation() const state = location.state as { created: boolean } | undefined diff --git a/src/sections/collection/CreatedAlert.tsx b/src/sections/collection/CreatedAlert.tsx new file mode 100644 index 000000000..6e33f6904 --- /dev/null +++ b/src/sections/collection/CreatedAlert.tsx @@ -0,0 +1,23 @@ +import { Alert } from '@iqss/dataverse-design-system' +import { Trans, useTranslation } from 'react-i18next' + +export const CreatedAlert = () => { + const { t } = useTranslation('collection') + return ( + + + ) + }} + /> + + ) +} diff --git a/src/sections/collection/collection-items-panel/CollectionItemsPanel.tsx b/src/sections/collection/collection-items-panel/CollectionItemsPanel.tsx index 840ad6a5a..120b830a2 100644 --- a/src/sections/collection/collection-items-panel/CollectionItemsPanel.tsx +++ b/src/sections/collection/collection-items-panel/CollectionItemsPanel.tsx @@ -1,16 +1,20 @@ import { useEffect, useRef, useState } from 'react' +import { useSearchParams } from 'react-router-dom' import { CollectionRepository } from '../../../collection/domain/repositories/CollectionRepository' import { CollectionItemsPaginationInfo } from '../../../collection/domain/models/CollectionItemsPaginationInfo' import { CollectionSearchCriteria } from '../../../collection/domain/models/CollectionSearchCriteria' import { useGetAccumulatedItems } from './useGetAccumulatedItems' -import { UseCollectionQueryParamsReturnType } from '../useCollectionQueryParams' +import { UseCollectionQueryParamsReturnType } from '../useGetCollectionQueryParams' import { useLoading } from '../../loading/LoadingContext' import { FilterPanel } from './filter-panel/FilterPanel' import { ItemsList } from './items-list/ItemsList' import { SearchPanel } from './search-panel/SearchPanel' +import { QueryParamKey } from '../../Route.enum' +import { CollectionItemType } from '../../../collection/domain/models/CollectionItemType' +import { ItemTypeChange } from './filter-panel/type-filters/TypeFilters' import styles from './CollectionItemsPanel.module.scss' -interface ItemsPanelProps { +interface CollectionItemsPanelProps { collectionId: string collectionRepository: CollectionRepository collectionQueryParams: UseCollectionQueryParamsReturnType @@ -22,19 +26,20 @@ export const CollectionItemsPanel = ({ collectionRepository, collectionQueryParams, addDataSlot -}: ItemsPanelProps) => { +}: CollectionItemsPanelProps) => { const { setIsLoading } = useLoading() + const [_, setSearchParams] = useSearchParams() - const initialSearchCriteria = new CollectionSearchCriteria( + // This object will update every time we update a query param in the URL with the setSearchParams setter + const currentSearchCriteria = new CollectionSearchCriteria( collectionQueryParams.searchQuery, - collectionQueryParams.typesQuery + collectionQueryParams.typesQuery || [CollectionItemType.COLLECTION, CollectionItemType.DATASET] ) - console.log({ initialSearchCriteria }) - - const [searchCriteria, setSearchCriteria] = - useState(initialSearchCriteria) - + /* + TODO:ME For now I think we really shouldnt care about setting an inital page based on the page query param + Items in specific page of a list could change while browsing at different times so is not so useful for url sharing. + */ const [paginationInfo, setPaginationInfo] = useState( new CollectionItemsPaginationInfo() ) @@ -60,8 +65,8 @@ export const CollectionItemsPanel = ({ if (totalAvailable !== undefined) { paginationInfoToSend = currentPagination.goToNextPage() } - console.log('paginationInfoToSend', paginationInfoToSend) - const totalItemsCount = await loadMore(paginationInfoToSend, searchCriteria) + + const totalItemsCount = await loadMore(paginationInfoToSend, currentSearchCriteria) if (totalItemsCount !== undefined) { const paginationInfoUpdated = paginationInfoToSend.withTotal(totalItemsCount) @@ -69,16 +74,77 @@ export const CollectionItemsPanel = ({ } } - // This function is called when the user changes the search criteria (search input, filters, etc.) - const handleCriteriaChange = async (newCriteria: CollectionSearchCriteria) => { + const handleSearchSubmit = async (searchValue: string) => { itemsListContainerRef.current?.scrollTo({ top: 0 }) - setSearchCriteria(newCriteria) + const resetedPaginationInfo = new CollectionItemsPaginationInfo() + setPaginationInfo(resetedPaginationInfo) + + if (searchValue === '') { + // Update the URL without the search value, keep other querys + setSearchParams((currentSearchParams) => { + currentSearchParams.delete(QueryParamKey.QUERY) + return currentSearchParams + }) + } else { + // Update the URL with the search value ,keep other querys and include all item types always + setSearchParams((currentSearchParams) => ({ + ...currentSearchParams, + [QueryParamKey.COLLECTION_ITEM_TYPES]: [ + CollectionItemType.COLLECTION, + CollectionItemType.DATASET, + CollectionItemType.FILE + ].join(','), + [QueryParamKey.QUERY]: searchValue + })) + } + + // WHEN SEARCHING, WE RESET THE PAGINATION INFO AND KEEP ALL ITEM TYPES!! + const newCollectionSearchCriteria = new CollectionSearchCriteria( + searchValue === '' ? undefined : searchValue, + [CollectionItemType.COLLECTION, CollectionItemType.DATASET, CollectionItemType.FILE] + ) + + const totalItemsCount = await loadMore(resetedPaginationInfo, newCollectionSearchCriteria, true) + + if (totalItemsCount !== undefined) { + const paginationInfoUpdated = resetedPaginationInfo.withTotal(totalItemsCount) + setPaginationInfo(paginationInfoUpdated) + } + } + + // WHEN APPLYING FILTERS, WE RESET THE PAGINATION INFO AND IF SEARCH VALUE EXISTS, WE KEEP IT!! + const handleItemsTypeChange = async (itemTypeChange: ItemTypeChange) => { + console.log({ itemTypeChange }) + const { type, checked } = itemTypeChange + + const newItemsTypes = checked + ? [...new Set([...(currentSearchCriteria?.itemTypes ?? []), type])] + : (currentSearchCriteria.itemTypes ?? []).filter((itemType) => itemType !== type) + + console.log({ newItemsTypes }) + // KEEP SEARCH VALUE IF EXISTS + itemsListContainerRef.current?.scrollTo({ top: 0 }) const resetedPaginationInfo = new CollectionItemsPaginationInfo() setPaginationInfo(resetedPaginationInfo) - const totalItemsCount = await loadMore(resetedPaginationInfo, newCriteria, true) + // Update the URL with the new item types, keep other querys and include the search value if exists + setSearchParams((currentSearchParams) => ({ + ...currentSearchParams, + [QueryParamKey.COLLECTION_ITEM_TYPES]: newItemsTypes.join(','), + ...(currentSearchCriteria.searchText && { + [QueryParamKey.QUERY]: currentSearchCriteria.searchText + }) + })) + + const newCollectionSearchCriteria = new CollectionSearchCriteria( + currentSearchCriteria.searchText, + newItemsTypes + ) + + const totalItemsCount = await loadMore(resetedPaginationInfo, newCollectionSearchCriteria, true) + if (totalItemsCount !== undefined) { const paginationInfoUpdated = resetedPaginationInfo.withTotal(totalItemsCount) setPaginationInfo(paginationInfoUpdated) @@ -92,12 +158,20 @@ export const CollectionItemsPanel = ({ return (
- +
{addDataSlot}
- + { +interface FilterPanelProps { + currentItemTypes?: CollectionItemType[] + onItemTypesChange: (itemTypeChange: ItemTypeChange) => void + isLoadingCollectionItems: boolean +} + +export const FilterPanel = ({ + currentItemTypes, + onItemTypesChange, + isLoadingCollectionItems +}: FilterPanelProps) => { const [showOffcanvas, setShowOffcanvas] = useState(false) const handleCloseOffcanvas = () => setShowOffcanvas(false) @@ -26,7 +37,11 @@ export const FilterPanel = () => {
- +
diff --git a/src/sections/collection/collection-items-panel/filter-panel/type-filters/TypeFilters.module.scss b/src/sections/collection/collection-items-panel/filter-panel/type-filters/TypeFilters.module.scss index 6f8131091..49b4d3a62 100644 --- a/src/sections/collection/collection-items-panel/filter-panel/type-filters/TypeFilters.module.scss +++ b/src/sections/collection/collection-items-panel/filter-panel/type-filters/TypeFilters.module.scss @@ -4,13 +4,21 @@ input[type='checkbox'] { cursor: pointer; + & + label { + color: $dv-primary-color; + cursor: pointer; + } + &:checked + label { font-weight: 500; } - } - .label-content-wrapper { - color: $dv-primary-color; - cursor: pointer; + &:disabled { + opacity: 0.7; + + & + label { + opacity: 0.7; + } + } } } diff --git a/src/sections/collection/collection-items-panel/filter-panel/type-filters/TypeFilters.tsx b/src/sections/collection/collection-items-panel/filter-panel/type-filters/TypeFilters.tsx index 54f851b30..ac6ac96d9 100644 --- a/src/sections/collection/collection-items-panel/filter-panel/type-filters/TypeFilters.tsx +++ b/src/sections/collection/collection-items-panel/filter-panel/type-filters/TypeFilters.tsx @@ -1,42 +1,83 @@ import { ChangeEvent } from 'react' import { Form, Icon, IconName, Stack } from '@iqss/dataverse-design-system' import styles from './TypeFilters.module.scss' +import { CollectionItemType } from '../../../../../collection/domain/models/CollectionItemType' + +interface TypeFiltersProps { + currentItemTypes?: CollectionItemType[] + onItemTypesChange: (itemTypeChange: ItemTypeChange) => void + isLoadingCollectionItems: boolean +} + +export interface ItemTypeChange { + type: CollectionItemType + checked: boolean +} + +export const TypeFilters = ({ + currentItemTypes, + onItemTypesChange, + isLoadingCollectionItems +}: TypeFiltersProps) => { + const handleItemTypeChange = (type: CollectionItemType, checked: boolean) => { + onItemTypesChange({ type, checked }) + } + + const collectionCheckDisabled = + isLoadingCollectionItems || + (currentItemTypes?.length === 1 && currentItemTypes?.includes(CollectionItemType.COLLECTION)) + + const datasetCheckDisabled = + isLoadingCollectionItems || + (currentItemTypes?.length === 1 && currentItemTypes?.includes(CollectionItemType.DATASET)) + + const fileCheckDisabled = + isLoadingCollectionItems || + (currentItemTypes?.length === 1 && currentItemTypes?.includes(CollectionItemType.FILE)) -export const TypeFilters = () => { return ( ) => console.log(e.target.checked)} + onChange={(e: ChangeEvent) => + handleItemTypeChange(CollectionItemType.COLLECTION, e.target.checked) + } label={ - + <> Collections (19) - + } - // checked={Boolean(value as boolean)} // TODO:ME Handle this and other filter states in a useReducer + checked={Boolean(currentItemTypes?.includes(CollectionItemType.COLLECTION))} + disabled={collectionCheckDisabled} /> ) => console.log(e.target.checked)} + onChange={(e: ChangeEvent) => + handleItemTypeChange(CollectionItemType.DATASET, e.target.checked) + } label={ - + <> Datasets (32) - + } - // checked={Boolean(value as boolean)} + checked={Boolean(currentItemTypes?.includes(CollectionItemType.DATASET))} + disabled={datasetCheckDisabled} /> ) => console.log(e.target.checked)} + onChange={(e: ChangeEvent) => + handleItemTypeChange(CollectionItemType.FILE, e.target.checked) + } label={ - + <> Files (10,081) - + } - // checked={Boolean(value as boolean)} + checked={Boolean(currentItemTypes?.includes(CollectionItemType.FILE))} + disabled={fileCheckDisabled} /> ) diff --git a/src/sections/collection/collection-items-panel/items-list/ItemsList.tsx b/src/sections/collection/collection-items-panel/items-list/ItemsList.tsx index f5772db2f..022d1acfd 100644 --- a/src/sections/collection/collection-items-panel/items-list/ItemsList.tsx +++ b/src/sections/collection/collection-items-panel/items-list/ItemsList.tsx @@ -63,6 +63,7 @@ export const ItemsList = forwardRef( {areItemsAvailable && ( <>
+ {/* TODO:ME Maybe show skeleton while loading or prevent 0 in total results somehow */} -

Assert type collection, dataset or file here

+

+ This is a : {collectionItem?.type === 'file' && 'File'} + {collectionItem?.type === 'dataset' && 'Dataset'} + {collectionItem?.type === 'collection' && 'Collection'} +

) })} diff --git a/src/sections/collection/collection-items-panel/search-panel/SearchPanel.tsx b/src/sections/collection/collection-items-panel/search-panel/SearchPanel.tsx index 5d3d8861b..3506f55f4 100644 --- a/src/sections/collection/collection-items-panel/search-panel/SearchPanel.tsx +++ b/src/sections/collection/collection-items-panel/search-panel/SearchPanel.tsx @@ -1,25 +1,29 @@ import { useState } from 'react' import { Button, Form } from '@iqss/dataverse-design-system' import { Search } from 'react-bootstrap-icons' -import { QueryParamKey, Route } from '../../../Route.enum' import styles from './SearchPanel.module.scss' -export const SearchPanel = () => { - const [searchValue, setSearchValue] = useState('') +interface SearchPanelProps { + initialSearchValue?: string + isLoadingCollectionItems: boolean + onSubmitSearch: (searchValue: string) => void +} + +export const SearchPanel = ({ + initialSearchValue = '', + isLoadingCollectionItems, + onSubmitSearch +}: SearchPanelProps) => { + const [searchValue, setSearchValue] = useState(initialSearchValue) const handleSubmit = (event: React.FormEvent) => { event.preventDefault() const trimmedSearchValue = searchValue.trim() - if (!trimmedSearchValue) return - - console.log({ trimmedSearchValue }) + const encodedSearchValue = encodeURIComponent(trimmedSearchValue) - // const encodedSearchValue = encodeURIComponent(trimmedSearchValue) - - // const searchParams = new URLSearchParams() - // searchParams.set(QueryParamKey.QUERY, encodedSearchValue) + onSubmitSearch(encodedSearchValue) } const handleSearchChange = (event: React.ChangeEvent) => { @@ -37,7 +41,12 @@ export const SearchPanel = () => { value={searchValue} onChange={handleSearchChange} /> -