@c-rex/components 0.1.14 → 0.1.16
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 +5 -1
- package/src/autocomplete.tsx +2 -0
- package/src/dialog-filter.tsx +15 -9
- package/src/file-icon.tsx +9 -0
- package/src/info/info-card.tsx +2 -0
- package/src/info/info-table.tsx +47 -13
- package/src/navbar/navbar.tsx +7 -6
- package/src/navbar/search-input.tsx +1 -1
- package/src/navbar/sign-in-out-btns.tsx +17 -3
- package/src/navbar/user-menu.tsx +9 -8
- package/src/render-article.tsx +1 -1
- package/src/result-list.tsx +21 -23
- package/src/result-view/blog.tsx +1 -0
- package/src/result-view/table-with-images.tsx +174 -0
- package/src/result-view/table.tsx +2 -2
- package/src/stores/language-store.ts +4 -15
- package/src/stores/search-settings-store.ts +1 -1
- package/src/result-view/dropdown-menu.tsx +0 -39
- /package/src/{result-view → stories}/blog-view.stories.tsx +0 -0
- /package/src/{result-view → stories}/table-view.stories.tsx +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c-rex/components",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"files": [
|
|
5
5
|
"src"
|
|
6
6
|
],
|
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
"types": "./src/breadcrumb.tsx",
|
|
26
26
|
"import": "./src/breadcrumb.tsx"
|
|
27
27
|
},
|
|
28
|
+
"./settings-menu": {
|
|
29
|
+
"types": "./src/navbar/settings.tsx",
|
|
30
|
+
"import": "./src/navbar/settings.tsx"
|
|
31
|
+
},
|
|
28
32
|
"./flag": {
|
|
29
33
|
"types": "./src/flag.tsx",
|
|
30
34
|
"import": "./src/flag.tsx"
|
package/src/autocomplete.tsx
CHANGED
package/src/dialog-filter.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { FC, useState } from "react"
|
|
1
|
+
import React, { FC, useEffect, useState } from "react"
|
|
2
2
|
import { Button } from "@c-rex/ui/button"
|
|
3
3
|
import {
|
|
4
4
|
Dialog,
|
|
@@ -35,17 +35,18 @@ export const DialogFilter: FC<DialogFilterProps> = ({ trigger }) => {
|
|
|
35
35
|
const t = useTranslations("filter");
|
|
36
36
|
const { setLoading } = useSearchContext();
|
|
37
37
|
|
|
38
|
-
const savedLike = useSearchSettingsStore
|
|
39
|
-
const savedOperator = useSearchSettingsStore
|
|
40
|
-
const savedWildcard = useSearchSettingsStore
|
|
41
|
-
const savedLanguages = useLanguageStore
|
|
42
|
-
const availableLanguagesAndCountries = useLanguageStore
|
|
38
|
+
const savedLike = useSearchSettingsStore((state) => state.like)
|
|
39
|
+
const savedOperator = useSearchSettingsStore((state) => state.operator)
|
|
40
|
+
const savedWildcard = useSearchSettingsStore((state) => state.wildcard)
|
|
41
|
+
const savedLanguages = useLanguageStore(state => state.contentLang);
|
|
42
|
+
const availableLanguagesAndCountries = useLanguageStore(state => state.availableLanguages);
|
|
43
43
|
|
|
44
44
|
const [like, setLike] = useState<boolean>(savedLike);
|
|
45
45
|
const [operator, setOperator] = useState<string>(savedOperator);
|
|
46
46
|
const [wildcard, setWildcard] = useState<string>(savedWildcard);
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
|
|
48
|
+
const generateLanguagesList = (): Languages[] => {
|
|
49
|
+
return availableLanguagesAndCountries?.map((item: LanguageAndCountries) => {
|
|
49
50
|
const checked = savedLanguages.includes(item.value)
|
|
50
51
|
|
|
51
52
|
return {
|
|
@@ -54,8 +55,9 @@ export const DialogFilter: FC<DialogFilterProps> = ({ trigger }) => {
|
|
|
54
55
|
checked,
|
|
55
56
|
}
|
|
56
57
|
})
|
|
57
|
-
|
|
58
|
+
}
|
|
58
59
|
|
|
60
|
+
const [languages, setLanguages] = useState<Languages[]>(generateLanguagesList());
|
|
59
61
|
const [params, setParams] = useQueryStates({
|
|
60
62
|
search: parseAsString,
|
|
61
63
|
language: parseAsString,
|
|
@@ -100,6 +102,10 @@ export const DialogFilter: FC<DialogFilterProps> = ({ trigger }) => {
|
|
|
100
102
|
});
|
|
101
103
|
}
|
|
102
104
|
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
setLanguages(generateLanguagesList())
|
|
107
|
+
}, [availableLanguagesAndCountries])
|
|
108
|
+
|
|
103
109
|
return (
|
|
104
110
|
<Dialog>
|
|
105
111
|
<DialogTrigger asChild>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PiFilePdf } from "react-icons/pi";
|
|
2
|
+
|
|
3
|
+
export const FileIcon = ({ extension }: { extension: string }) => {
|
|
4
|
+
const IconsToFileExtension: Record<string, React.ReactNode> = {
|
|
5
|
+
"application/pdf": <PiFilePdf className="!h-5 !w-5 text-primary" />,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
return IconsToFileExtension[extension] || null;
|
|
9
|
+
};
|
package/src/info/info-card.tsx
CHANGED
package/src/info/info-table.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { FC } from "react";
|
|
2
|
-
import { articleInfoItemType, DocumentsType } from "@c-rex/types";
|
|
2
|
+
import { articleInfoItemType, DocumentsType, Favorite } from "@c-rex/types";
|
|
3
3
|
import { Card, CardContent, CardHeader, CardTitle } from "@c-rex/ui/card";
|
|
4
4
|
import {
|
|
5
5
|
Table,
|
|
@@ -15,25 +15,23 @@ import {
|
|
|
15
15
|
DropdownMenuItem,
|
|
16
16
|
DropdownMenuTrigger,
|
|
17
17
|
} from "@c-rex/ui/dropdown-menu";
|
|
18
|
-
import { CloudDownload, Eye } from "lucide-react";
|
|
18
|
+
import { Bookmark, CloudDownload, Eye } from "lucide-react";
|
|
19
19
|
|
|
20
|
-
import { FaFilePdf } from "react-icons/fa6";
|
|
21
20
|
import { AvailableVersionsInterface } from "@c-rex/interfaces";
|
|
22
21
|
import { Flag } from "../flag";
|
|
22
|
+
import { Button } from "@c-rex/ui/button";
|
|
23
23
|
import { useLanguageStore } from "../stores/language-store";
|
|
24
|
+
import { FileIcon } from "../file-icon";
|
|
24
25
|
|
|
25
26
|
type Props = {
|
|
26
27
|
title: string;
|
|
27
28
|
files?: DocumentsType
|
|
28
29
|
items: articleInfoItemType[]
|
|
29
30
|
availableVersions?: AvailableVersionsInterface[]
|
|
31
|
+
markersList?: Favorite[]
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
const
|
|
33
|
-
"application/pdf": <FaFilePdf className="h-6 w-6" />,
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export const InfoTable: FC<Props> = ({ title, items, files, availableVersions }) => {
|
|
34
|
+
export const InfoTable: FC<Props> = ({ title, items, files, availableVersions, markersList }) => {
|
|
37
35
|
const t = useTranslations();
|
|
38
36
|
const uiLang = useLanguageStore(state => state.uiLang);
|
|
39
37
|
const newItems = filteredItems(items)
|
|
@@ -75,18 +73,20 @@ export const InfoTable: FC<Props> = ({ title, items, files, availableVersions })
|
|
|
75
73
|
|
|
76
74
|
return (
|
|
77
75
|
<DropdownMenu key={index}>
|
|
78
|
-
<DropdownMenuTrigger className="
|
|
79
|
-
|
|
76
|
+
<DropdownMenuTrigger className="mr-2" asChild >
|
|
77
|
+
<Button variant="outline" size="icon" >
|
|
78
|
+
<FileIcon extension={item} />
|
|
79
|
+
</Button>
|
|
80
80
|
</DropdownMenuTrigger>
|
|
81
81
|
<DropdownMenuContent>
|
|
82
82
|
<DropdownMenuItem>
|
|
83
83
|
<a href={files[item].view} target="_blank" rel="noreferrer" className="flex items-center">
|
|
84
|
-
<Eye className="mr-2
|
|
84
|
+
<Eye className="mr-2" /> Open
|
|
85
85
|
</a>
|
|
86
86
|
</DropdownMenuItem>
|
|
87
87
|
<DropdownMenuItem>
|
|
88
88
|
<a href={files[item].download} target="_blank" rel="noreferrer" className="flex items-center">
|
|
89
|
-
<CloudDownload className="mr-2
|
|
89
|
+
<CloudDownload className="mr-2" /> Download
|
|
90
90
|
</a>
|
|
91
91
|
</DropdownMenuItem>
|
|
92
92
|
|
|
@@ -118,9 +118,43 @@ export const InfoTable: FC<Props> = ({ title, items, files, availableVersions })
|
|
|
118
118
|
</TableRow>
|
|
119
119
|
)}
|
|
120
120
|
|
|
121
|
+
{markersList && markersList.length > 0 && (
|
|
122
|
+
<TableRow className="min-h-12">
|
|
123
|
+
<TableCell className="font-medium w-28 pl-4">
|
|
124
|
+
<h4 className="text-sm font-medium">Bookmarks</h4>
|
|
125
|
+
</TableCell>
|
|
126
|
+
<TableCell className="text-xs text-muted-foreground flex items-center gap-2 min-h-12">
|
|
127
|
+
<DropdownMenu>
|
|
128
|
+
<DropdownMenuTrigger className="mr-2" asChild>
|
|
129
|
+
<Button variant="outline" size="icon" className="relative">
|
|
130
|
+
<Bookmark className="text-primary" />
|
|
131
|
+
|
|
132
|
+
<span
|
|
133
|
+
className="absolute -top-[10px] -right-[10px] min-w-5 min-h-5 bg-primary text-white rounded-full"
|
|
134
|
+
>{markersList.length}</span>
|
|
135
|
+
</Button>
|
|
136
|
+
</DropdownMenuTrigger>
|
|
137
|
+
<DropdownMenuContent>
|
|
138
|
+
|
|
139
|
+
{markersList.map((marker) => (
|
|
140
|
+
<DropdownMenuItem key={marker.id}>
|
|
141
|
+
<a href={`./${marker.id}`} target="_blank" rel="noreferrer" className="flex items-center">
|
|
142
|
+
{marker.label}
|
|
143
|
+
</a>
|
|
144
|
+
</DropdownMenuItem>
|
|
145
|
+
))}
|
|
146
|
+
|
|
147
|
+
</DropdownMenuContent>
|
|
148
|
+
</DropdownMenu>
|
|
149
|
+
|
|
150
|
+
</TableCell>
|
|
151
|
+
</TableRow>
|
|
152
|
+
)}
|
|
153
|
+
|
|
154
|
+
|
|
121
155
|
</TableBody>
|
|
122
156
|
</Table>
|
|
123
157
|
</CardContent>
|
|
124
|
-
</Card>
|
|
158
|
+
</Card >
|
|
125
159
|
);
|
|
126
160
|
}
|
package/src/navbar/navbar.tsx
CHANGED
|
@@ -2,10 +2,10 @@ import { FC } from "react";
|
|
|
2
2
|
import Link from "next/link";
|
|
3
3
|
import { SignInBtn } from "./sign-in-out-btns";
|
|
4
4
|
import { getServerSession } from "next-auth";
|
|
5
|
-
import { getConfigs } from "@c-rex/utils/next-cookies";
|
|
6
5
|
import { SettingsMenu } from "./settings";
|
|
7
6
|
import { UserMenu } from "./user-menu";
|
|
8
7
|
import { SearchInput } from "./search-input";
|
|
8
|
+
import { getClientConfig, getServerConfig } from "@c-rex/core";
|
|
9
9
|
|
|
10
10
|
interface NavBarProps {
|
|
11
11
|
title: string;
|
|
@@ -14,10 +14,11 @@ interface NavBarProps {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export const NavBar: FC<NavBarProps> = async ({ title, showInput, showPkgFilter }) => {
|
|
17
|
-
const
|
|
17
|
+
const clientConfigs = getClientConfig();
|
|
18
|
+
const serverConfigs = getServerConfig();
|
|
18
19
|
|
|
19
20
|
let session: any;
|
|
20
|
-
if (
|
|
21
|
+
if (clientConfigs.OIDC.userEnabled) {
|
|
21
22
|
session = await getServerSession();
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -42,17 +43,17 @@ export const NavBar: FC<NavBarProps> = async ({ title, showInput, showPkgFilter
|
|
|
42
43
|
<div className="flex items-center gap-2 absolute sm:static top-4 h-14 sm:h-min right-4">
|
|
43
44
|
<SearchInput showInput={showInput} showPkgFilter={showPkgFilter} placedOn="NAVBAR" />
|
|
44
45
|
|
|
45
|
-
{
|
|
46
|
+
{clientConfigs.OIDC.userEnabled && (
|
|
46
47
|
<>
|
|
47
48
|
{session ? (
|
|
48
|
-
<UserMenu session={session} />
|
|
49
|
+
<UserMenu session={session} OIDCEndPoint={serverConfigs.OIDC.user.issuer} />
|
|
49
50
|
) : (
|
|
50
51
|
<SignInBtn />
|
|
51
52
|
)}
|
|
52
53
|
</>
|
|
53
54
|
)}
|
|
54
55
|
|
|
55
|
-
{
|
|
56
|
+
{clientConfigs.languageSwitcher.enabled && (
|
|
56
57
|
<SettingsMenu />
|
|
57
58
|
)}
|
|
58
59
|
</div>
|
|
@@ -23,7 +23,7 @@ export const SearchInput: FC<Props> = ({ showInput, showPkgFilter, placedOn = "N
|
|
|
23
23
|
placedOn === "NAVBAR" ? "hidden lg:flex" : "sm:flex-none sm:w-60 md:w-72 lg:w-full lg:hidden flex",
|
|
24
24
|
"flex-1 items-center px-3 border rounded-full h-8 c-rex-search-bar"
|
|
25
25
|
)}>
|
|
26
|
-
<Search className="
|
|
26
|
+
<Search className="shrink-0 opacity-50" />
|
|
27
27
|
|
|
28
28
|
<AutoComplete
|
|
29
29
|
initialValue=""
|
|
@@ -1,21 +1,35 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import React from "react";
|
|
3
|
+
import React, { useState } from "react";
|
|
4
4
|
import { signIn } from "next-auth/react";
|
|
5
5
|
import { Button } from "@c-rex/ui/button";
|
|
6
6
|
import { useTranslations } from "next-intl";
|
|
7
7
|
|
|
8
8
|
export const SignInBtn = () => {
|
|
9
9
|
const t = useTranslations("user")
|
|
10
|
+
const [isLoading, setIsLoading] = useState<boolean>(false)
|
|
11
|
+
|
|
12
|
+
const handleSignIn = () => {
|
|
13
|
+
setIsLoading(true);
|
|
14
|
+
signIn("crex", { prompt: "login", callbackUrl: "/" });
|
|
15
|
+
}
|
|
16
|
+
|
|
10
17
|
return (
|
|
11
18
|
<Button
|
|
12
19
|
className="gap-2 px-5 md:flex"
|
|
13
20
|
variant="default"
|
|
14
21
|
size="sm"
|
|
15
22
|
rounded="full"
|
|
16
|
-
|
|
23
|
+
disabled={isLoading}
|
|
24
|
+
onClick={handleSignIn}
|
|
17
25
|
>
|
|
18
|
-
|
|
26
|
+
{isLoading ? (
|
|
27
|
+
<div className="flex items-center justify-center py-4">
|
|
28
|
+
<div className="animate-spin rounded-full h-5 w-5 border-2 border-gray-300 border-t-gray-950" />
|
|
29
|
+
</div>
|
|
30
|
+
) : (
|
|
31
|
+
<span>{t("signIn")}</span>
|
|
32
|
+
)}
|
|
19
33
|
</Button>
|
|
20
34
|
);
|
|
21
35
|
}
|
package/src/navbar/user-menu.tsx
CHANGED
|
@@ -11,19 +11,17 @@ import {
|
|
|
11
11
|
} from "@c-rex/ui/dropdown-menu"
|
|
12
12
|
import { CircleUser } from "lucide-react";
|
|
13
13
|
import { useTranslations } from "next-intl";
|
|
14
|
-
import { useAppConfig } from "@c-rex/contexts/config-provider";
|
|
15
14
|
import { signOut } from "next-auth/react";
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
interface Props {
|
|
19
18
|
session: any
|
|
19
|
+
OIDCEndPoint: string
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export const UserMenu: FC<Props> = ({ session }) => {
|
|
23
|
-
const t = useTranslations();
|
|
24
|
-
const { configs } = useAppConfig();
|
|
25
22
|
|
|
26
|
-
|
|
23
|
+
export const UserMenu: FC<Props> = ({ session, OIDCEndPoint }) => {
|
|
24
|
+
const t = useTranslations();
|
|
27
25
|
|
|
28
26
|
return (
|
|
29
27
|
<DropdownMenu>
|
|
@@ -38,18 +36,21 @@ export const UserMenu: FC<Props> = ({ session }) => {
|
|
|
38
36
|
<DropdownMenuSeparator />
|
|
39
37
|
<DropdownMenuGroup>
|
|
40
38
|
<DropdownMenuItem>
|
|
41
|
-
<a href={`${
|
|
39
|
+
<a href={`${OIDCEndPoint}oidc/Profile`} target="_blank" rel="noreferrer">
|
|
42
40
|
Profile settings
|
|
43
41
|
</a>
|
|
44
42
|
</DropdownMenuItem>
|
|
45
43
|
|
|
46
44
|
<DropdownMenuItem>
|
|
47
|
-
<a href={`${
|
|
45
|
+
<a href={`${OIDCEndPoint}oidc/Profile/ChangePassword`} target="_blank" rel="noreferrer">
|
|
48
46
|
Change password
|
|
49
47
|
</a>
|
|
50
48
|
</DropdownMenuItem>
|
|
51
49
|
|
|
52
|
-
<DropdownMenuItem
|
|
50
|
+
<DropdownMenuItem
|
|
51
|
+
onClick={() => signOut({ callbackUrl: "/api/auth/logout" })}
|
|
52
|
+
className='text-red-500 focus:text-red-500'
|
|
53
|
+
>
|
|
53
54
|
{t("user.signOut")}
|
|
54
55
|
</DropdownMenuItem>
|
|
55
56
|
</DropdownMenuGroup>
|
package/src/render-article.tsx
CHANGED
|
@@ -61,7 +61,7 @@ export const RenderArticle = ({ htmlContent, contentLang }: Props) => {
|
|
|
61
61
|
}, [highlightedContent, registerContainer]);
|
|
62
62
|
|
|
63
63
|
return (
|
|
64
|
-
<main ref={containerRef} lang={contentLang} className="pb-4">
|
|
64
|
+
<main ref={containerRef} lang={contentLang} className="pb-4 min-h-[70vh]">
|
|
65
65
|
{highlightedContent}
|
|
66
66
|
</main>
|
|
67
67
|
);
|
package/src/result-list.tsx
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import React, { FC } from "react";
|
|
1
|
+
import React, { FC, ReactNode } from "react";
|
|
2
2
|
import { DefaultPageInfo, informationUnitsResponseItem, TopicsResponseItem, } from "@c-rex/interfaces";
|
|
3
3
|
import { Empty } from "./empty";
|
|
4
4
|
import BlogView from './result-view/blog';
|
|
5
5
|
import TableView from './result-view/table';
|
|
6
6
|
import { Pagination } from "./pagination";
|
|
7
7
|
import { useAppConfig } from "@c-rex/contexts/config-provider";
|
|
8
|
+
import { ResultViewStyles } from "@c-rex/types";
|
|
9
|
+
import TableWithImageView from "./result-view/table-with-images";
|
|
8
10
|
|
|
9
11
|
interface ResultListProps {
|
|
10
12
|
items: informationUnitsResponseItem[] | TopicsResponseItem[];
|
|
@@ -14,32 +16,28 @@ interface ResultListProps {
|
|
|
14
16
|
export const ResultList: FC<ResultListProps> = ({ items, pagination }: ResultListProps) => {
|
|
15
17
|
const { configs } = useAppConfig()
|
|
16
18
|
|
|
17
|
-
const
|
|
19
|
+
const listComponent: Record<ResultViewStyles, ReactNode> = {
|
|
20
|
+
"cards": <BlogView items={items as TopicsResponseItem[]} />,
|
|
21
|
+
"table": <TableView items={items as informationUnitsResponseItem[]} />,
|
|
22
|
+
"table-with-images": <TableWithImageView items={items as informationUnitsResponseItem[]} />,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!(configs.results.resultViewStyle in listComponent)) {
|
|
26
|
+
throw new Error(`Unsupported result view style: ${configs.results.resultViewStyle}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (items.length === 0) {
|
|
30
|
+
return <Empty />;
|
|
31
|
+
}
|
|
18
32
|
|
|
19
33
|
return (
|
|
20
34
|
<>
|
|
21
|
-
{
|
|
22
|
-
items.length === 0 ? (
|
|
23
|
-
<Empty />
|
|
24
|
-
) : (
|
|
25
|
-
<>
|
|
26
|
-
{isTableView ? (
|
|
27
|
-
<TableView
|
|
28
|
-
items={items as informationUnitsResponseItem[]}
|
|
29
|
-
/>
|
|
30
|
-
) : (
|
|
31
|
-
<BlogView
|
|
32
|
-
items={items as TopicsResponseItem[]}
|
|
33
|
-
/>
|
|
34
|
-
)}
|
|
35
|
+
{listComponent[configs.results.resultViewStyle]}
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
</>
|
|
41
|
-
)
|
|
42
|
-
}
|
|
37
|
+
<Pagination
|
|
38
|
+
totalPages={pagination.pageCount}
|
|
39
|
+
currentPage={pagination.pageNumber}
|
|
40
|
+
/>
|
|
43
41
|
</>
|
|
44
42
|
);
|
|
45
43
|
};
|
package/src/result-view/blog.tsx
CHANGED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { FC, useEffect, useState } from "react";
|
|
2
|
+
import { informationUnitsResponseItem } from "@c-rex/interfaces";
|
|
3
|
+
import { CloudDownload, Eye, FileStack, ImageOff } from "lucide-react";
|
|
4
|
+
import { FaFilePdf } from "react-icons/fa6";
|
|
5
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@c-rex/ui/dropdown-menu";
|
|
6
|
+
import { cn } from "@c-rex/utils";
|
|
7
|
+
import { useQueryState } from "nuqs";
|
|
8
|
+
import { Skeleton } from "@c-rex/ui/skeleton";
|
|
9
|
+
import { FavoriteButton } from "../favorite-button";
|
|
10
|
+
import { Button } from "@c-rex/ui/button";
|
|
11
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "@c-rex/ui/tooltip";
|
|
12
|
+
import { ResultTypes } from "@c-rex/types";
|
|
13
|
+
|
|
14
|
+
interface TableWithImageViewProps {
|
|
15
|
+
items: informationUnitsResponseItem[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const IconsToFileExtension: Record<string, React.ReactNode> = {
|
|
19
|
+
"application/pdf": <FaFilePdf className="h-5 w-5" />,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const Image: FC<{ id: string }> = ({ id }) => {
|
|
23
|
+
const [imageUrl, setImageUrl] = useState("");
|
|
24
|
+
const [loading, setLoading] = useState(true);
|
|
25
|
+
const [error, setError] = useState(false);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
let ignore = false;
|
|
29
|
+
|
|
30
|
+
async function fetchImage() {
|
|
31
|
+
try {
|
|
32
|
+
const res = await fetch(`https://picsum.photos/v2/list?limit=1`);
|
|
33
|
+
|
|
34
|
+
{/*
|
|
35
|
+
TODO: talvez vamos receber mais de uma imagem, mas por enquanto pegamos apenas a primeira na lista
|
|
36
|
+
*/}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
const data = await res.json();
|
|
40
|
+
if (!ignore) {
|
|
41
|
+
setImageUrl(data[0].download_url);
|
|
42
|
+
setLoading(false);
|
|
43
|
+
}
|
|
44
|
+
} catch (err) {
|
|
45
|
+
setError(true);
|
|
46
|
+
console.error("Error loading image", err);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fetchImage();
|
|
51
|
+
return () => {
|
|
52
|
+
ignore = true
|
|
53
|
+
};
|
|
54
|
+
}, [id]);
|
|
55
|
+
|
|
56
|
+
if (error) {
|
|
57
|
+
return <ImageOff className="h-16 w-16 text-muted" />;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div className="h-16 w-16 flex items-center">
|
|
62
|
+
{loading ? (
|
|
63
|
+
<Skeleton className="h-16 w-16" />
|
|
64
|
+
) : (
|
|
65
|
+
<img
|
|
66
|
+
src={imageUrl}
|
|
67
|
+
loading="lazy"
|
|
68
|
+
onLoad={() => setLoading(false)}
|
|
69
|
+
/>
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const TableWithImageView: FC<TableWithImageViewProps> = ({ items, params }) => {
|
|
76
|
+
const [query] = useQueryState("search");
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<div className="mb-6">
|
|
80
|
+
{items.map((item, index) => (
|
|
81
|
+
<div
|
|
82
|
+
className={cn(
|
|
83
|
+
"min-h-12 flex flex-wrap items-center border px-4 py-2 gap-2 rounded mb-2",
|
|
84
|
+
`c-rex-result-item c-rex-result-${item.type.toLowerCase()}`,
|
|
85
|
+
item.disabled && "c-rex-result-item-disabled",
|
|
86
|
+
)}
|
|
87
|
+
key={index}
|
|
88
|
+
>
|
|
89
|
+
|
|
90
|
+
<div className="h-16 w-16 flex items-center">
|
|
91
|
+
<Image id={item.shortId} {...params} />
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div className="flex-1 p-2 flex flex-col">
|
|
95
|
+
<span className=" text-sm text-muted-foreground">
|
|
96
|
+
{item.language.split("-")[0]?.toUpperCase()}
|
|
97
|
+
</span>
|
|
98
|
+
|
|
99
|
+
<span className="text-lg font-medium">
|
|
100
|
+
{item.disabled ? (
|
|
101
|
+
item.title
|
|
102
|
+
) : (
|
|
103
|
+
<a className="hover:underline" href={`${item.link}?q=${query}`}>{item.title}</a>
|
|
104
|
+
)}
|
|
105
|
+
</span>
|
|
106
|
+
|
|
107
|
+
<span className="text-sm">
|
|
108
|
+
{item.type}
|
|
109
|
+
</span>
|
|
110
|
+
<span className="text-sm">
|
|
111
|
+
short description here {/*
|
|
112
|
+
TODO: get abstract if has the value key on it. Same behavior on class props
|
|
113
|
+
|
|
114
|
+
abstract and description are not being returned by the API yet
|
|
115
|
+
*/}
|
|
116
|
+
</span>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<div className="flex flex-col p-2 ml-auto justify-center">
|
|
120
|
+
<span className="text-end text-sm text-muted-foreground mb-2">
|
|
121
|
+
{item.revision}
|
|
122
|
+
</span>
|
|
123
|
+
<div className="flex gap-2">
|
|
124
|
+
{Object.keys(item.files).map((fileKey, index) => {
|
|
125
|
+
if (!item.files[fileKey]) return null
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<DropdownMenu key={index}>
|
|
129
|
+
<DropdownMenuTrigger>
|
|
130
|
+
<Button variant="ghost" size="icon" rounded="full">
|
|
131
|
+
{IconsToFileExtension[fileKey]}
|
|
132
|
+
</Button>
|
|
133
|
+
</DropdownMenuTrigger>
|
|
134
|
+
<DropdownMenuContent>
|
|
135
|
+
<DropdownMenuItem>
|
|
136
|
+
<a href={item.files[fileKey].view} target="_blank" rel="noreferrer" className="flex items-center">
|
|
137
|
+
<Eye className="mr-2" /> Open {/*TODO: use i18n functions*/}
|
|
138
|
+
</a>
|
|
139
|
+
</DropdownMenuItem>
|
|
140
|
+
<DropdownMenuItem>
|
|
141
|
+
<a href={item.files[fileKey].download} target="_blank" rel="noreferrer" className="flex items-center">
|
|
142
|
+
<CloudDownload className="mr-2" /> Download {/*TODO: use i18n functions*/}
|
|
143
|
+
</a>
|
|
144
|
+
</DropdownMenuItem>
|
|
145
|
+
</DropdownMenuContent>
|
|
146
|
+
</DropdownMenu>
|
|
147
|
+
)
|
|
148
|
+
})}
|
|
149
|
+
|
|
150
|
+
<FavoriteButton id={item.shortId} type={item.type as ResultTypes} label={item.title} />
|
|
151
|
+
|
|
152
|
+
{item.multipleVersions.length > 1 && (
|
|
153
|
+
<Tooltip>
|
|
154
|
+
<TooltipTrigger asChild>
|
|
155
|
+
<Button variant="ghost" size="icon" rounded="full">
|
|
156
|
+
<FileStack /> {JSON.stringify(item.multipleVersions)}
|
|
157
|
+
</Button>
|
|
158
|
+
</TooltipTrigger>
|
|
159
|
+
<TooltipContent>
|
|
160
|
+
|
|
161
|
+
Available in: {item.multipleVersions}
|
|
162
|
+
</TooltipContent>
|
|
163
|
+
</Tooltip>
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
))}
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export default TableWithImageView;
|
|
@@ -79,12 +79,12 @@ const TableView: FC<TableViewProps> = ({ items }) => {
|
|
|
79
79
|
<DropdownMenuContent>
|
|
80
80
|
<DropdownMenuItem>
|
|
81
81
|
<a href={item.files[fileKey].view} target="_blank" rel="noreferrer" className="flex items-center">
|
|
82
|
-
<Eye className="mr-2
|
|
82
|
+
<Eye className="mr-2" /> Open {/*TODO: use i18n functions*/}
|
|
83
83
|
</a>
|
|
84
84
|
</DropdownMenuItem>
|
|
85
85
|
<DropdownMenuItem>
|
|
86
86
|
<a href={item.files[fileKey].download} target="_blank" rel="noreferrer" className="flex items-center">
|
|
87
|
-
<CloudDownload className="mr-2
|
|
87
|
+
<CloudDownload className="mr-2" /> Download {/*TODO: use i18n functions*/}
|
|
88
88
|
</a>
|
|
89
89
|
</DropdownMenuItem>
|
|
90
90
|
</DropdownMenuContent>
|
|
@@ -2,6 +2,7 @@ import { UI_LANG_KEY } from "@c-rex/constants";
|
|
|
2
2
|
import { AVAILABLE_CONTENT_LANG_KEY } from "@c-rex/constants";
|
|
3
3
|
import { CONTENT_LANG_KEY } from "@c-rex/constants";
|
|
4
4
|
import { LanguageAndCountries } from "@c-rex/interfaces";
|
|
5
|
+
import { getCookie, setCookie } from "@c-rex/utils";
|
|
5
6
|
import { create } from "zustand";
|
|
6
7
|
|
|
7
8
|
type LanguageStoreType = {
|
|
@@ -14,33 +15,21 @@ type LanguageStoreType = {
|
|
|
14
15
|
hydrate: () => void;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
|
-
function setCookie(name: string, value: string, days = 365) {
|
|
18
|
-
const expires = new Date(Date.now() + days * 864e5).toUTCString();
|
|
19
|
-
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function getCookie(name: string): string {
|
|
23
|
-
return document.cookie
|
|
24
|
-
.split('; ')
|
|
25
|
-
.find(row => row.startsWith(name + '='))
|
|
26
|
-
?.split('=')[1] || '';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
18
|
export const useLanguageStore = create<LanguageStoreType>((set) => ({
|
|
30
19
|
contentLang: "",
|
|
31
20
|
uiLang: "",
|
|
32
21
|
availableLanguages: [],
|
|
33
22
|
setContentLang: (v) => {
|
|
34
23
|
set({ contentLang: v });
|
|
35
|
-
setCookie(CONTENT_LANG_KEY, v);
|
|
24
|
+
setCookie(CONTENT_LANG_KEY, v, 7);
|
|
36
25
|
},
|
|
37
26
|
setUiLang: (v) => {
|
|
38
27
|
set({ uiLang: v });
|
|
39
|
-
setCookie(UI_LANG_KEY, v);
|
|
28
|
+
setCookie(UI_LANG_KEY, v, 7);
|
|
40
29
|
},
|
|
41
30
|
setAvailableLanguages: (list) => {
|
|
42
31
|
set({ availableLanguages: list });
|
|
43
|
-
setCookie(AVAILABLE_CONTENT_LANG_KEY, JSON.stringify(list));
|
|
32
|
+
setCookie(AVAILABLE_CONTENT_LANG_KEY, JSON.stringify(list), 7);
|
|
44
33
|
},
|
|
45
34
|
hydrate: () => {
|
|
46
35
|
set({
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import React, { FC, JSX } from "react";
|
|
2
|
-
import {
|
|
3
|
-
DropdownMenu as DropdownMenuComp,
|
|
4
|
-
DropdownMenuContent,
|
|
5
|
-
DropdownMenuItem,
|
|
6
|
-
DropdownMenuTrigger,
|
|
7
|
-
} from "@c-rex/ui/dropdown-menu";
|
|
8
|
-
|
|
9
|
-
interface DropdownMenuProps {
|
|
10
|
-
items: {
|
|
11
|
-
format: string;
|
|
12
|
-
link: string;
|
|
13
|
-
}[];
|
|
14
|
-
icon: JSX.Element;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const DropdownMenu: FC<DropdownMenuProps> = ({
|
|
18
|
-
items, icon
|
|
19
|
-
}) => {
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<DropdownMenuComp>
|
|
23
|
-
<DropdownMenuTrigger className="mx-2">
|
|
24
|
-
{icon}
|
|
25
|
-
</DropdownMenuTrigger>
|
|
26
|
-
<DropdownMenuContent>
|
|
27
|
-
{items.map((file, index) => {
|
|
28
|
-
return (
|
|
29
|
-
<DropdownMenuItem key={index}>
|
|
30
|
-
<a href={file.link} target="_blank" rel="noreferrer">
|
|
31
|
-
{file.format}
|
|
32
|
-
</a>
|
|
33
|
-
</DropdownMenuItem>
|
|
34
|
-
)
|
|
35
|
-
})}
|
|
36
|
-
</DropdownMenuContent>
|
|
37
|
-
</DropdownMenuComp>
|
|
38
|
-
);
|
|
39
|
-
};
|
|
File without changes
|
|
File without changes
|