@c-rex/components 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +78 -62
- package/src/article/article-action-bar.tsx +89 -0
- package/src/article/article-content.tsx +55 -0
- package/src/autocomplete.tsx +55 -50
- package/src/breadcrumb.tsx +3 -1
- package/src/directoryNodes/tree-of-content.tsx +49 -0
- package/src/{bookmark-button.tsx → favorites/bookmark-button.tsx} +12 -3
- package/src/{favorite-button.tsx → favorites/favorite-button.tsx} +1 -1
- package/src/generated/client-components.tsx +1350 -0
- package/src/generated/create-client-request.tsx +105 -0
- package/src/generated/create-server-request.tsx +61 -0
- package/src/generated/create-suggestions-request.tsx +56 -0
- package/src/generated/server-components.tsx +1056 -0
- package/src/generated/suggestions.tsx +299 -0
- package/src/info/bookmark.tsx +51 -0
- package/src/info/info-table.tsx +127 -60
- package/src/info/shared.tsx +1 -1
- package/src/navbar/language-switcher/shared.tsx +1 -1
- package/src/navbar/navbar.tsx +1 -1
- package/src/{stories → navbar/stories}/navbar.stories.tsx +1 -1
- package/src/page-wrapper.tsx +1 -1
- package/src/renditions/file-download.tsx +84 -0
- package/src/renditions/html.tsx +55 -0
- package/src/renditions/image/container.tsx +52 -0
- package/src/renditions/image/rendition.tsx +61 -0
- package/src/{dialog-filter.tsx → results/dialog-filter.tsx} +22 -23
- package/src/results/filter-navbar.tsx +241 -0
- package/src/results/filter-sidebar/index.tsx +125 -0
- package/src/results/filter-sidebar/utils.ts +164 -0
- package/src/{pagination.tsx → results/pagination.tsx} +12 -10
- package/src/results/result-container.tsx +70 -0
- package/src/{stories/blog-view.stories.tsx → results/stories/cards.stories.tsx} +1 -1
- package/src/{stories/table-view.stories.tsx → results/stories/table.stories.tsx} +1 -1
- package/src/results/table-with-images.tsx +140 -0
- package/src/{result-view → results}/table.tsx +1 -2
- package/src/results/utils.ts +67 -0
- package/src/{navbar/search-input.tsx → search-input.tsx} +9 -6
- package/src/share-button.tsx +49 -0
- package/src/stores/search-settings-store.ts +1 -1
- package/src/blur-image.tsx +0 -23
- package/src/left-sidebar.tsx +0 -90
- package/src/result-list.tsx +0 -43
- package/src/result-view/table-with-images.tsx +0 -199
- package/src/right-sidebar.tsx +0 -70
- package/src/search-modal.tsx +0 -140
- package/src/stories/blur-image.stories.tsx +0 -51
- package/src/stories/sidebar.stories.tsx +0 -94
- /package/src/{file-icon.tsx → icons/file-icon.tsx} +0 -0
- /package/src/{flag.tsx → icons/flag-icon.tsx} +0 -0
- /package/src/{loading.tsx → icons/loading.tsx} +0 -0
- /package/src/{result-view/blog.tsx → results/cards.tsx} +0 -0
- /package/src/{empty.tsx → results/empty.tsx} +0 -0
- /package/src/{stories → results/stories}/empty.stories.tsx +0 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { FC, useMemo } from "react";
|
|
4
|
+
import { useTranslations } from 'next-intl'
|
|
5
|
+
import { Check, ChevronDown } from "lucide-react"
|
|
6
|
+
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@c-rex/ui/collapsible";
|
|
7
|
+
import {
|
|
8
|
+
SidebarContent,
|
|
9
|
+
SidebarGroup,
|
|
10
|
+
SidebarGroupContent,
|
|
11
|
+
SidebarGroupLabel,
|
|
12
|
+
SidebarHeader,
|
|
13
|
+
SidebarMenu,
|
|
14
|
+
SidebarMenuSub,
|
|
15
|
+
SidebarMenuSubButton,
|
|
16
|
+
SidebarMenuSubItem
|
|
17
|
+
} from "@c-rex/ui/sidebar";
|
|
18
|
+
import { parseAsString, useQueryStates } from "nuqs";
|
|
19
|
+
import { memoizeFilteredTags, removeFilterItem, updateFilterParam } from "./utils";
|
|
20
|
+
import { FilterItem, Tags } from "@c-rex/interfaces";
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
export interface FilterSidebarProps {
|
|
24
|
+
tags?: Tags
|
|
25
|
+
totalItemCount?: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
//TODO; check layout on mobile
|
|
30
|
+
export const FilterSidebar: FC<FilterSidebarProps> = ({ tags, totalItemCount }) => {
|
|
31
|
+
const t = useTranslations();
|
|
32
|
+
const [params, setParams] = useQueryStates({
|
|
33
|
+
packages: parseAsString,
|
|
34
|
+
filter: parseAsString,
|
|
35
|
+
}, {
|
|
36
|
+
history: 'push',
|
|
37
|
+
shallow: false,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const filteredTags = useMemo(() => {
|
|
41
|
+
return memoizeFilteredTags(tags, params.filter, params.packages);
|
|
42
|
+
}, [tags, params.filter, params.packages]);
|
|
43
|
+
|
|
44
|
+
const onClickHandler = (key: string, item: FilterItem) => {
|
|
45
|
+
if (item.active) {
|
|
46
|
+
setParams(removeFilterItem(key, item, params.filter))
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setParams(updateFilterParam(key, item, params.filter))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const content = (
|
|
54
|
+
<SidebarContent className="!gap-0" suppressHydrationWarning>
|
|
55
|
+
|
|
56
|
+
{Object.entries(filteredTags).map(([key, value]) => (
|
|
57
|
+
|
|
58
|
+
<Collapsible defaultOpen key={key} className="py-0 group/collapsible">
|
|
59
|
+
<SidebarGroup>
|
|
60
|
+
|
|
61
|
+
<SidebarGroupLabel asChild className="hover:bg-sidebar-accent text-sidebar-accent-foreground text-sm font-bold">
|
|
62
|
+
<CollapsibleTrigger className="!h-9">
|
|
63
|
+
{t(`filter.tags.${key}`)}
|
|
64
|
+
<ChevronDown className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
|
|
65
|
+
</CollapsibleTrigger>
|
|
66
|
+
</SidebarGroupLabel>
|
|
67
|
+
|
|
68
|
+
<CollapsibleContent>
|
|
69
|
+
<SidebarGroupContent>
|
|
70
|
+
<SidebarMenu>
|
|
71
|
+
<SidebarMenuSub>
|
|
72
|
+
{value.map((item) => (
|
|
73
|
+
<SidebarMenuSubItem key={item.shortId}>
|
|
74
|
+
<SidebarMenuSubButton
|
|
75
|
+
className="cursor-pointer"
|
|
76
|
+
isActive={item.active}
|
|
77
|
+
onClick={() => onClickHandler(key, item)}
|
|
78
|
+
>
|
|
79
|
+
{item.label} ({item.hits}/{item.total})
|
|
80
|
+
{item.active && <Check className="ml-2" />}
|
|
81
|
+
</SidebarMenuSubButton>
|
|
82
|
+
</SidebarMenuSubItem>
|
|
83
|
+
))}
|
|
84
|
+
</SidebarMenuSub>
|
|
85
|
+
</SidebarMenu>
|
|
86
|
+
</SidebarGroupContent>
|
|
87
|
+
</CollapsibleContent>
|
|
88
|
+
</SidebarGroup>
|
|
89
|
+
</Collapsible>
|
|
90
|
+
))}
|
|
91
|
+
</SidebarContent>
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
/*
|
|
95
|
+
if (isMobile) {
|
|
96
|
+
return (
|
|
97
|
+
|
|
98
|
+
<SheetContent
|
|
99
|
+
side="left"
|
|
100
|
+
className="!pt-6 !px-2 w-[400px] overflow-y-auto"
|
|
101
|
+
>
|
|
102
|
+
<SheetHeader className="justify-center items-end font-bold">
|
|
103
|
+
{t("filter.filters")}
|
|
104
|
+
<span className="text-xs text-muted-foreground leading-5">
|
|
105
|
+
{totalItemCount} {t("results.results")}
|
|
106
|
+
</span>
|
|
107
|
+
</SheetHeader>
|
|
108
|
+
{content}
|
|
109
|
+
</SheetContent>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<div className="w-60 lg:w-80 bg-sidebar rounded-md border pb-4">
|
|
116
|
+
<SidebarHeader className="justify-center items-end font-bold">
|
|
117
|
+
{t("filter.filters")}
|
|
118
|
+
<span className="text-xs text-muted-foreground leading-5">
|
|
119
|
+
{totalItemCount} {t("results.results")}
|
|
120
|
+
</span>
|
|
121
|
+
</SidebarHeader>
|
|
122
|
+
{content}
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { FilterItem, Tags } from "@c-rex/interfaces";
|
|
2
|
+
import { EN_LANG } from "@c-rex/constants";
|
|
3
|
+
|
|
4
|
+
export const updateFilterParam = (key: string, item: FilterItem, filter: string | null): Record<string, string | null> => {
|
|
5
|
+
|
|
6
|
+
if (key === "packages") {
|
|
7
|
+
return { packages: item.shortId }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const value = `${key}.shortId=${item.shortId}`
|
|
11
|
+
let aux = value
|
|
12
|
+
|
|
13
|
+
if (filter != null) {
|
|
14
|
+
const splittedParam = filter.split(",")
|
|
15
|
+
const finalValue = [...splittedParam]
|
|
16
|
+
|
|
17
|
+
const hasParams = filter.includes(key)
|
|
18
|
+
|
|
19
|
+
if (hasParams) {
|
|
20
|
+
let mainIndex = -1
|
|
21
|
+
|
|
22
|
+
splittedParam.forEach((el, index) => {
|
|
23
|
+
if (el.includes(key)) {
|
|
24
|
+
mainIndex = index
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
finalValue[mainIndex] = value
|
|
28
|
+
} else {
|
|
29
|
+
finalValue.push(value)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
aux = finalValue.join(",")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { filter: aux }
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const removeFilterItem = (key: string, item: FilterItem, filter: string | null): Record<string, string | null> => {
|
|
39
|
+
if (key === "packages") {
|
|
40
|
+
return { packages: null }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (filter !== null) {
|
|
44
|
+
const value = `${key}.shortId=${item.shortId}`
|
|
45
|
+
const newValue = filter.split(",").filter(item => item !== value).join(",")
|
|
46
|
+
return { filter: newValue.length === 0 ? null : newValue }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const clearData = (tags: Tags): Record<string, FilterItem[]> => {
|
|
53
|
+
const contentLang = EN_LANG.toLowerCase()
|
|
54
|
+
const splittedContentLang = contentLang.split("-")[0];
|
|
55
|
+
const filteredTags: Record<string, FilterItem[]> = {}
|
|
56
|
+
|
|
57
|
+
for (const [key, value] of Object.entries(tags)) {
|
|
58
|
+
if (!value || !value.items || value.items.length === 0) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const aux = value.items.map(item => {
|
|
63
|
+
if (item?.shortId === undefined) return null
|
|
64
|
+
if (Number(item.hits) === 0) return null
|
|
65
|
+
if (item?.labels === undefined || item?.labels.length === 0) {
|
|
66
|
+
/*
|
|
67
|
+
logger.log({
|
|
68
|
+
level: "warning",
|
|
69
|
+
message: `No labels on item with id ${item.shortId} from category ${key}`
|
|
70
|
+
})
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let label = ""
|
|
77
|
+
|
|
78
|
+
for (const labelItem of item.labels) {
|
|
79
|
+
if (labelItem.language === undefined) {
|
|
80
|
+
/*
|
|
81
|
+
logger.log({
|
|
82
|
+
level: "info",
|
|
83
|
+
message: `No language on label ${labelItem.value} from category ${key}`
|
|
84
|
+
})
|
|
85
|
+
*/
|
|
86
|
+
label = labelItem.value
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (labelItem.language.toLowerCase() === contentLang || labelItem.language.toLowerCase() === splittedContentLang) {
|
|
91
|
+
label = labelItem.value
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
label = labelItem.value
|
|
96
|
+
|
|
97
|
+
// todo: fallback in english
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
hits: item.hits,
|
|
102
|
+
total: item.total,
|
|
103
|
+
label: label,
|
|
104
|
+
active: false,
|
|
105
|
+
shortId: item.shortId,
|
|
106
|
+
}
|
|
107
|
+
}).filter((item) => item !== null)
|
|
108
|
+
|
|
109
|
+
if (aux.length === 0) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
filteredTags[key] = aux;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return filteredTags;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const memoizeFilteredTags = (tags: Tags | undefined, filter: string | null, packages: string | null) => {
|
|
120
|
+
|
|
121
|
+
if (!tags) return {};
|
|
122
|
+
|
|
123
|
+
const newTags: Record<string, FilterItem[]> = clearData(tags);
|
|
124
|
+
|
|
125
|
+
if (filter !== null) {
|
|
126
|
+
const splittedParam = filter.split(",")
|
|
127
|
+
|
|
128
|
+
splittedParam.forEach((item) => {
|
|
129
|
+
const aux = item.split(".shortId=")
|
|
130
|
+
const name = aux[0] as string
|
|
131
|
+
const shortId = aux[1] as string
|
|
132
|
+
|
|
133
|
+
if (!Object.keys(newTags).includes(name)) {
|
|
134
|
+
newTags[name] = []
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
newTags[name]?.forEach((el) => {
|
|
138
|
+
if (el.shortId == shortId) {
|
|
139
|
+
el.active = true
|
|
140
|
+
} else {
|
|
141
|
+
el.active = false
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
} else {
|
|
146
|
+
Object.keys(newTags).forEach((key) => {
|
|
147
|
+
newTags[key]?.forEach((el) => {
|
|
148
|
+
el.active = false
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (packages !== null && newTags["packages"]) {
|
|
154
|
+
newTags["packages"].forEach((el) => {
|
|
155
|
+
if (el.shortId == packages) {
|
|
156
|
+
el.active = true
|
|
157
|
+
} else {
|
|
158
|
+
el.active = false
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return newTags
|
|
164
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { FC } from "react";
|
|
2
4
|
import {
|
|
3
5
|
Pagination as PaginationUI,
|
|
4
6
|
PaginationContent,
|
|
@@ -8,13 +10,13 @@ import {
|
|
|
8
10
|
PaginationPrevious,
|
|
9
11
|
} from "@c-rex/ui/pagination"
|
|
10
12
|
import { parseAsInteger, useQueryState } from "nuqs";
|
|
13
|
+
import { ResultContainerPageInfoModel } from "@c-rex/interfaces";
|
|
11
14
|
|
|
12
15
|
interface PaginationProps {
|
|
13
|
-
|
|
14
|
-
currentPage: number;
|
|
16
|
+
pageInfo: ResultContainerPageInfoModel;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
export const Pagination: FC<PaginationProps> = ({
|
|
19
|
+
export const Pagination: FC<PaginationProps> = ({ pageInfo }) => {
|
|
18
20
|
const disabledClass = "opacity-50 pointer-events-none";
|
|
19
21
|
|
|
20
22
|
const [_, setPage] = useQueryState('page',
|
|
@@ -34,16 +36,16 @@ export const Pagination: FC<PaginationProps> = ({ totalPages, currentPage }) =>
|
|
|
34
36
|
<PaginationItem>
|
|
35
37
|
<PaginationPrevious
|
|
36
38
|
href="#"
|
|
37
|
-
className={
|
|
38
|
-
onClick={() => onChangePage(
|
|
39
|
+
className={pageInfo.pageNumber === 1 ? disabledClass : ""}
|
|
40
|
+
onClick={() => onChangePage(pageInfo.pageNumber! - 1)}
|
|
39
41
|
/>
|
|
40
42
|
</PaginationItem>
|
|
41
43
|
|
|
42
|
-
{Array.from({ length:
|
|
44
|
+
{Array.from({ length: pageInfo.pageCount || 1 }, (_, index) => index + 1).map((page) => (
|
|
43
45
|
<PaginationItem key={page}>
|
|
44
46
|
<PaginationLink
|
|
45
47
|
href="#"
|
|
46
|
-
isActive={page ===
|
|
48
|
+
isActive={page === pageInfo.pageNumber}
|
|
47
49
|
onClick={() => onChangePage(page)}
|
|
48
50
|
>
|
|
49
51
|
{page}
|
|
@@ -59,8 +61,8 @@ export const Pagination: FC<PaginationProps> = ({ totalPages, currentPage }) =>
|
|
|
59
61
|
<PaginationItem>
|
|
60
62
|
<PaginationNext
|
|
61
63
|
href="#"
|
|
62
|
-
onClick={() => onChangePage(
|
|
63
|
-
className={
|
|
64
|
+
onClick={() => onChangePage(pageInfo.pageNumber! + 1)}
|
|
65
|
+
className={pageInfo.pageNumber === pageInfo.pageCount ? disabledClass : ""}
|
|
64
66
|
/>
|
|
65
67
|
</PaginationItem>
|
|
66
68
|
</PaginationContent>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import React, { FC } from "react";
|
|
2
|
+
import {
|
|
3
|
+
DocumentModel,
|
|
4
|
+
ResultContainerModel,
|
|
5
|
+
ExternalProductGraphicModel,
|
|
6
|
+
FragmentModel,
|
|
7
|
+
InformationUnitModel,
|
|
8
|
+
PackageModel,
|
|
9
|
+
TopicModel
|
|
10
|
+
} from "@c-rex/interfaces";
|
|
11
|
+
import { Empty } from "./empty";
|
|
12
|
+
|
|
13
|
+
type ResultData = ResultContainerModel<
|
|
14
|
+
DocumentModel |
|
|
15
|
+
ExternalProductGraphicModel |
|
|
16
|
+
FragmentModel |
|
|
17
|
+
InformationUnitModel |
|
|
18
|
+
PackageModel |
|
|
19
|
+
TopicModel
|
|
20
|
+
>;
|
|
21
|
+
|
|
22
|
+
const vertical = "vertical";
|
|
23
|
+
const horizontal = "horizontal";
|
|
24
|
+
|
|
25
|
+
interface ResultContainerProps {
|
|
26
|
+
data?: ResultData;
|
|
27
|
+
error?: { message: string; name: string };
|
|
28
|
+
layout?: typeof vertical | typeof horizontal;
|
|
29
|
+
|
|
30
|
+
FilterComponent?: FC<any>;
|
|
31
|
+
ResultsComponent: FC<any>;
|
|
32
|
+
PaginationComponent: FC<any>;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const ResultContainer: FC<ResultContainerProps> = ({
|
|
37
|
+
data,
|
|
38
|
+
error,
|
|
39
|
+
layout = horizontal,
|
|
40
|
+
FilterComponent,
|
|
41
|
+
ResultsComponent,
|
|
42
|
+
PaginationComponent,
|
|
43
|
+
...props
|
|
44
|
+
}) => {
|
|
45
|
+
if (error) {
|
|
46
|
+
return <div>Error: {error.message}</div>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!data || data.items.length === 0) {
|
|
50
|
+
return <Empty />;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className="flex flex-row gap-6 pb-6">
|
|
55
|
+
|
|
56
|
+
{(data.tags && FilterComponent) && (
|
|
57
|
+
<FilterComponent
|
|
58
|
+
tags={data.tags}
|
|
59
|
+
totalItemCount={data.pageInfo.totalItemCount!}
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
)}
|
|
63
|
+
<div className="flex-1">
|
|
64
|
+
<ResultsComponent items={data.items} {...props} />
|
|
65
|
+
|
|
66
|
+
<PaginationComponent pageInfo={data.pageInfo} {...props} />
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
import {
|
|
3
|
+
DocumentModel,
|
|
4
|
+
ExternalProductGraphicModel,
|
|
5
|
+
FragmentModel,
|
|
6
|
+
InformationUnitModel,
|
|
7
|
+
PackageModel,
|
|
8
|
+
TopicModel
|
|
9
|
+
} from "@c-rex/interfaces";
|
|
10
|
+
import { FileStack } from "lucide-react";
|
|
11
|
+
import { OpenOrDownloadFileDropdown } from "../renditions/file-download";
|
|
12
|
+
import { cn, getType, getTitle, getLanguage } from "@c-rex/utils";
|
|
13
|
+
import { Button } from "@c-rex/ui/button";
|
|
14
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "@c-rex/ui/tooltip";
|
|
15
|
+
import { Favorite, ResultTypes } from "@c-rex/types";
|
|
16
|
+
import { getVersions } from "./utils";
|
|
17
|
+
import { RESULT_TYPES } from "@c-rex/constants";
|
|
18
|
+
import { FavoriteButton } from "../favorites/favorite-button";
|
|
19
|
+
import { ImageRenditionContainer } from "../renditions/image/container";
|
|
20
|
+
import { BookmarkButton } from "../favorites/bookmark-button";
|
|
21
|
+
import { HtmlRendition } from "../renditions/html";
|
|
22
|
+
|
|
23
|
+
interface TableWithImageProps {
|
|
24
|
+
items: (
|
|
25
|
+
DocumentModel |
|
|
26
|
+
ExternalProductGraphicModel |
|
|
27
|
+
FragmentModel |
|
|
28
|
+
InformationUnitModel |
|
|
29
|
+
PackageModel |
|
|
30
|
+
TopicModel
|
|
31
|
+
)[];
|
|
32
|
+
query?: string;
|
|
33
|
+
markersList?: Favorite[];
|
|
34
|
+
disabledResults?: ResultTypes[];
|
|
35
|
+
imageRestrictions?: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
export const TableWithImage: FC<TableWithImageProps> = ({ items, query = "", disabledResults = [], imageRestrictions = [] }) => {
|
|
40
|
+
return (
|
|
41
|
+
<div className="flex-1">
|
|
42
|
+
{items.map((item, index) => {
|
|
43
|
+
const title = getTitle(item.titles, item.labels);
|
|
44
|
+
const language = getLanguage(item.languages);
|
|
45
|
+
const itemType = getType(item.class);
|
|
46
|
+
const disabled = disabledResults.includes(itemType);
|
|
47
|
+
const isDocument = itemType === RESULT_TYPES.DOCUMENT;
|
|
48
|
+
const multipleVersions = getVersions(item.versionOf);
|
|
49
|
+
const packageId = item.packages && item.packages.length > 0 ? item.packages[0]?.shortId : null;
|
|
50
|
+
|
|
51
|
+
let itemLink = `/topics/${item.shortId}/content`
|
|
52
|
+
if (isDocument) {
|
|
53
|
+
itemLink = `/documents/${item.shortId}/details`
|
|
54
|
+
}
|
|
55
|
+
itemLink += `?q=${query}`
|
|
56
|
+
|
|
57
|
+
if (packageId) {
|
|
58
|
+
itemLink += `&package=${packageId}`
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div
|
|
64
|
+
key={index}
|
|
65
|
+
className={cn(
|
|
66
|
+
"min-h-12 flex flex-wrap items-center border px-4 py-2 gap-2 rounded",
|
|
67
|
+
index == items.length - 1 ? "" : "mb-4",
|
|
68
|
+
`c-rex-result-item c-rex-result-${itemType}`,
|
|
69
|
+
disabled && "c-rex-result-item-disabled",
|
|
70
|
+
)}
|
|
71
|
+
>
|
|
72
|
+
<div className="h-16 w-16 flex items-start">
|
|
73
|
+
<ImageRenditionContainer
|
|
74
|
+
itemShortId={item.shortId!}
|
|
75
|
+
imageRestrictions={imageRestrictions}
|
|
76
|
+
emptyImageStyle="h-16 w-16"
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<div className="flex-1 p-2 flex flex-col">
|
|
81
|
+
<span className="text-sm text-muted-foreground">
|
|
82
|
+
{language} - {item.shortId}
|
|
83
|
+
</span>
|
|
84
|
+
|
|
85
|
+
<span className="text-lg font-medium">
|
|
86
|
+
{disabled ? (
|
|
87
|
+
title
|
|
88
|
+
) : (
|
|
89
|
+
<a className="hover:underline" href={itemLink}>{title}</a>
|
|
90
|
+
)}
|
|
91
|
+
</span>
|
|
92
|
+
|
|
93
|
+
<span className="text-sm">
|
|
94
|
+
{itemType}
|
|
95
|
+
</span>
|
|
96
|
+
<span className="text-sm">
|
|
97
|
+
<HtmlRendition shortId={item.shortId!} />
|
|
98
|
+
</span>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div className="flex flex-col p-2 ml-auto justify-center">
|
|
102
|
+
<span className="text-end text-sm text-muted-foreground mb-2">
|
|
103
|
+
{item.revision}
|
|
104
|
+
</span>
|
|
105
|
+
<div className="flex gap-2">
|
|
106
|
+
<OpenOrDownloadFileDropdown renditions={item.renditions} />
|
|
107
|
+
|
|
108
|
+
{isDocument && (
|
|
109
|
+
<BookmarkButton
|
|
110
|
+
shortId={item.shortId!}
|
|
111
|
+
|
|
112
|
+
/>
|
|
113
|
+
)}
|
|
114
|
+
|
|
115
|
+
<FavoriteButton
|
|
116
|
+
id={item.shortId!}
|
|
117
|
+
type={itemType}
|
|
118
|
+
label={title}
|
|
119
|
+
/>
|
|
120
|
+
|
|
121
|
+
{multipleVersions.length > 1 && (
|
|
122
|
+
<Tooltip>
|
|
123
|
+
<TooltipTrigger asChild>
|
|
124
|
+
<Button variant="ghost" size="icon">
|
|
125
|
+
<FileStack />
|
|
126
|
+
</Button>
|
|
127
|
+
</TooltipTrigger>
|
|
128
|
+
<TooltipContent>
|
|
129
|
+
Available in: {multipleVersions.join(", ")}
|
|
130
|
+
</TooltipContent>
|
|
131
|
+
</Tooltip>
|
|
132
|
+
)}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
})}
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
@@ -5,7 +5,7 @@ import { CloudDownload, Eye } from "lucide-react";
|
|
|
5
5
|
import { FaFilePdf } from "react-icons/fa6";
|
|
6
6
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@c-rex/ui/dropdown-menu";
|
|
7
7
|
import { cn } from "@c-rex/utils";
|
|
8
|
-
import { Flag } from "../flag";
|
|
8
|
+
import { Flag } from "../icons/flag-icon";
|
|
9
9
|
import { Badge } from "@c-rex/ui/badge";
|
|
10
10
|
import { useQueryState } from "nuqs";
|
|
11
11
|
|
|
@@ -13,7 +13,6 @@ interface TableViewProps {
|
|
|
13
13
|
items: informationUnitsResponseItem[];
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
17
16
|
const IconsToFileExtension: Record<string, React.ReactNode> = {
|
|
18
17
|
"application/pdf": <FaFilePdf className="h-6 w-6" />,
|
|
19
18
|
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { LiteralModel, ObjectRefModel, RenditionModel } from "@c-rex/interfaces";
|
|
2
|
+
import { DocumentsType } from "@c-rex/types";
|
|
3
|
+
|
|
4
|
+
export const getFileRenditions = ({ renditions }: { renditions: RenditionModel[] }): DocumentsType => {
|
|
5
|
+
if (renditions == undefined || renditions.length == 0) {
|
|
6
|
+
return {} as DocumentsType;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const filteredRenditions = renditions.filter(
|
|
10
|
+
(item) => item.format != "application/xhtml+xml" && item.format != "application/json" && item.format != "application/llm+xml"
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
if (filteredRenditions.length == 0 || filteredRenditions[0] == undefined) {
|
|
14
|
+
return {} as DocumentsType;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const result: {
|
|
18
|
+
[key: string]: {
|
|
19
|
+
view: string;
|
|
20
|
+
download: string;
|
|
21
|
+
}
|
|
22
|
+
} = {}
|
|
23
|
+
|
|
24
|
+
filteredRenditions.forEach((item) => {
|
|
25
|
+
const key = item.format!
|
|
26
|
+
|
|
27
|
+
if (result[key] == undefined) {
|
|
28
|
+
result[key] = {
|
|
29
|
+
view: "",
|
|
30
|
+
download: ""
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const download = item.links?.filter((link) => link.rel == "download")[0]?.href
|
|
34
|
+
const view = item.links?.filter((link) => link.rel == "view")[0]?.href
|
|
35
|
+
|
|
36
|
+
if (download != null && download != undefined) {
|
|
37
|
+
result[key].download = download
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (view != null && view != undefined) {
|
|
41
|
+
result[key].view = view
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
return result
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
export const getDescription = (descriptions?: LiteralModel[] | null, abstracts?: LiteralModel[] | null): string => {
|
|
50
|
+
const desc = descriptions?.[0]?.value || abstracts?.[0]?.value;
|
|
51
|
+
return desc || "";
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const getClassLabel = (classObj?: { labels?: LiteralModel[] | null }): string => {
|
|
55
|
+
return classObj?.labels?.[0]?.value || "Document";
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const getVersions = (versionOf?: ObjectRefModel) => {
|
|
59
|
+
let versionList: string[] = []
|
|
60
|
+
if (versionOf && versionOf.labels) {
|
|
61
|
+
const aux = versionOf.labels.map(item => item.language!)
|
|
62
|
+
versionList = aux.filter((value) => value !== undefined);
|
|
63
|
+
}
|
|
64
|
+
return versionList;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
|