@c-rex/templates 0.1.26 → 0.1.28

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c-rex/templates",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src"
@@ -6,11 +6,12 @@ import { LeftSidebar } from "@c-rex/components/left-sidebar";
6
6
  import { RightSidebar } from "@c-rex/components/right-sidebar";
7
7
  import { Breadcrumb } from "@c-rex/components/breadcrumb";
8
8
  import { RenderArticle } from "@c-rex/components/render-article";
9
- import { articleInfoItemType, DocumentsType, Favorite } from "@c-rex/types";
9
+ import { articleInfoItemType, DocumentsType } from "@c-rex/types";
10
10
  import { AvailableVersionsInterface, TreeOfContent } from "@c-rex/interfaces";
11
11
  import { Separator } from "@c-rex/ui/separator";
12
12
  import { ArrowBigLeft, PanelRight, ArrowBigRight, FileSearchIcon, X, Search } from "lucide-react";
13
- import { SearchInput } from "../../../components/src/navbar/search-input";
13
+ import { SearchInput } from "../../../components/src/search-input";
14
+
14
15
  import { useMultiSidebar } from "@c-rex/ui/sidebar";
15
16
  import { Button } from "@c-rex/ui/button";
16
17
  import { useQueryState } from "nuqs";
@@ -106,36 +107,36 @@ export const ArticleWrapper = ({
106
107
  <RenderArticle htmlContent={htmlContent} contentLang={articleLang} />
107
108
 
108
109
  {/*
109
- <div className="absolute top-0 right-0 flex flex-col gap-2 items-end">
110
- {favoritesList.map((item) => (
111
- <div
112
- key={item.id}
113
- className={cn(
114
- "group h-8 cursor-pointer w-4 hover:w-40 transition-all duration-300 relative",
115
- `bg-${item.color}`,
116
- item.id == id ? "rounded-r-sm" : "rounded-sm"
117
- )}
118
- >
119
- {item.id == id && (
120
- <div className={cn(
121
- "absolute -left-[16px] top-1/2 -translate-y-1/2 h-8 border-t-[16px] border-t-transparent border-b-[16px] border-b-transparent border-r-[16px]",
122
- `border-r-${item.color}`,
123
- )} />
124
- )}
125
- <a
126
- href={`../topics/${item.id}`}
127
- className="flex items-center justify-end h-8 opacity-0 group-hover:opacity-100 text-primary-foreground transition-all duration-300 text-right px-2"
110
+ <div className="absolute top-0 right-0 flex flex-col gap-2 items-end">
111
+ {favoritesList.map((item) => (
112
+ <div
113
+ key={item.id}
114
+ className={cn(
115
+ "group h-8 cursor-pointer w-4 hover:w-40 transition-all duration-300 relative",
116
+ `bg-${item.color}`,
117
+ item.id == id ? "rounded-r-sm" : "rounded-sm"
118
+ )}
128
119
  >
129
- <span
130
- className="text-right text-ellipsis overflow-hidden whitespace-nowrap"
131
- title={item.label}
120
+ {item.id == id && (
121
+ <div className={cn(
122
+ "absolute -left-[16px] top-1/2 -translate-y-1/2 h-8 border-t-[16px] border-t-transparent border-b-[16px] border-b-transparent border-r-[16px]",
123
+ `border-r-${item.color}`,
124
+ )} />
125
+ )}
126
+ <a
127
+ href={`../topics/${item.id}`}
128
+ className="flex items-center justify-end h-8 opacity-0 group-hover:opacity-100 text-primary-foreground transition-all duration-300 text-right px-2"
132
129
  >
133
- {item.label}
134
- </span>
135
- </a>
136
- </div>
137
- ))}
138
- </div>
130
+ <span
131
+ className="text-right text-ellipsis overflow-hidden whitespace-nowrap"
132
+ title={item.label}
133
+ >
134
+ {item.label}
135
+ </span>
136
+ </a>
137
+ </div>
138
+ ))}
139
+ </div>
139
140
  */}
140
141
  </div>
141
142
 
@@ -3,7 +3,6 @@ import { PageWrapper } from "@c-rex/components/page-wrapper";
3
3
  import { OperatorType, WildCardType } from "@c-rex/types";
4
4
  import { InformationUnitsService } from "@c-rex/services";
5
5
  import { informationUnitsResponse } from "@c-rex/interfaces";
6
- import { SearchProvider } from "@c-rex/contexts/search";
7
6
  import { CrexSDK } from "@c-rex/core/sdk";
8
7
 
9
8
  interface HomeProps {
@@ -57,7 +56,7 @@ export const HomeLayout = async ({ searchParams }: HomeProps) => {
57
56
  }
58
57
 
59
58
  return (
60
- <PageWrapper title="" pageType="HOME">
59
+ <PageWrapper>
61
60
  <HomePage data={data} />
62
61
  </PageWrapper>
63
62
  );
package/src/home/page.tsx CHANGED
@@ -7,7 +7,7 @@ import { parseAsBoolean, parseAsInteger, parseAsString, useQueryStates } from 'n
7
7
  import { informationUnitsResponse } from "@c-rex/interfaces";
8
8
  import { Button } from "@c-rex/ui/button";
9
9
  import { Badge } from "@c-rex/ui/badge";
10
- import { ResultList } from "@c-rex/components/result-list";
10
+ import { ResultContainer } from "@c-rex/components/result-container";
11
11
  import { DialogFilter } from "@c-rex/components/dialog-filter";
12
12
  import { AutoComplete } from "@c-rex/components/autocomplete";
13
13
  import { DEVICE_OPTIONS, OPERATOR_OPTIONS } from "@c-rex/constants";
@@ -366,7 +366,7 @@ export const HomePage: FC<HomePageProps> = ({ data }) => {
366
366
  </Sheet>
367
367
 
368
368
  <div className="flex-1">
369
- <ResultList
369
+ <ResultContainer
370
370
  items={data.items}
371
371
  pagination={data.pageInfo}
372
372
  />
@@ -7,7 +7,7 @@ import { parseAsBoolean, parseAsInteger, parseAsString, useQueryStates } from 'n
7
7
  import { informationUnitsResponse } from "@c-rex/interfaces";
8
8
  import { Button } from "@c-rex/ui/button";
9
9
  import { Badge } from "@c-rex/ui/badge";
10
- import { ResultList } from "@c-rex/components/result-list";
10
+ import { ResultContainer } from "@c-rex/components/result-container";
11
11
  import { DialogFilter } from "@c-rex/components/dialog-filter";
12
12
  import { AutoComplete } from "@c-rex/components/autocomplete";
13
13
  import { DEVICE_OPTIONS, OPERATOR_OPTIONS } from "@c-rex/constants";
@@ -373,7 +373,7 @@ export const HomePage: FC<HomePageProps> = ({ data }) => {
373
373
  </Sheet>
374
374
 
375
375
  <div className="flex-1">
376
- <ResultList
376
+ <ResultContainer
377
377
  items={data.items}
378
378
  pagination={data.pageInfo}
379
379
  />
package/src/layout.tsx CHANGED
@@ -8,8 +8,10 @@ import { cookies } from "next/headers";
8
8
  import { CrexSDK } from "@c-rex/core/sdk";
9
9
  import { getIssuerMetadata } from "@c-rex/core/OIDC";
10
10
  import { resolveUILanguage, resolveContentLanguage } from "./utils";
11
- import { LanguageService } from "@c-rex/services";
12
- import { CONTENT_LANG_KEY, UI_LANG_KEY } from "@c-rex/constants";
11
+ import { informationUnitsLanguagesServer } from "@c-rex/services/server-requests";
12
+ import { UI_LANG_KEY } from "@c-rex/constants";
13
+ import { transformLanguageData } from "@c-rex/utils";
14
+ import { getSearchSettingsFromCookie } from "@c-rex/components/search-settings-store";
13
15
 
14
16
  interface DefaultRootLayoutProps {
15
17
  children: React.ReactNode;
@@ -23,15 +25,15 @@ export const DefaultRootLayout = async ({ children }: DefaultRootLayoutProps) =>
23
25
  const uiLangCookie = cookieStore.get(UI_LANG_KEY)?.value ?? null;
24
26
  const clientConfigs = sdk.getClientConfig();
25
27
  const serverConfigs = sdk.getServerConfig();
26
- const languageService = new LanguageService()
27
- const contentLangCookie = cookieStore.get(CONTENT_LANG_KEY)?.value ?? null;
28
+ const searchSettings = getSearchSettingsFromCookie(cookieStore.get("c-rex-search-settings")?.value);
28
29
 
29
30
  sdk.updateConfigProp('OIDC', {
30
31
  ...serverConfigs.OIDC,
31
32
  issuerMetadata: metadata
32
33
  });
33
34
 
34
- const availableLanguages = await languageService.getLanguagesAndCountries();
35
+ const data = await informationUnitsLanguagesServer();
36
+ const availableLanguages = transformLanguageData(data as { value: string; score: number; }[]);
35
37
 
36
38
  const uiLang = resolveUILanguage({
37
39
  uiLangCookie,
@@ -39,7 +41,7 @@ export const DefaultRootLayout = async ({ children }: DefaultRootLayoutProps) =>
39
41
  });
40
42
 
41
43
  const contentLang = resolveContentLanguage({
42
- contentLangCookie,
44
+ contentLangCookie: searchSettings.language,
43
45
  availableLanguages,
44
46
  defaultLang: clientConfigs.languageSwitcher.default
45
47
  });
@@ -66,4 +68,4 @@ export const DefaultRootLayout = async ({ children }: DefaultRootLayoutProps) =>
66
68
  </html>
67
69
  </NuqsAdapter>
68
70
  );
69
- }
71
+ };
package/src/utils.ts CHANGED
@@ -46,4 +46,4 @@ export const resolveContentLanguage = ({
46
46
  if (hasLang) return browserLang;
47
47
 
48
48
  return defaultLang;
49
- }
49
+ }
@@ -1,289 +0,0 @@
1
- import { DirectoryNodesService, InformationUnitsService } from "@c-rex/services";
2
- import { CollectionContext, CollectionResult, DirectoryNodes, informationUnitsItems, informationUnitsResponseItem } from "@c-rex/interfaces";
3
- import * as cheerio from "cheerio"
4
- import { articleInfoItemType, metaTags } from "@c-rex/types";
5
-
6
-
7
- export abstract class BaseArticleCollector {
8
- protected informationService = new InformationUnitsService();
9
- protected directoryNodeService = new DirectoryNodesService();
10
-
11
- private collectVersions: boolean = false;
12
- private collectDocument: boolean = false;
13
- private collectArticleInfo: boolean = false;
14
- private collectDocumentInfo: boolean = false;
15
- private additionalTasks: Map<string, (context: CollectionContext) => Promise<any>> = new Map();
16
- private collectHtmlContent: boolean = false;
17
-
18
- async collect(id: string): Promise<CollectionResult> {
19
- const informationUnitsItem = await this.getInformationUnit(id);
20
- const rootNode = await this.getRootNode(informationUnitsItem.directoryNodes);
21
-
22
- if (!rootNode) throw new Error("Root node not found");
23
-
24
- const context: CollectionContext = {
25
- id,
26
- informationUnitsItem,
27
- rootNode
28
- };
29
-
30
- this.configureTasks();
31
-
32
- const tasks = await this.buildTasks(context);
33
- const results = await Promise.all(tasks);
34
- return this.buildResult(context, this.processResults(results));
35
- }
36
-
37
- protected abstract configureTasks(): void;
38
-
39
-
40
- protected withVersions(): this {
41
- this.collectVersions = true;
42
- return this;
43
- }
44
-
45
- protected withDocument(): this {
46
- this.collectDocument = true;
47
- return this;
48
- }
49
-
50
- protected withHtmlContent(): this {
51
- this.collectHtmlContent = true;
52
- return this;
53
- }
54
-
55
- protected withArticleInfo(): this {
56
- this.collectArticleInfo = true;
57
- return this;
58
- }
59
-
60
- protected withDocumentInfo(): this {
61
- this.collectDocumentInfo = true;
62
- return this;
63
- }
64
-
65
- protected withCustomTask(name: string, task: (context: CollectionContext) => Promise<any>): this {
66
- this.additionalTasks.set(name, task);
67
- return this;
68
- }
69
-
70
- private async buildTasks(context: CollectionContext): Promise<Array<{ name: string, result: any }>> {
71
- const tasks: Array<{ name: string, task: () => Promise<any> }> = [];
72
-
73
- if (this.collectVersions) {
74
- tasks.push({
75
- name: 'articleVersions',
76
- task: () => this.getArticleVersions(context)
77
- });
78
-
79
- tasks.push({
80
- name: 'documentVersions',
81
- task: () => this.getDocumentVersions(context)
82
- });
83
- }
84
-
85
- if (this.collectDocument) {
86
- tasks.push({
87
- name: 'document',
88
- task: () => this.getDocument(context)
89
- });
90
- }
91
-
92
- if (this.collectHtmlContent) {
93
- tasks.push({
94
- name: 'htmlContent',
95
- task: () => this.getHtmlContent(context)
96
- });
97
- }
98
-
99
- for (const [name, taskFn] of this.additionalTasks) {
100
- tasks.push({
101
- name,
102
- task: () => taskFn(context)
103
- });
104
- }
105
-
106
- return Promise.all(
107
- tasks.map(async ({ name, task }) => ({
108
- name,
109
- result: await task()
110
- }))
111
- );
112
- }
113
-
114
- private processResults(results: Array<{ name: string, result: any }>): Record<string, any> {
115
- return results.reduce((acc, { name, result }) => {
116
- acc[name] = result;
117
- return acc;
118
- }, {} as Record<string, any>);
119
- }
120
-
121
- protected async getInformationUnit(id: string) {
122
- return await this.informationService.getItem({
123
- id,
124
- shouldGetAllFields: true
125
- });
126
- }
127
-
128
- protected async getDocument(context: CollectionContext) {
129
- if (context.rootNode.informationUnits[0]) {
130
- const infoId = context.rootNode.informationUnits[0].shortId;
131
- return await this.informationService.getItem({
132
- id: infoId!,
133
- shouldGetAllFields: true
134
- });
135
- }
136
- return null;
137
- }
138
-
139
- protected async getRootNode(directoryNodes: DirectoryNodes[]): Promise<DirectoryNodes | null> {
140
- if (directoryNodes.length === 0 || !directoryNodes[0]) {
141
- return null;
142
- }
143
-
144
- let id = directoryNodes[0].shortId;
145
- let response = await this.directoryNodeService.getItem(id);
146
-
147
- if (!response.ancestors || response.ancestors.length === 0) {
148
- return response;
149
- }
150
-
151
- const noHasInfo = !response.informationUnits?.[0];
152
- const notHasLabel = !response.labels?.[0];
153
-
154
- if (noHasInfo || notHasLabel) return null
155
- if (response.ancestors == undefined) return null
156
- if (response.ancestors[0] == undefined) return null
157
-
158
- const lastIndex = response.ancestors[0].length - 1;
159
-
160
- if (response.ancestors[0][lastIndex] == undefined) return null
161
-
162
- id = response.ancestors[0][lastIndex].shortId;
163
- response = await this.directoryNodeService.getItem(id);
164
-
165
- return response;
166
- }
167
-
168
- protected async getHtmlContent(context: CollectionContext): Promise<string> {
169
- throw new Error("getHtmlContent must be implemented by subclass");
170
- }
171
-
172
- protected async getArticleVersions(context: CollectionContext): Promise<informationUnitsResponseItem[]> {
173
- throw new Error("getArticleVersions must be implemented by subclass");
174
- }
175
-
176
- protected async getDocumentVersions(context: CollectionContext): Promise<informationUnitsResponseItem[]> {
177
- throw new Error("getDocumentVersions must be implemented by subclass");
178
- }
179
-
180
- protected splitHtmlContentAndTags(html: string): { metaTags: metaTags, articleHtml: string } {
181
- const $ = cheerio.load(html)
182
-
183
- const metaTags = $("meta").map((_, el) => {
184
- const name = $(el).attr("name")
185
- const content = $(el).attr("content")
186
- return name && content ? { name, content } : null
187
- }).get().filter(Boolean)
188
-
189
- const articleHtml = $("main").html() || ""
190
-
191
- return {
192
- metaTags,
193
- articleHtml,
194
- }
195
- }
196
-
197
- protected buildResult(context: CollectionContext, data: Record<string, any>): CollectionResult {
198
- const { metaTags, articleHtml } = this.splitHtmlContentAndTags(data.htmlContent);
199
- const result: CollectionResult = {
200
- articleHtml: articleHtml,
201
- metaTags: metaTags,
202
- document: data.document,
203
- article: context.informationUnitsItem,
204
- rootNode: context.rootNode,
205
- articleAvailableVersions: data.articleVersions || [],
206
- documentAvailableVersions: data.documentVersions || [],
207
- }
208
-
209
- if (this.collectArticleInfo) {
210
- result['articleInfo'] = this.processArticleInfo(context.informationUnitsItem)
211
- }
212
-
213
- if (this.collectDocumentInfo) {
214
- result['documentInfo'] = this.processArticleInfo(data.document)
215
- }
216
-
217
- return {
218
- ...result,
219
- ...data
220
- };
221
- }
222
-
223
- protected processArticleInfo = (
224
- informationUnit: informationUnitsItems,
225
- excludeKeys: string[] = ['shortId', 'id']
226
- ): articleInfoItemType[] => {
227
- const result: articleInfoItemType[] = [];
228
-
229
- Object.entries(informationUnit).forEach(([key, value]) => {
230
-
231
- if (excludeKeys.includes(key) || value === null || value === undefined) {
232
- return;
233
- }
234
-
235
- const processedValue = processValue(value);
236
-
237
- if (processedValue !== null) {
238
- result.push({
239
- label: key,
240
- value: processedValue
241
- });
242
- }
243
- });
244
-
245
- return result;
246
- };
247
- }
248
-
249
- const processValue = (value: any): string | null => {
250
-
251
- if (Array.isArray(value)) {
252
- if (value.length === 0) return null;
253
-
254
-
255
- if (value.length === 1) {
256
- return processSingleValue(value[0]);
257
- }
258
-
259
-
260
- const processedValues = value
261
- .map(item => processSingleValue(item))
262
- .filter(item => item !== null);
263
-
264
- return processedValues.length > 0 ? processedValues.join(", ") : null;
265
- }
266
-
267
- return processSingleValue(value);
268
- };
269
-
270
- const processSingleValue = (item: any): string | null => {
271
-
272
- if (item === null || item === undefined) {
273
- return null;
274
- }
275
-
276
- if (typeof item === 'object') {
277
- if ('shortId' in item || 'id' in item) {
278
- return null;
279
- }
280
-
281
- if ('value' in item && item.value !== null && item.value !== undefined) {
282
- return String(item.value);
283
- }
284
-
285
- return null;
286
- }
287
-
288
- return String(item);
289
- };
@@ -1,73 +0,0 @@
1
- import { BaseArticleCollector } from './BaseArticleCollector';
2
- import { CollectionContext, informationUnitsResponseItem } from "@c-rex/interfaces";
3
- import { RenditionsService } from "@c-rex/services";
4
- import { DocumentsType } from '@c-rex/types';
5
- import { getFileRenditions } from '@c-rex/utils';
6
-
7
- export class DocumentArticleCollector extends BaseArticleCollector {
8
- private renditionService = new RenditionsService();
9
-
10
- protected configureTasks(): void {
11
- this.withVersions()
12
- .withDocument()
13
- .withHtmlContent()
14
- .withArticleInfo()
15
- .withDocumentInfo()
16
- .withCustomTask('attachments', this.getAttachments.bind(this))
17
- }
18
-
19
- protected async getHtmlContent(context: CollectionContext): Promise<string> {
20
- if (!context.rootNode.childNodes[0]) {
21
- return "";
22
- }
23
- const directoryId = context.rootNode.childNodes[0].shortId;
24
- const response = await this.directoryNodeService.getItem(directoryId);
25
-
26
- const infoId = response.informationUnits[0]?.shortId as string
27
- const childInformationUnit = await this.informationService.getItem({
28
- id: infoId
29
- });
30
-
31
- return await this.renditionService.getHTMLRendition({
32
- renditions: childInformationUnit.renditions
33
- });
34
- }
35
-
36
- protected async getDocumentVersions(context: CollectionContext): Promise<informationUnitsResponseItem[]> {
37
- const versions = await this.informationService.getList({
38
- restrict: [`versionOf.shortId=${context.informationUnitsItem.versionOf.shortId}`],
39
- fields: ["renditions", "class", "languages", "labels"],
40
- })
41
-
42
- return versions.items;
43
- }
44
-
45
- protected async getArticleVersions(context: CollectionContext): Promise<informationUnitsResponseItem[]> {
46
- if (!context.rootNode.childNodes[0]) {
47
- return [];
48
- }
49
- const directoryId = context.rootNode.childNodes[0].shortId;
50
- const response = await this.directoryNodeService.getItem(directoryId);
51
-
52
- const infoId = response.informationUnits[0]?.shortId as string
53
- const childInformationUnit = await this.informationService.getItem({
54
- id: infoId
55
- });
56
-
57
- const versions = await this.informationService.getList({
58
- restrict: [`versionOf.shortId=${childInformationUnit.versionOf.shortId}`],
59
- fields: ["renditions", "class", "languages", "labels"],
60
- })
61
-
62
- return versions.items;
63
- }
64
-
65
- private async getAttachments(context: CollectionContext): Promise<DocumentsType> {
66
- const rootNodeInfoID = context.rootNode.informationUnits[0]?.shortId as string
67
- const childInformationUnit = await this.informationService.getItem({
68
- id: rootNodeInfoID
69
- });
70
-
71
- return getFileRenditions({ renditions: childInformationUnit.renditions });
72
- }
73
- }
@@ -1,57 +0,0 @@
1
- import { BaseArticleCollector } from './BaseArticleCollector';
2
- import { CollectionContext, informationUnitsResponseItem } from "@c-rex/interfaces";
3
- import { RenditionsService } from "@c-rex/services";
4
- import { DocumentsType } from '@c-rex/types';
5
- import { getFileRenditions } from '@c-rex/utils';
6
-
7
- export class TopicArticleCollector extends BaseArticleCollector {
8
- private renditionService = new RenditionsService();
9
-
10
- protected configureTasks(): void {
11
- this.withVersions()
12
- .withDocument()
13
- .withHtmlContent()
14
- .withArticleInfo()
15
- .withDocumentInfo()
16
- .withCustomTask('attachments', this.getAttachments.bind(this))
17
-
18
- }
19
-
20
- protected async getHtmlContent(context: CollectionContext): Promise<string> {
21
- return await this.renditionService.getHTMLRendition({
22
- renditions: context.informationUnitsItem.renditions
23
- });
24
- }
25
-
26
- protected async getDocumentVersions(context: CollectionContext): Promise<informationUnitsResponseItem[]> {
27
- const rootNodeInfoID = context.rootNode.informationUnits[0]?.shortId as string
28
- const childInformationUnit = await this.informationService.getItem({
29
- id: rootNodeInfoID
30
- });
31
-
32
- const versions = await this.informationService.getList({
33
- restrict: [`versionOf.shortId=${childInformationUnit.versionOf.shortId}`],
34
- fields: ["renditions", "class", "languages", "labels"],
35
- })
36
-
37
- return versions.items;
38
- }
39
-
40
- protected async getArticleVersions(context: CollectionContext): Promise<informationUnitsResponseItem[]> {
41
- const versions = await this.informationService.getList({
42
- restrict: [`versionOf.shortId=${context.informationUnitsItem.versionOf.shortId}`],
43
- fields: ["renditions", "class", "languages", "labels"],
44
- })
45
-
46
- return versions.items;
47
- }
48
-
49
- private async getAttachments(context: CollectionContext): Promise<DocumentsType> {
50
- const rootNodeInfoID = context.rootNode.informationUnits[0]?.shortId as string
51
- const childInformationUnit = await this.informationService.getItem({
52
- id: rootNodeInfoID
53
- });
54
-
55
- return getFileRenditions({ renditions: childInformationUnit.renditions });
56
- }
57
- }
@@ -1,105 +0,0 @@
1
- "use client";
2
-
3
- import React, { FC } 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 { useBreakpoint } from "@c-rex/ui/hooks";
19
- import { DEVICE_OPTIONS } from "@c-rex/constants";
20
- import { SheetContent, SheetHeader } from "@c-rex/ui/sheet";
21
-
22
- interface FilterSidebarProps {
23
- tags: { [key: string]: any[] }
24
- totalItemCount: number
25
- updateFilterParam: (key: string, item: any) => void
26
- }
27
-
28
- export const FilterSidebar: FC<FilterSidebarProps> = ({ tags, totalItemCount, updateFilterParam }) => {
29
- const t = useTranslations();
30
- const device = useBreakpoint();
31
- const isMobile = device !== null && (device === DEVICE_OPTIONS.MOBILE);
32
-
33
- if (Object.keys(tags).length === 0) return null;
34
-
35
- const content = (
36
- <SidebarContent className="!gap-0">
37
-
38
- {Object.entries(tags).map(([key, value]: any) => (
39
-
40
- <Collapsible defaultOpen key={key} className="py-0 group/collapsible">
41
- <SidebarGroup>
42
-
43
- <SidebarGroupLabel asChild className="hover:bg-sidebar-accent text-sidebar-accent-foreground text-sm font-bold">
44
- <CollapsibleTrigger className="!h-9">
45
- {t(`filter.tags.${key}`)}
46
- <ChevronDown className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
47
- </CollapsibleTrigger>
48
- </SidebarGroupLabel>
49
-
50
- <CollapsibleContent>
51
- <SidebarGroupContent>
52
- <SidebarMenu>
53
- <SidebarMenuSub>
54
- {value.map((item: any) => (
55
- <SidebarMenuSubItem key={item.shortId}>
56
- <SidebarMenuSubButton
57
- className="cursor-pointer"
58
- isActive={item.active}
59
- onClick={() => updateFilterParam(key, item)}
60
- >
61
- {item.label} ({item.hits}/{item.total})
62
- {item.active && <Check className="ml-2" />}
63
- </SidebarMenuSubButton>
64
- </SidebarMenuSubItem>
65
- ))}
66
- </SidebarMenuSub>
67
- </SidebarMenu>
68
- </SidebarGroupContent>
69
- </CollapsibleContent>
70
- </SidebarGroup>
71
- </Collapsible>
72
- ))}
73
- </SidebarContent>
74
- )
75
-
76
- if (isMobile) {
77
- return (
78
-
79
- <SheetContent
80
- side="left"
81
- className="!pt-6 !px-2 w-[400px] overflow-y-auto"
82
- >
83
- <SheetHeader className="justify-center items-end font-bold">
84
- {t("filter.filters")}
85
- <span className="text-xs text-muted-foreground leading-5">
86
- {totalItemCount} {t("results.results")}
87
- </span>
88
- </SheetHeader>
89
- {content}
90
- </SheetContent>
91
- )
92
- }
93
-
94
- return (
95
- <div className="w-60 lg:w-80 bg-sidebar rounded-md border pb-4">
96
- <SidebarHeader className="justify-center items-end font-bold">
97
- {t("filter.filters")}
98
- <span className="text-xs text-muted-foreground leading-5">
99
- {totalItemCount} {t("results.results")}
100
- </span>
101
- </SidebarHeader>
102
- {content}
103
- </div>
104
- );
105
- };