@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.
Files changed (53) hide show
  1. package/package.json +78 -62
  2. package/src/article/article-action-bar.tsx +89 -0
  3. package/src/article/article-content.tsx +55 -0
  4. package/src/autocomplete.tsx +55 -50
  5. package/src/breadcrumb.tsx +3 -1
  6. package/src/directoryNodes/tree-of-content.tsx +49 -0
  7. package/src/{bookmark-button.tsx → favorites/bookmark-button.tsx} +12 -3
  8. package/src/{favorite-button.tsx → favorites/favorite-button.tsx} +1 -1
  9. package/src/generated/client-components.tsx +1350 -0
  10. package/src/generated/create-client-request.tsx +105 -0
  11. package/src/generated/create-server-request.tsx +61 -0
  12. package/src/generated/create-suggestions-request.tsx +56 -0
  13. package/src/generated/server-components.tsx +1056 -0
  14. package/src/generated/suggestions.tsx +299 -0
  15. package/src/info/bookmark.tsx +51 -0
  16. package/src/info/info-table.tsx +127 -60
  17. package/src/info/shared.tsx +1 -1
  18. package/src/navbar/language-switcher/shared.tsx +1 -1
  19. package/src/navbar/navbar.tsx +1 -1
  20. package/src/{stories → navbar/stories}/navbar.stories.tsx +1 -1
  21. package/src/page-wrapper.tsx +1 -1
  22. package/src/renditions/file-download.tsx +84 -0
  23. package/src/renditions/html.tsx +55 -0
  24. package/src/renditions/image/container.tsx +52 -0
  25. package/src/renditions/image/rendition.tsx +61 -0
  26. package/src/{dialog-filter.tsx → results/dialog-filter.tsx} +22 -23
  27. package/src/results/filter-navbar.tsx +241 -0
  28. package/src/results/filter-sidebar/index.tsx +125 -0
  29. package/src/results/filter-sidebar/utils.ts +164 -0
  30. package/src/{pagination.tsx → results/pagination.tsx} +12 -10
  31. package/src/results/result-container.tsx +70 -0
  32. package/src/{stories/blog-view.stories.tsx → results/stories/cards.stories.tsx} +1 -1
  33. package/src/{stories/table-view.stories.tsx → results/stories/table.stories.tsx} +1 -1
  34. package/src/results/table-with-images.tsx +140 -0
  35. package/src/{result-view → results}/table.tsx +1 -2
  36. package/src/results/utils.ts +67 -0
  37. package/src/{navbar/search-input.tsx → search-input.tsx} +9 -6
  38. package/src/share-button.tsx +49 -0
  39. package/src/stores/search-settings-store.ts +1 -1
  40. package/src/blur-image.tsx +0 -23
  41. package/src/left-sidebar.tsx +0 -90
  42. package/src/result-list.tsx +0 -43
  43. package/src/result-view/table-with-images.tsx +0 -199
  44. package/src/right-sidebar.tsx +0 -70
  45. package/src/search-modal.tsx +0 -140
  46. package/src/stories/blur-image.stories.tsx +0 -51
  47. package/src/stories/sidebar.stories.tsx +0 -94
  48. /package/src/{file-icon.tsx → icons/file-icon.tsx} +0 -0
  49. /package/src/{flag.tsx → icons/flag-icon.tsx} +0 -0
  50. /package/src/{loading.tsx → icons/loading.tsx} +0 -0
  51. /package/src/{result-view/blog.tsx → results/cards.tsx} +0 -0
  52. /package/src/{empty.tsx → results/empty.tsx} +0 -0
  53. /package/src/{stories → results/stories}/empty.stories.tsx +0 -0
@@ -0,0 +1,84 @@
1
+ import { FC } from "react";
2
+ import { RenditionModel } from "@c-rex/interfaces";
3
+ import {
4
+ DropdownMenu,
5
+ DropdownMenuContent,
6
+ DropdownMenuItem,
7
+ DropdownMenuTrigger
8
+ } from "@c-rex/ui/dropdown-menu";
9
+ import { Button } from "@c-rex/ui/button";
10
+ import { FileIcon } from "../icons/file-icon";
11
+ import { CloudDownload, Eye } from "lucide-react";
12
+
13
+
14
+ interface OpenOrDownloadFileDropdown {
15
+ renditions: RenditionModel[] | null | undefined;
16
+ }
17
+
18
+ export const OpenOrDownloadFileDropdown: FC<OpenOrDownloadFileDropdown> = async ({ renditions }) => {
19
+
20
+ if (renditions == null || renditions.length == 0) return null;
21
+
22
+ const result: {
23
+ [key: string]: {
24
+ view: string;
25
+ download: string;
26
+ }
27
+ } = {}
28
+
29
+ renditions.forEach((item) => {
30
+ const key = item.format!
31
+
32
+ if (result[key] == undefined) {
33
+ result[key] = {
34
+ view: "",
35
+ download: ""
36
+ }
37
+ }
38
+ const download = item.links?.filter((link) => link.rel == "download")[0]?.href
39
+ const view = item.links?.filter((link) => link.rel == "view")[0]?.href
40
+
41
+ if (download != null && download != undefined) {
42
+ result[key].download = download
43
+ }
44
+
45
+ if (view != null && view != undefined) {
46
+ result[key].view = view
47
+ }
48
+ })
49
+
50
+ return (
51
+ <>
52
+ {Object.keys(result).map((fileKey, idx) => {
53
+ const file = result[fileKey];
54
+ if (!file) return null;
55
+
56
+ return (
57
+ <DropdownMenu key={idx}>
58
+ <DropdownMenuTrigger asChild>
59
+ <Button variant="ghost" size="icon">
60
+ <FileIcon extension={fileKey} />
61
+ </Button>
62
+ </DropdownMenuTrigger>
63
+ <DropdownMenuContent>
64
+ {file.view && (
65
+ <DropdownMenuItem asChild>
66
+ <a href={file.view} target="_blank" rel="noreferrer" className="flex items-center">
67
+ <Eye className="mr-2" /> Open
68
+ </a>
69
+ </DropdownMenuItem>
70
+ )}
71
+ {file.download && (
72
+ <DropdownMenuItem asChild>
73
+ <a href={file.download} target="_blank" rel="noreferrer" className="flex items-center">
74
+ <CloudDownload className="mr-2" /> Download
75
+ </a>
76
+ </DropdownMenuItem>
77
+ )}
78
+ </DropdownMenuContent>
79
+ </DropdownMenu>
80
+ );
81
+ })}
82
+ </>
83
+ )
84
+ }
@@ -0,0 +1,55 @@
1
+ import { FC } from "react";
2
+ import * as cheerio from "cheerio"
3
+ import { FragmentsGetAll } from "../generated/server-components";
4
+
5
+ interface HtmlRenditionProps {
6
+ htmlFormats?: string[]
7
+ shortId: string
8
+ }
9
+
10
+ export const HtmlRendition: FC<HtmlRenditionProps> = ({
11
+ shortId,
12
+ htmlFormats = ["application/xhtml+xml", "application/html", "text/html"]
13
+ }) => {
14
+ const empty = <div>No rendition available</div>;
15
+
16
+ return (
17
+ <FragmentsGetAll
18
+ queryParams={{
19
+ Fields: ["titles", "renditions"],
20
+ Embed: ["renditions"],
21
+ PageSize: 1,
22
+ Links: true,
23
+ Restrict: [
24
+ `informationUnits=${shortId}`,
25
+ ...htmlFormats.map(format => `renditions.format=${format}`)
26
+ ],
27
+
28
+ }}
29
+ render={async (data, error) => {
30
+ if (error) {
31
+ return <div>Error loading content</div>;
32
+ }
33
+
34
+ const renditions = data?.items?.[0]?.renditions;
35
+
36
+ if (renditions == null || renditions.length == 0) return empty;
37
+ if (renditions.length == 0 || renditions[0] == undefined || renditions[0].links == undefined) return empty;
38
+
39
+ const filteredLinks = renditions[0].links.filter((item) => item.rel == "view");
40
+
41
+ if (filteredLinks.length == 0 || filteredLinks[0] == undefined || filteredLinks[0].href == undefined) return empty;
42
+
43
+ const url = filteredLinks[0].href;
44
+ const html = await fetch(url).then(res => res.text());
45
+
46
+
47
+ const $ = cheerio.load(html)
48
+
49
+ const articleHtml = $("body").html() || ""
50
+
51
+ return <div dangerouslySetInnerHTML={{ __html: articleHtml }} />;
52
+ }}
53
+ />
54
+ )
55
+ }
@@ -0,0 +1,52 @@
1
+ "use client"
2
+
3
+ import { FC } from "react";
4
+ import { FragmentsGetAllClient } from "../../generated/client-components";
5
+ import { ImageRendition } from "./rendition";
6
+ import { Skeleton } from "@c-rex/ui/skeleton";
7
+
8
+ interface ImageContainerProps {
9
+ itemShortId: string;
10
+ imageRestrictions?: string[];
11
+ imageFormats?: string[];
12
+ emptyImageStyle?: string;
13
+ imageStyle?: string;
14
+ }
15
+
16
+ export const ImageRenditionContainer: FC<ImageContainerProps> = ({
17
+ itemShortId,
18
+ imageRestrictions,
19
+ emptyImageStyle,
20
+ imageStyle,
21
+ imageFormats = ["image/svg+xml", "image/gif", "image/png", "image/jpg"],
22
+ }) => {
23
+ const newRestrictions = [
24
+ `informationUnits=${itemShortId}`,
25
+ //...imageFormats.map(format => `renditions.format=${format}`)
26
+ ];
27
+
28
+ return (
29
+ <FragmentsGetAllClient
30
+ queryParams={{
31
+ Fields: ["titles", "renditions"],
32
+ Embed: ["renditions"],
33
+ PageSize: 1,
34
+ Links: true,
35
+ Restrict: newRestrictions,
36
+ }}
37
+ >
38
+ {({ data, isLoading }) => (
39
+ isLoading ? (
40
+ <Skeleton className="w-full h-full" />
41
+ ) : (
42
+ <ImageRendition
43
+ items={data?.items as any}
44
+ formats={imageFormats}
45
+ emptyImageStyle={emptyImageStyle}
46
+ imageStyle={imageStyle}
47
+ />
48
+ )
49
+ )}
50
+ </FragmentsGetAllClient>
51
+ )
52
+ }
@@ -0,0 +1,61 @@
1
+ "use client"
2
+
3
+ import { FC } from "react";
4
+ import { ImageOff } from "lucide-react";
5
+ import { DocumentModel, ExternalProductGraphicModel, FragmentModel, InformationUnitModel, PackageModel, RenditionModel, TopicModel } from "@c-rex/interfaces";
6
+ import { cn } from "@c-rex/utils";
7
+ interface ImageRenditionProps {
8
+ items: (
9
+ DocumentModel |
10
+ ExternalProductGraphicModel |
11
+ FragmentModel |
12
+ InformationUnitModel |
13
+ PackageModel |
14
+ TopicModel
15
+ )[];
16
+ emptyImageStyle?: string;
17
+ imageStyle?: string;
18
+ formats: string[];
19
+ }
20
+
21
+ export const ImageRendition: FC<ImageRenditionProps> = ({ items, formats, emptyImageStyle, imageStyle }) => {
22
+ if (!items || items.length === 0) return <ImageOff className={cn(emptyImageStyle, "text-muted")} />;
23
+
24
+ const item = items[0];
25
+ if (!item || item == undefined) return <ImageOff className={cn(emptyImageStyle, "text-muted")} />;
26
+
27
+ const wanted = formats.map((f) => f.toLowerCase());
28
+ let rendition = null
29
+ const renditions = items[0]?.renditions as RenditionModel[] || [];
30
+
31
+
32
+ for (const fmt of wanted) {
33
+
34
+ const found = renditions.find((r) => r.format?.toLowerCase() === fmt);
35
+
36
+ if (found) {
37
+ rendition = found;
38
+ break;
39
+ }
40
+ }
41
+
42
+ if (rendition == null) return <ImageOff className={cn(emptyImageStyle, "text-muted")} />;
43
+
44
+ const src =
45
+ rendition.links?.find((l) => l.rel === "view")?.href ??
46
+ rendition.links?.find((l) => l.rel === "download")?.href ??
47
+ rendition.links?.find((l) => l.rel === "resource")?.href ??
48
+ rendition.source ??
49
+ "";
50
+
51
+ if (!src) return <ImageOff className={cn(emptyImageStyle, "text-muted")} />;
52
+
53
+ const alt =
54
+ item.labels?.[0]?.value ??
55
+ item.titles?.[0]?.value ??
56
+ "Document image";
57
+
58
+ return (
59
+ <img src={src} alt={alt} loading="lazy" className={cn(imageStyle, "w-full")} />
60
+ );
61
+ }
@@ -1,3 +1,5 @@
1
+ "use client"
2
+
1
3
  import React, { FC, useEffect, useState } from "react"
2
4
  import { Button } from "@c-rex/ui/button"
3
5
  import {
@@ -17,9 +19,9 @@ import { useTranslations } from "next-intl"
17
19
  import { LanguageAndCountries } from "@c-rex/interfaces"
18
20
  import { WILD_CARD_OPTIONS, OPERATOR_OPTIONS } from "@c-rex/constants"
19
21
  import { Switch } from "@c-rex/ui/switch"
20
- import { useSearchSettingsStore } from "./stores/search-settings-store"
22
+ import { useSearchSettingsStore } from "../stores/search-settings-store"
21
23
  import { OperatorType, WildCardType } from "@c-rex/types"
22
- import { useLanguageStore } from "./stores/language-store"
24
+ import { useLanguageStore } from "../stores/language-store"
23
25
 
24
26
  interface DialogFilterProps {
25
27
  trigger: React.ReactNode;
@@ -32,30 +34,31 @@ interface Languages {
32
34
 
33
35
  export const DialogFilter: FC<DialogFilterProps> = ({ trigger }) => {
34
36
  const t = useTranslations("filter");
35
-
36
- const savedLike = useSearchSettingsStore((state) => state.like)
37
- const savedOperator = useSearchSettingsStore((state) => state.operator)
38
- const savedWildcard = useSearchSettingsStore((state) => state.wildcard)
39
- const savedLanguages = useLanguageStore(state => state.contentLang);
37
+ const contentLang = useLanguageStore(state => state.contentLang);
38
+ const searchLanguages = useSearchSettingsStore(state => state.language);
40
39
  const availableLanguagesAndCountries = useLanguageStore(state => state.availableLanguages);
41
40
 
42
- const [like, setLike] = useState<boolean>(savedLike);
43
- const [operator, setOperator] = useState<string>(savedOperator);
44
- const [wildcard, setWildcard] = useState<string>(savedWildcard);
41
+ const [like, setLike] = useState<boolean>(useSearchSettingsStore.getState().like);
42
+ const [operator, setOperator] = useState<string>(useSearchSettingsStore.getState().operator);
43
+ const [wildcard, setWildcard] = useState<string>(useSearchSettingsStore.getState().wildcard);
44
+ const [languages, setLanguages] = useState<Languages[]>([]);
45
45
 
46
- const generateLanguagesList = (): Languages[] => {
47
- return availableLanguagesAndCountries?.map((item: LanguageAndCountries) => {
48
- const checked = savedLanguages.includes(item.value)
46
+ useEffect(() => {
47
+ const aux: Languages[] = availableLanguagesAndCountries.map((item: LanguageAndCountries) => {
48
+ const checked = searchLanguages.length === 0
49
+ ? item.value === contentLang
50
+ : searchLanguages.includes(item.value);
49
51
 
50
52
  return {
51
53
  lang: item.lang,
52
54
  value: item.value,
53
- checked,
55
+ checked: checked,
54
56
  }
55
- })
56
- }
57
+ });
58
+
59
+ setLanguages(aux);
60
+ }, [availableLanguagesAndCountries, contentLang]);
57
61
 
58
- const [languages, setLanguages] = useState<Languages[]>(generateLanguagesList());
59
62
  const [params, setParams] = useQueryStates({
60
63
  search: parseAsString,
61
64
  language: parseAsString,
@@ -99,10 +102,6 @@ export const DialogFilter: FC<DialogFilterProps> = ({ trigger }) => {
99
102
  });
100
103
  }
101
104
 
102
- useEffect(() => {
103
- setLanguages(generateLanguagesList())
104
- }, [availableLanguagesAndCountries])
105
-
106
105
  return (
107
106
  <Dialog>
108
107
  <DialogTrigger asChild>
@@ -118,13 +117,13 @@ export const DialogFilter: FC<DialogFilterProps> = ({ trigger }) => {
118
117
  {t("languages")}:
119
118
  </Label>
120
119
  <div className="flex items-center">
121
- {languages?.map((item: any, index: any) => {
120
+ {languages?.map((item, index) => {
122
121
  return (
123
122
  <div key={index} className="mr-4">
124
123
  <Checkbox
125
124
  id={`available-languages${item.lang}`}
126
125
  className="mr-2"
127
- {...({ checked: item.checked })}
126
+ checked={item.checked}
128
127
  onCheckedChange={(checked: boolean) => onChangeCheckbox({
129
128
  ...item,
130
129
  checked: checked,
@@ -0,0 +1,241 @@
1
+ "use client"
2
+ import { FC, useMemo } from "react";
3
+ import { useTranslations } from "next-intl";
4
+ import { parseAsBoolean, parseAsInteger, parseAsString, useQueryStates } from "nuqs";
5
+ import { Button } from "@c-rex/ui/button";
6
+ import { Funnel, X } from "lucide-react";
7
+ import { Badge } from "@c-rex/ui/badge";
8
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@c-rex/ui/tooltip";
9
+ import { OPERATOR_OPTIONS } from "@c-rex/constants";
10
+ import { Tags } from "@c-rex/interfaces";
11
+ import { memoizeFilteredTags } from "./filter-sidebar/utils";
12
+
13
+ type filterModel = {
14
+ key: string
15
+ name?: string
16
+ value: string
17
+ default?: string | boolean | null
18
+ removable: boolean
19
+ }
20
+
21
+ type FilterNavbarProps = {
22
+ tags?: Tags
23
+ }
24
+
25
+ export const FilterNavbar: FC<FilterNavbarProps> = ({ tags }) => {
26
+ const t = useTranslations()
27
+ const [params, setParams] = useQueryStates({
28
+ language: parseAsString,
29
+ page: parseAsInteger,
30
+ wildcard: parseAsString,
31
+ operator: parseAsString,
32
+ packages: parseAsString,
33
+ filter: parseAsString,
34
+ like: parseAsBoolean,
35
+ search: {
36
+ defaultValue: "",
37
+ parse(value) {
38
+ return value
39
+ },
40
+ },
41
+ }, {
42
+ history: 'push',
43
+ shallow: false,
44
+ });
45
+ const isMobile = false
46
+
47
+
48
+ const filteredTags = useMemo(() => {
49
+ return memoizeFilteredTags(tags, params.filter, params.packages);
50
+ }, [tags, params.filter, params.packages]);
51
+
52
+ const filters = useMemo(() => {
53
+ const filters: filterModel[] = [{
54
+ key: "operator",
55
+ name: t("filter.operator"),
56
+ value: `${params?.operator !== OPERATOR_OPTIONS.OR}`,
57
+ removable: false
58
+ }, {
59
+ key: "like",
60
+ name: t("filter.like"),
61
+ value: `${params.like}`,
62
+ removable: false
63
+ }, {
64
+ key: "wildcard",
65
+ name: t("filter.wildcard"),
66
+ value: params.wildcard as string,
67
+ removable: false,
68
+ }]
69
+
70
+ if (params.language !== null) {
71
+ const languages = params.language.split(",")
72
+ languages.forEach((item) => {
73
+ const aux = languages.filter(langItem => langItem !== item)
74
+ filters.push({
75
+ key: "language",
76
+ name: t(`languages`),
77
+ value: item.toUpperCase(),
78
+ removable: languages.length > 1,
79
+ default: aux.join(",")
80
+ })
81
+ })
82
+ }
83
+
84
+ if (params.filter !== null) {
85
+ const splittedParam = params.filter.split(",")
86
+
87
+ splittedParam.forEach((item, index) => {
88
+ const aux = item.split(".shortId=")
89
+ const name = aux[0] as string
90
+ const shortId = aux[1] as string
91
+
92
+ const defaultValue = [...splittedParam]
93
+ defaultValue.splice(index, 1)
94
+
95
+ if (!Object.keys(filteredTags).includes(name) || !filteredTags[name]) return;
96
+
97
+ const tag = filteredTags[name].find(el => el.shortId === shortId)
98
+ if (!tag) return;
99
+
100
+ const value = defaultValue.length == 0 ? null : defaultValue.join(",")
101
+
102
+ filters.push({
103
+ key: "filter",
104
+ name: t(`filter.tags.${name}`),
105
+ value: tag.label,
106
+ removable: true,
107
+ default: value
108
+ })
109
+ })
110
+ }
111
+
112
+ if (params.packages !== null) {
113
+ let label = params.packages
114
+
115
+ if (filteredTags["packages"]) {
116
+ const aux = filteredTags["packages"].find(el => el.shortId === params.packages)
117
+
118
+ if (aux) {
119
+ label = aux.label
120
+ }
121
+ }
122
+
123
+ filters.push({
124
+ key: "packages",
125
+ name: t("filter.tags.packages"),
126
+ value: label,
127
+ removable: true,
128
+ default: null
129
+ })
130
+ }
131
+
132
+ Object.keys(params)
133
+ .filter(item => !["page", "search", "language", "operator", "like", "wildcard", "filter", "packages"].includes(item))
134
+ .forEach(item => {
135
+ if (params[item as keyof typeof params] === null || params[item as keyof typeof params] === undefined) return;
136
+ const value = params[item as keyof typeof params] as string
137
+ filters.push({ key: item, value: value, removable: true, default: null })
138
+ })
139
+
140
+ return filters
141
+ }, [filteredTags, params]);
142
+
143
+ if (filters == null || filters.length === 0) {
144
+ return null;
145
+ }
146
+
147
+ return (
148
+ <div className="pb-4 flex justify-between">
149
+
150
+ <div className="flex flex-wrap gap-2">
151
+ <Button
152
+ size="sm"
153
+ variant="secondary"
154
+ //onClick={() => setOpen(true)}
155
+ className="md:hidden"
156
+ >
157
+ <Funnel className="h-2" />
158
+ </Button>
159
+
160
+ {filters.length > 0 && (
161
+ <>
162
+ {filters.slice(0, 1).map((item) => (
163
+ <Badge
164
+ key={`${item.key}-${item?.value}`}
165
+ variant="outline"
166
+ className="h-8"
167
+ >
168
+ {item?.name ? item.name : item.key}: {item.value}
169
+
170
+ {item.removable && (
171
+ <Button size="xs" variant="ghost" onClick={() => {
172
+ setParams({ [item.key]: item?.default })
173
+ }}>
174
+ <X className="h-2" />
175
+ </Button>
176
+ )}
177
+ </Badge>
178
+ ))}
179
+
180
+
181
+ {isMobile ? (
182
+ <TooltipProvider>
183
+ <Tooltip delayDuration={100}>
184
+ <TooltipTrigger>
185
+ <Badge
186
+ key={`grouped-filters`}
187
+ variant="outline"
188
+ className="h-8"
189
+ >
190
+ +{filters.length - 1} {t("filter.filters")}
191
+ </Badge>
192
+
193
+ </TooltipTrigger>
194
+
195
+ <TooltipContent>
196
+ {filters.slice(1).map((item) => {
197
+ const label = item?.name ? item.name : item.key
198
+ const returnString = `${label}: ${item.value}`;
199
+ return <div className="capitalize" key={returnString}>{returnString}</div>;
200
+ })}
201
+ </TooltipContent>
202
+ </Tooltip>
203
+ </TooltipProvider>
204
+ ) : (
205
+ <>
206
+ {filters.slice(1).map((item) => (
207
+ <Badge
208
+ key={`${item.key}-${item?.value}`}
209
+ variant="outline"
210
+ className="h-8"
211
+ >
212
+ {item?.name ? item.name : item.key}: {item.value}
213
+
214
+ {item.removable && (
215
+ <Button size="xs" variant="ghost" onClick={() => {
216
+ setParams({ [item.key]: item?.default })
217
+ }}>
218
+ <X className="h-2" />
219
+ </Button>
220
+ )}
221
+ </Badge>
222
+ ))}
223
+ </>
224
+ )}
225
+ </>
226
+ )}
227
+ </div>
228
+
229
+ <Button
230
+ size="sm"
231
+ variant="secondary"
232
+ onClick={() => {
233
+ setParams({ filter: null, packages: null })
234
+ }}
235
+ >
236
+ {t("reset")}
237
+ </Button>
238
+
239
+ </div>
240
+ );
241
+ };