Skip to content

Commit 20ca824

Browse files
committed
add library layout
1 parent ad52ff7 commit 20ca824

File tree

10 files changed

+205
-12
lines changed

10 files changed

+205
-12
lines changed
Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,40 @@
11
import { notFound } from 'next/navigation';
2+
import { match, P } from 'ts-pattern';
23
import snakeCase from 'lodash/snakeCase';
34

45
import { getEntityByExtendedType } from '@/entity-configuration/domain/helpers';
5-
import { Browse } from '@/features/views/listing/browse';
6+
import { BrowseLibraryScope } from '@/features/views/listing/browse-library';
7+
import { BrowseStandardScope } from '@/features/views/listing/browse-scope';
8+
import { WorkspaceScope } from '@/constants';
69
import { KebabCase } from '@/utils/type';
710

811
import type { TExtendedEntitiesTypeDict } from '@/api/entitycore/types/extended-entity-type';
912
import type { ServerSideComponentProp, WorkspaceContext } from '@/types/common';
13+
import type { TWorkspaceScope } from '@/constants';
1014

1115
export default async function Page({
1216
params,
17+
searchParams,
1318
}: ServerSideComponentProp<
1419
WorkspaceContext & { type: KebabCase<TExtendedEntitiesTypeDict> },
15-
null
20+
{ scope: TWorkspaceScope | null }
1621
>) {
1722
const { type } = await params;
23+
const { scope } = await searchParams;
1824

1925
const entity = getEntityByExtendedType({ type: snakeCase(type) as TExtendedEntitiesTypeDict });
2026

21-
if (!entity) return notFound();
27+
const content = match({ scope, entity })
28+
.with({ entity: P.nullish }, () => notFound())
29+
.with(
30+
{
31+
scope: P.union(P.nullish, WorkspaceScope.Public, WorkspaceScope.Project),
32+
entity: P.not(P.nullish),
33+
},
34+
() => <BrowseStandardScope />
35+
)
36+
.with({ scope: WorkspaceScope.Bookmarks }, () => <BrowseLibraryScope />)
37+
.otherwise(() => notFound());
2238

23-
return <Browse />;
39+
return content;
2440
}

src/features/views/listing/browse.tsx renamed to src/features/views/listing/browse-library.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import {
3333
useSelectEntityClickEvent,
3434
} from '@/ui/segments/mini-detail-view/event';
3535

36-
export function Browse() {
36+
export function BrowseLibraryScope() {
3737
const searchParams = useSearchParams();
3838
const scope = searchParams.get('scope') as TWorkspaceScope;
3939
const { type } = useParams<WorkspaceContext & { type: KebabCase<TExtendedEntitiesTypeDict> }>();
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
'use client';
2+
3+
import { useParams, useSearchParams } from 'next/navigation';
4+
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
5+
import { useState } from 'react';
6+
7+
import snakeCase from 'lodash/snakeCase';
8+
import compact from 'lodash/compact';
9+
import get from 'lodash/get';
10+
11+
import { useDataTableColumns } from '@/ui/segments/data-table/elements/use-data-table-columns';
12+
import { useQueryExtendedEntityType } from '@/ui/hooks/use-query-extended-entity-type';
13+
import {
14+
coreActiveColumnsAtom,
15+
corePageNumberAtom,
16+
coreSortStateAtom,
17+
} from '@/ui/segments/data-table/elements/context';
18+
import { getEntityByExtendedType } from '@/entity-configuration/domain/helpers';
19+
import { MiniDetailView } from '@/ui/segments/mini-detail-view';
20+
import { useWorkspace } from '@/ui/hooks/use-workspace';
21+
import { MainTable } from '@/ui/segments/data-table';
22+
import { DEFAULT_PAGE_NUMBER } from '@/constants';
23+
import { cn } from '@/utils/css-class';
24+
25+
import type { TExtendedEntitiesTypeDict } from '@/api/entitycore/types/extended-entity-type';
26+
import type { EntityCoreIdentifiableNamed } from '@/api/entitycore/types/shared/global';
27+
import type { EntityCoreResponse } from '@/api/entitycore/types/shared/response';
28+
import type { WorkspaceContext } from '@/types/common';
29+
import type { TWorkspaceScope } from '@/constants';
30+
import type { KebabCase } from '@/utils/type';
31+
import {
32+
makeSelectEntityClickEvent,
33+
useSelectEntityClickEvent,
34+
} from '@/ui/segments/mini-detail-view/event';
35+
36+
export function BrowseStandardScope() {
37+
const searchParams = useSearchParams();
38+
const scope = searchParams.get('scope') as TWorkspaceScope;
39+
const { type } = useParams<WorkspaceContext & { type: KebabCase<TExtendedEntitiesTypeDict> }>();
40+
const { virtualLabId, projectId } = useWorkspace();
41+
const dataKey = compact([virtualLabId, projectId, type, scope]).join('/');
42+
const dataType = snakeCase(type) as TExtendedEntitiesTypeDict;
43+
44+
const entity = getEntityByExtendedType({ type: dataType });
45+
const setPageNumber = useSetAtom(corePageNumberAtom(dataKey));
46+
const [sortState, setSortState] = useAtom(coreSortStateAtom({ key: dataKey }));
47+
const [miniViewPresent, updateDisplayMiniView] = useState(false);
48+
49+
const onSortChange = (newSortState: any) => {
50+
setPageNumber(DEFAULT_PAGE_NUMBER);
51+
setSortState(newSortState);
52+
};
53+
54+
const allColumns = useDataTableColumns<EntityCoreIdentifiableNamed>({
55+
dataType,
56+
sortState,
57+
setSortState: onSortChange,
58+
});
59+
60+
const activeColumns = useAtomValue(coreActiveColumnsAtom({ dataType, key: dataKey }));
61+
const columns = allColumns.filter(({ key }) => (activeColumns || []).includes(key as string));
62+
63+
const { data, error, isPlaceholderData, isFetching, isLoading } = useQueryExtendedEntityType({
64+
context: {
65+
key: dataKey,
66+
workspaceScope: scope,
67+
extendedEntityType: snakeCase(type) as TExtendedEntitiesTypeDict,
68+
},
69+
workspace: { virtualLabId, projectId },
70+
queryFn: async ({ queryKey }) => {
71+
const [{ workspace, queryParameters }] = queryKey;
72+
return entity?.api?.query.list?.({
73+
withFacets: true,
74+
filters: { ...queryParameters },
75+
context: workspace,
76+
});
77+
},
78+
enabled: ({ queryKey }) => {
79+
const [{ queryParameters }] = queryKey;
80+
if (!get(queryParameters, 'within_brain_region_brain_region_id', null)) return false;
81+
return true;
82+
},
83+
});
84+
85+
const dataSource = (data as EntityCoreResponse<EntityCoreIdentifiableNamed>)?.data;
86+
const facets = (data as EntityCoreResponse<EntityCoreIdentifiableNamed>)?.facets;
87+
const pagination = (data as EntityCoreResponse<EntityCoreIdentifiableNamed>)?.pagination;
88+
89+
const onCellClick = (_: string, record: EntityCoreIdentifiableNamed) => {
90+
// navigate(
91+
// `${V2_MIGRATION_TEMPORARY_BASE_PATH}/${virtualLabId}/${projectId}/explore/view/${kebabCase(record.type)}/${record.id}`
92+
// );
93+
makeSelectEntityClickEvent({
94+
display: true,
95+
data: record,
96+
});
97+
};
98+
99+
useSelectEntityClickEvent((event) => {
100+
updateDisplayMiniView(event.detail.display);
101+
});
102+
103+
if (error)
104+
return (
105+
<div>
106+
<pre>{JSON.stringify(error, null, 2)}</pre>
107+
</div>
108+
);
109+
110+
return (
111+
<>
112+
<div
113+
id="explore-body-container"
114+
data-testid="explore-body-container"
115+
className="h-full max-h-[calc(100vh-11.8rem)] min-h-0 w-full min-w-0 overflow-hidden rounded-2xl [grid-area:body]"
116+
>
117+
<div id="main-listing-table-container" className={cn('h-full w-full')}>
118+
<MainTable<EntityCoreIdentifiableNamed>
119+
controlsVisible
120+
showLoadingState
121+
sticky={{ offsetHeader: 75.5 }}
122+
isLoading={(isPlaceholderData && isFetching) || isLoading}
123+
dataScope={scope}
124+
dataSource={dataSource ?? []}
125+
dataType={dataType}
126+
workspace={{ virtualLabId, projectId }}
127+
dataKey={dataKey}
128+
columns={columns}
129+
facets={facets}
130+
onCellClick={onCellClick}
131+
resultPagination={{
132+
pagination,
133+
totalData: dataSource?.length,
134+
}}
135+
cls={{
136+
table: cn(
137+
'[&_.ant-table]:bg-neutral-1! [&_.ant-table-header_th]:bg-neutral-1!',
138+
'[&_.ant-table-placeholder]:bg-neutral-1! [&_.ant-table-tbody_tr.ant-table-placeholder]:bg-neutral-1!'
139+
),
140+
}}
141+
/>
142+
</div>
143+
</div>
144+
<div
145+
id="mini-detail-view-container"
146+
className={cn(
147+
'h-full max-h-[calc(100vh-11.8rem)] w-full min-w-0',
148+
'[grid-area:mini-view]',
149+
{
150+
hidden: !miniViewPresent,
151+
}
152+
)}
153+
>
154+
<MiniDetailView />
155+
</div>
156+
</>
157+
);
158+
}

src/ui/hooks/use-query-extended-entity-type.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import {
1313
} from '@/features/brain-region-hierarchy/context';
1414
import { transformFiltersToQuery } from '@/api/entitycore/transformers';
1515
import { compactRecord } from '@/utils/dictionary';
16-
import { pageNumberAtom } from '@/state/explore-section/list-view-atoms';
1716
import {
1817
coreFiltersAtom,
18+
corePageNumberAtom,
1919
coreSearchStringAtom,
2020
coreSortStateAtom,
2121
} from '@/ui/segments/data-table/elements/context';
@@ -52,7 +52,7 @@ function useQueryParameters({ context }: { context: QueryContext }) {
5252
const selectedBrainRegin = useAtomValue(selectedBrainRegionAtom);
5353
const sortState = useAtomValue(coreSortStateAtom({ key: context.key }));
5454
const searchString = useAtomValue(coreSearchStringAtom(context.key));
55-
const pageNumber = useAtomValue(pageNumberAtom(context.key));
55+
const pageNumber = useAtomValue(corePageNumberAtom(context.key));
5656
const filters = useAtomValue(
5757
coreFiltersAtom({ dataType: context.extendedEntityType, key: context.key })
5858
);

src/ui/segments/data-table/elements/pagination.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Pagination as AntPagination, type PaginationProps } from 'antd';
22
import { useAtom } from 'jotai';
33
import type { ComponentProps } from 'react';
44

5-
import { pageNumberAtom } from '@/state/explore-section/list-view-atoms';
5+
import { corePageNumberAtom } from '@/ui/segments/data-table/elements/context';
66
import { DEFAULT_PAGE_SIZE } from '@/constants';
77
import { cn } from '@/utils/css-class';
88

@@ -19,7 +19,7 @@ type Props = {
1919
};
2020

2121
export function Pagination({ dataKey, size, resultPagination, className }: Props) {
22-
const [page, updatePage] = useAtom(pageNumberAtom(dataKey));
22+
const [page, updatePage] = useAtom(corePageNumberAtom(dataKey));
2323

2424
return (
2525
<AntPagination

src/ui/segments/data-table/search.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export function Search({ dataKey, className }: SearchProps) {
5151

5252
const handleClearSearch = (): void => {
5353
setSearchInput('');
54+
setPageNumber(DEFAULT_PAGE_NUMBER);
5455
searchInputRef.current?.focus();
5556
};
5657

src/ui/segments/explore/default-content.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,35 @@
11
'use client';
22

3+
import { parseAsString, useQueryState, type Parser } from 'nuqs';
34
import { useState, type ReactNode } from 'react';
5+
import { match, P } from 'ts-pattern';
46

57
import { useSelectEntityClickEvent } from '@/ui/segments/mini-detail-view/event';
6-
import { ExploreMenu } from '@/ui/segments/explore/left-menu';
8+
import { ExploreMenu } from '@/ui/segments/explore/scope-left-menu';
9+
import { LibraryLeftMenu } from '@/ui/segments/explore/library-left-menu';
710
import { Card } from '@/ui/molecules/card';
811
import { cn } from '@/utils/css-class';
912

13+
import type { TWorkspaceScope } from '@/constants';
14+
1015
type Props = { dataKey: string; children: ReactNode };
1116

1217
export function DefaultContent({ children, dataKey }: Props) {
1318
const [miniViewPresent, setMiniViewPresent] = useState(false);
19+
const [scope] = useQueryState(
20+
'scope',
21+
parseAsString.withOptions({ clearOnDefault: false, shallow: true }) as Parser<TWorkspaceScope>
22+
);
23+
1424
useSelectEntityClickEvent((ev) => {
1525
setMiniViewPresent(ev.detail.display);
1626
});
1727

28+
const menu = match(scope)
29+
.with(P.union('project', 'public'), () => <ExploreMenu dataKey={dataKey} />)
30+
.with('bookmarks', () => <LibraryLeftMenu />)
31+
.otherwise(() => null);
32+
1833
return (
1934
<>
2035
<div
@@ -26,7 +41,7 @@ export function DefaultContent({ children, dataKey }: Props) {
2641
)}
2742
>
2843
<Card borderless className="h-full w-full gap-0 bg-white py-0 shadow-lg">
29-
<ExploreMenu dataKey={dataKey} />
44+
{menu}
3045
</Card>
3146
</div>
3247
{children}

src/ui/segments/explore/header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ function ExploreTabs() {
3838
// @ts-ignore
3939
tabsConfig: tabsConfigItems,
4040
tabKey: 'scope',
41-
shallow: true,
41+
shallow: false,
4242
clearOnDefault: false,
4343
defaultKey: 'public',
4444
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function LibraryLeftMenu() {
2+
return <div>LibraryLeftMenu</div>;
3+
}
File renamed without changes.

0 commit comments

Comments
 (0)