Improved the filterHasAttributes + Chip
This commit is contained in:
		
							parent
							
								
									ae25df8d72
								
							
						
					
					
						commit
						de3f385458
					
				| @ -188,14 +188,13 @@ export const AppLayout = ({ | ||||
|     return memo; | ||||
|   }, [router.locale, router.locales]); | ||||
| 
 | ||||
|   const currencyOptions = useMemo(() => { | ||||
|     const memo: string[] = []; | ||||
|     filterHasAttributes(currencies).map((currentCurrency) => { | ||||
|       if (isDefinedAndNotEmpty(currentCurrency.attributes.code)) | ||||
|         memo.push(currentCurrency.attributes.code); | ||||
|     }); | ||||
|     return memo; | ||||
|   }, [currencies]); | ||||
|   const currencyOptions = useMemo( | ||||
|     () => | ||||
|       filterHasAttributes(currencies, ["attributes"] as const).map( | ||||
|         (currentCurrency) => currentCurrency.attributes.code | ||||
|       ), | ||||
|     [currencies] | ||||
|   ); | ||||
| 
 | ||||
|   const [currencySelect, setCurrencySelect] = useState<number>(-1); | ||||
| 
 | ||||
|  | ||||
| @ -7,12 +7,12 @@ import { cJoin } from "helpers/className"; | ||||
| 
 | ||||
| interface Props { | ||||
|   className?: string; | ||||
|   children: React.ReactNode; | ||||
|   text: string; | ||||
| } | ||||
| 
 | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export const Chip = ({ className, children }: Props): JSX.Element => ( | ||||
| export const Chip = ({ className, text }: Props): JSX.Element => ( | ||||
|   <div | ||||
|     className={cJoin( | ||||
|       `grid place-content-center place-items-center whitespace-nowrap rounded-full
 | ||||
| @ -21,6 +21,6 @@ export const Chip = ({ className, children }: Props): JSX.Element => ( | ||||
|       className | ||||
|     )} | ||||
|   > | ||||
|     {children} | ||||
|     {text} | ||||
|   </div> | ||||
| ); | ||||
|  | ||||
| @ -103,7 +103,7 @@ export const ScanSet = ({ | ||||
|     }); | ||||
| 
 | ||||
|   const pages = useMemo( | ||||
|     () => selectedScan && filterHasAttributes(selectedScan.pages?.data), | ||||
|     () => filterHasAttributes(selectedScan?.pages?.data, ["attributes"]), | ||||
|     [selectedScan] | ||||
|   ); | ||||
| 
 | ||||
| @ -119,12 +119,15 @@ export const ScanSet = ({ | ||||
|               {title} | ||||
|             </h2> | ||||
| 
 | ||||
|             <Chip> | ||||
|               {selectedScan.language?.data?.attributes?.code === | ||||
|               selectedScan.source_language?.data?.attributes?.code | ||||
|                 ? "Scan" | ||||
|                 : "Scanlation"} | ||||
|             </Chip> | ||||
|             {/* TODO: Add Scan and Scanlation to langui */} | ||||
|             <Chip | ||||
|               text={ | ||||
|                 selectedScan.language?.data?.attributes?.code === | ||||
|                 selectedScan.source_language?.data?.attributes?.code | ||||
|                   ? "Scan" | ||||
|                   : "Scanlation" | ||||
|               } | ||||
|             /> | ||||
|           </div> | ||||
| 
 | ||||
|           <div className="flex flex-row flex-wrap place-items-center gap-4 pb-6"> | ||||
| @ -144,7 +147,7 @@ export const ScanSet = ({ | ||||
|                 content={getStatusDescription(selectedScan.status, langui)} | ||||
|                 maxWidth={"20rem"} | ||||
|               > | ||||
|                 <Chip>{selectedScan.status}</Chip> | ||||
|                 <Chip text={selectedScan.status} /> | ||||
|               </ToolTip> | ||||
|             </div> | ||||
| 
 | ||||
| @ -153,16 +156,17 @@ export const ScanSet = ({ | ||||
|                 {/* TODO: Add Scanner to langui */} | ||||
|                 <p className="font-headers font-bold">{"Scanners"}:</p> | ||||
|                 <div className="grid place-content-center place-items-center gap-2"> | ||||
|                   {filterHasAttributes(selectedScan.scanners.data).map( | ||||
|                     (scanner) => ( | ||||
|                       <Fragment key={scanner.id}> | ||||
|                         <RecorderChip | ||||
|                           langui={langui} | ||||
|                           recorder={scanner.attributes} | ||||
|                         /> | ||||
|                       </Fragment> | ||||
|                     ) | ||||
|                   )} | ||||
|                   {filterHasAttributes(selectedScan.scanners.data, [ | ||||
|                     "id", | ||||
|                     "attributes", | ||||
|                   ] as const).map((scanner) => ( | ||||
|                     <Fragment key={scanner.id}> | ||||
|                       <RecorderChip | ||||
|                         langui={langui} | ||||
|                         recorder={scanner.attributes} | ||||
|                       /> | ||||
|                     </Fragment> | ||||
|                   ))} | ||||
|                 </div> | ||||
|               </div> | ||||
|             )} | ||||
| @ -172,16 +176,17 @@ export const ScanSet = ({ | ||||
|                 {/* TODO: Add Cleaners to langui */} | ||||
|                 <p className="font-headers font-bold">{"Cleaners"}:</p> | ||||
|                 <div className="grid place-content-center place-items-center gap-2"> | ||||
|                   {filterHasAttributes(selectedScan.cleaners.data).map( | ||||
|                     (cleaner) => ( | ||||
|                       <Fragment key={cleaner.id}> | ||||
|                         <RecorderChip | ||||
|                           langui={langui} | ||||
|                           recorder={cleaner.attributes} | ||||
|                         /> | ||||
|                       </Fragment> | ||||
|                     ) | ||||
|                   )} | ||||
|                   {filterHasAttributes(selectedScan.cleaners.data, [ | ||||
|                     "id", | ||||
|                     "attributes", | ||||
|                   ] as const).map((cleaner) => ( | ||||
|                     <Fragment key={cleaner.id}> | ||||
|                       <RecorderChip | ||||
|                         langui={langui} | ||||
|                         recorder={cleaner.attributes} | ||||
|                       /> | ||||
|                     </Fragment> | ||||
|                   ))} | ||||
|                 </div> | ||||
|               </div> | ||||
|             )} | ||||
| @ -189,27 +194,28 @@ export const ScanSet = ({ | ||||
|             {selectedScan.typesetters && | ||||
|               selectedScan.typesetters.data.length > 0 && ( | ||||
|                 <div> | ||||
|                   {/* TODO: Add Cleaners to Typesetters */} | ||||
|                   {/* TODO: Add typesetter to langui */} | ||||
|                   <p className="font-headers font-bold">{"Typesetters"}:</p> | ||||
|                   <div className="grid place-content-center place-items-center gap-2"> | ||||
|                     {filterHasAttributes(selectedScan.typesetters.data).map( | ||||
|                       (typesetter) => ( | ||||
|                         <Fragment key={typesetter.id}> | ||||
|                           <RecorderChip | ||||
|                             langui={langui} | ||||
|                             recorder={typesetter.attributes} | ||||
|                           /> | ||||
|                         </Fragment> | ||||
|                       ) | ||||
|                     )} | ||||
|                     {filterHasAttributes(selectedScan.typesetters.data, [ | ||||
|                       "id", | ||||
|                       "attributes", | ||||
|                     ] as const).map((typesetter) => ( | ||||
|                       <Fragment key={typesetter.id}> | ||||
|                         <RecorderChip | ||||
|                           langui={langui} | ||||
|                           recorder={typesetter.attributes} | ||||
|                         /> | ||||
|                       </Fragment> | ||||
|                     ))} | ||||
|                   </div> | ||||
|                 </div> | ||||
|               )} | ||||
| 
 | ||||
|             {isDefinedAndNotEmpty(selectedScan.notes) && ( | ||||
|               <ToolTip content={selectedScan.notes}> | ||||
|                 {/* TODO: Add Notes to Typesetters */} | ||||
|                 <Chip>{"Notes"}</Chip> | ||||
|                 {/* TODO: Add Notes to langui */} | ||||
|                 <Chip text={"Notes"} /> | ||||
|               </ToolTip> | ||||
|             )} | ||||
|           </div> | ||||
| @ -224,13 +230,9 @@ export const ScanSet = ({ | ||||
|                 className="cursor-pointer transition-transform | ||||
|                 drop-shadow-shade-lg hover:scale-[1.02]" | ||||
|                 onClick={() => { | ||||
|                   const images: string[] = []; | ||||
|                   pages.map((image) => { | ||||
|                     if (isDefinedAndNotEmpty(image.attributes.url)) | ||||
|                       images.push( | ||||
|                         getAssetURL(image.attributes.url, ImageQuality.Large) | ||||
|                       ); | ||||
|                   }); | ||||
|                   const images = pages.map((image) => | ||||
|                     getAssetURL(image.attributes.url, ImageQuality.Large) | ||||
|                   ); | ||||
|                   openLightBox(images, index); | ||||
|                 }} | ||||
|               > | ||||
|  | ||||
| @ -80,12 +80,15 @@ export const ScanSetCover = ({ | ||||
|                 {"Cover"} | ||||
|               </h2> | ||||
| 
 | ||||
|               <Chip> | ||||
|                 {selectedScan.language?.data?.attributes?.code === | ||||
|                 selectedScan.source_language?.data?.attributes?.code | ||||
|                   ? "Scan" | ||||
|                   : "Scanlation"} | ||||
|               </Chip> | ||||
|               {/* TODO: Add Scan and Scanlation to langui */} | ||||
|               <Chip | ||||
|                 text={ | ||||
|                   selectedScan.language?.data?.attributes?.code === | ||||
|                   selectedScan.source_language?.data?.attributes?.code | ||||
|                     ? "Scan" | ||||
|                     : "Scanlation" | ||||
|                 } | ||||
|               /> | ||||
|             </div> | ||||
| 
 | ||||
|             <div className="flex flex-row flex-wrap place-items-center gap-4 pb-6"> | ||||
| @ -97,7 +100,7 @@ export const ScanSetCover = ({ | ||||
|                   content={getStatusDescription(selectedScan.status, langui)} | ||||
|                   maxWidth={"20rem"} | ||||
|                 > | ||||
|                   <Chip>{selectedScan.status}</Chip> | ||||
|                   <Chip text={selectedScan.status} /> | ||||
|                 </ToolTip> | ||||
|               </div> | ||||
| 
 | ||||
| @ -106,16 +109,17 @@ export const ScanSetCover = ({ | ||||
|                   {/* TODO: Add Scanner to langui */} | ||||
|                   <p className="font-headers font-bold">{"Scanners"}:</p> | ||||
|                   <div className="grid place-content-center place-items-center gap-2"> | ||||
|                     {filterHasAttributes(selectedScan.scanners.data).map( | ||||
|                       (scanner) => ( | ||||
|                         <Fragment key={scanner.id}> | ||||
|                           <RecorderChip | ||||
|                             langui={langui} | ||||
|                             recorder={scanner.attributes} | ||||
|                           /> | ||||
|                         </Fragment> | ||||
|                       ) | ||||
|                     )} | ||||
|                     {filterHasAttributes(selectedScan.scanners.data, [ | ||||
|                       "id", | ||||
|                       "attributes", | ||||
|                     ] as const).map((scanner) => ( | ||||
|                       <Fragment key={scanner.id}> | ||||
|                         <RecorderChip | ||||
|                           langui={langui} | ||||
|                           recorder={scanner.attributes} | ||||
|                         /> | ||||
|                       </Fragment> | ||||
|                     ))} | ||||
|                   </div> | ||||
|                 </div> | ||||
|               )} | ||||
| @ -125,16 +129,17 @@ export const ScanSetCover = ({ | ||||
|                   {/* TODO: Add Cleaners to langui */} | ||||
|                   <p className="font-headers font-bold">{"Cleaners"}:</p> | ||||
|                   <div className="grid place-content-center place-items-center gap-2"> | ||||
|                     {filterHasAttributes(selectedScan.cleaners.data).map( | ||||
|                       (cleaner) => ( | ||||
|                         <Fragment key={cleaner.id}> | ||||
|                           <RecorderChip | ||||
|                             langui={langui} | ||||
|                             recorder={cleaner.attributes} | ||||
|                           /> | ||||
|                         </Fragment> | ||||
|                       ) | ||||
|                     )} | ||||
|                     {filterHasAttributes(selectedScan.cleaners.data, [ | ||||
|                       "id", | ||||
|                       "attributes", | ||||
|                     ] as const).map((cleaner) => ( | ||||
|                       <Fragment key={cleaner.id}> | ||||
|                         <RecorderChip | ||||
|                           langui={langui} | ||||
|                           recorder={cleaner.attributes} | ||||
|                         /> | ||||
|                       </Fragment> | ||||
|                     ))} | ||||
|                   </div> | ||||
|                 </div> | ||||
|               )} | ||||
| @ -145,16 +150,17 @@ export const ScanSetCover = ({ | ||||
|                     {/* TODO: Add Cleaners to Typesetters */} | ||||
|                     <p className="font-headers font-bold">{"Typesetters"}:</p> | ||||
|                     <div className="grid place-content-center place-items-center gap-2"> | ||||
|                       {filterHasAttributes(selectedScan.typesetters.data).map( | ||||
|                         (typesetter) => ( | ||||
|                           <Fragment key={typesetter.id}> | ||||
|                             <RecorderChip | ||||
|                               langui={langui} | ||||
|                               recorder={typesetter.attributes} | ||||
|                             /> | ||||
|                           </Fragment> | ||||
|                         ) | ||||
|                       )} | ||||
|                       {filterHasAttributes(selectedScan.typesetters.data, [ | ||||
|                         "id", | ||||
|                         "attributes", | ||||
|                       ] as const).map((typesetter) => ( | ||||
|                         <Fragment key={typesetter.id}> | ||||
|                           <RecorderChip | ||||
|                             langui={langui} | ||||
|                             recorder={typesetter.attributes} | ||||
|                           /> | ||||
|                         </Fragment> | ||||
|                       ))} | ||||
|                     </div> | ||||
|                   </div> | ||||
|                 )} | ||||
| @ -171,11 +177,10 @@ export const ScanSetCover = ({ | ||||
|                   className="cursor-pointer transition-transform | ||||
|                   drop-shadow-shade-lg hover:scale-[1.02]" | ||||
|                   onClick={() => { | ||||
|                     const imgs: string[] = []; | ||||
|                     coverImages.map((img) => { | ||||
|                       if (img.url) | ||||
|                         imgs.push(getAssetURL(img.url, ImageQuality.Large)); | ||||
|                     }); | ||||
|                     const imgs = coverImages.map((img) => | ||||
|                       getAssetURL(img.url, ImageQuality.Large) | ||||
|                     ); | ||||
| 
 | ||||
|                     openLightBox(imgs, index); | ||||
|                   }} | ||||
|                 > | ||||
|  | ||||
| @ -104,7 +104,7 @@ export const PostPage = ({ | ||||
|                     )} | ||||
|                     maxWidth={"20rem"} | ||||
|                   > | ||||
|                     <Chip>{selectedTranslation.status}</Chip> | ||||
|                     <Chip text={selectedTranslation.status} /> | ||||
|                   </ToolTip> | ||||
|                 </div> | ||||
|               )} | ||||
| @ -113,7 +113,10 @@ export const PostPage = ({ | ||||
|                 <div> | ||||
|                   <p className="font-headers font-bold">{"Authors"}:</p> | ||||
|                   <div className="grid place-content-center place-items-center gap-2"> | ||||
|                     {filterHasAttributes(post.authors.data).map((author) => ( | ||||
|                     {filterHasAttributes(post.authors.data, [ | ||||
|                       "id", | ||||
|                       "attributes", | ||||
|                     ] as const).map((author) => ( | ||||
|                       <Fragment key={author.id}> | ||||
|                         <RecorderChip | ||||
|                           langui={langui} | ||||
|  | ||||
| @ -262,7 +262,7 @@ export const PreviewCard = ({ | ||||
|           {topChips && topChips.length > 0 && ( | ||||
|             <div className="grid grid-flow-col place-content-start gap-1 overflow-hidden"> | ||||
|               {topChips.map((text, index) => ( | ||||
|                 <Chip key={index}>{text}</Chip> | ||||
|                 <Chip key={index} text={text} /> | ||||
|               ))} | ||||
|             </div> | ||||
|           )} | ||||
| @ -281,9 +281,7 @@ export const PreviewCard = ({ | ||||
|           {bottomChips && bottomChips.length > 0 && ( | ||||
|             <div className="grid grid-flow-col place-content-start gap-1 overflow-hidden"> | ||||
|               {bottomChips.map((text, index) => ( | ||||
|                 <Chip key={index} className="text-sm"> | ||||
|                   {text} | ||||
|                 </Chip> | ||||
|                 <Chip key={index} className="text-sm" text={text} /> | ||||
|               ))} | ||||
|             </div> | ||||
|           )} | ||||
|  | ||||
| @ -52,7 +52,7 @@ const PreviewLine = ({ | ||||
|         {topChips && topChips.length > 0 && ( | ||||
|           <div className="grid grid-flow-col place-content-start gap-1 overflow-hidden"> | ||||
|             {topChips.map((text, index) => ( | ||||
|               <Chip key={index}>{text}</Chip> | ||||
|               <Chip key={index} text={text} /> | ||||
|             ))} | ||||
|           </div> | ||||
|         )} | ||||
| @ -68,9 +68,7 @@ const PreviewLine = ({ | ||||
|         {bottomChips && bottomChips.length > 0 && ( | ||||
|           <div className="grid grid-flow-col place-content-start gap-1 overflow-hidden"> | ||||
|             {bottomChips.map((text, index) => ( | ||||
|               <Chip key={index} className="text-sm"> | ||||
|                 {text} | ||||
|               </Chip> | ||||
|               <Chip key={index} className="text-sm" text={text} /> | ||||
|             ))} | ||||
|           </div> | ||||
|         )} | ||||
|  | ||||
| @ -38,19 +38,19 @@ export const RecorderChip = ({ recorder, langui }: Props): JSX.Element => ( | ||||
|             {recorder.languages?.data && recorder.languages.data.length > 0 && ( | ||||
|               <div className="flex flex-row flex-wrap gap-1"> | ||||
|                 <p>{langui.languages}:</p> | ||||
|                 {filterHasAttributes(recorder.languages.data).map( | ||||
|                   (language) => ( | ||||
|                     <Fragment key={language.attributes.code}> | ||||
|                       <Chip>{language.attributes.code.toUpperCase()}</Chip> | ||||
|                     </Fragment> | ||||
|                   ) | ||||
|                 )} | ||||
|                 {filterHasAttributes(recorder.languages.data, [ | ||||
|                   "attributes", | ||||
|                 ] as const).map((language) => ( | ||||
|                   <Fragment key={language.attributes.code}> | ||||
|                     <Chip text={language.attributes.code.toUpperCase()} /> | ||||
|                   </Fragment> | ||||
|                 ))} | ||||
|               </div> | ||||
|             )} | ||||
|             {recorder.pronouns && ( | ||||
|               <div className="flex flex-row flex-wrap gap-1"> | ||||
|                 <p>{langui.pronouns}:</p> | ||||
|                 <Chip>{recorder.pronouns}</Chip> | ||||
|                 <Chip text={recorder.pronouns} /> | ||||
|               </div> | ||||
|             )} | ||||
|           </div> | ||||
| @ -60,10 +60,13 @@ export const RecorderChip = ({ recorder, langui }: Props): JSX.Element => ( | ||||
|     } | ||||
|     placement="top" | ||||
|   > | ||||
|     <Chip key={recorder.anonymous_code}> | ||||
|       {recorder.anonymize | ||||
|         ? `Recorder#${recorder.anonymous_code}` | ||||
|         : recorder.username} | ||||
|     </Chip> | ||||
|     <Chip | ||||
|       key={recorder.anonymous_code} | ||||
|       text={ | ||||
|         recorder.anonymize | ||||
|           ? `Recorder#${recorder.anonymous_code}` | ||||
|           : recorder.username | ||||
|       } | ||||
|     /> | ||||
|   </ToolTip> | ||||
| ); | ||||
|  | ||||
| @ -146,11 +146,13 @@ export const SmartList = <T,>({ | ||||
|                 first-of-type:pt-0" | ||||
|                     > | ||||
|                       {name} | ||||
|                       <Chip>{`${groupItems.length} ${ | ||||
|                         groupItems.length <= 1 | ||||
|                           ? langui.result?.toLowerCase() ?? "" | ||||
|                           : langui.results?.toLowerCase() ?? "" | ||||
|                       }`}</Chip>
 | ||||
|                       <Chip | ||||
|                         text={`${groupItems.length} ${ | ||||
|                           groupItems.length <= 1 | ||||
|                             ? langui.result?.toLowerCase() ?? "" | ||||
|                             : langui.results?.toLowerCase() ?? "" | ||||
|                         }`}
 | ||||
|                       /> | ||||
|                     </h2> | ||||
|                   )} | ||||
|                   <div | ||||
|  | ||||
| @ -80,12 +80,12 @@ export const ThumbnailHeader = ({ | ||||
|           <div className="flex flex-col place-items-center gap-2"> | ||||
|             <h3 className="text-xl">{langui.type}</h3> | ||||
|             <div className="flex flex-row flex-wrap"> | ||||
|               <Chip> | ||||
|                 {type.data.attributes.titles && | ||||
|                 type.data.attributes.titles.length > 0 | ||||
|                   ? type.data.attributes.titles[0]?.title | ||||
|                   : prettySlug(type.data.attributes.slug)} | ||||
|               </Chip> | ||||
|               <Chip | ||||
|                 text={ | ||||
|                   type.data.attributes.titles?.[0]?.title ?? | ||||
|                   prettySlug(type.data.attributes.slug) | ||||
|                 } | ||||
|               /> | ||||
|             </div> | ||||
|           </div> | ||||
|         )} | ||||
| @ -94,8 +94,11 @@ export const ThumbnailHeader = ({ | ||||
|           <div className="flex flex-col place-items-center gap-2"> | ||||
|             <h3 className="text-xl">{langui.categories}</h3> | ||||
|             <div className="flex flex-row flex-wrap place-content-center gap-2"> | ||||
|               {filterHasAttributes(categories.data).map((category) => ( | ||||
|                 <Chip key={category.id}>{category.attributes.name}</Chip> | ||||
|               {filterHasAttributes(categories.data, [ | ||||
|                 "attributes", | ||||
|                 "id", | ||||
|               ] as const).map((category) => ( | ||||
|                 <Chip key={category.id} text={category.attributes.name} /> | ||||
|               ))} | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
| @ -57,7 +57,7 @@ export const ChronologyItemComponent = ({ | ||||
|             filterHasAttributes(item.attributes.events, [ | ||||
|               "id", | ||||
|               "translations", | ||||
|             ]).map((event) => ( | ||||
|             ] as const).map((event) => ( | ||||
|               <Fragment key={event.id}> | ||||
|                 <div className="m-0"> | ||||
|                   {filterDefined(event.translations).map( | ||||
| @ -77,7 +77,7 @@ export const ChronologyItemComponent = ({ | ||||
|                                 )} | ||||
|                                 maxWidth={"20rem"} | ||||
|                               > | ||||
|                                 <Chip>{translation.status}</Chip> | ||||
|                                 <Chip text={translation.status} /> | ||||
|                               </ToolTip> | ||||
|                             )} | ||||
|                             {translation.title ? ( | ||||
|  | ||||
| @ -62,7 +62,7 @@ const DefinitionCard = ({ | ||||
|               content={getStatusDescription(selectedTranslation.status, langui)} | ||||
|               maxWidth={"20rem"} | ||||
|             > | ||||
|               <Chip>{selectedTranslation.status}</Chip> | ||||
|               <Chip text={selectedTranslation.status} /> | ||||
|             </ToolTip> | ||||
|           </> | ||||
|         )} | ||||
| @ -72,7 +72,7 @@ const DefinitionCard = ({ | ||||
|             <Separator /> | ||||
|             <div className="flex flex-row gap-1"> | ||||
|               {categories.map((category, categoryIndex) => ( | ||||
|                 <Chip key={categoryIndex}>{category}</Chip> | ||||
|                 <Chip key={categoryIndex} text={category} /> | ||||
|               ))} | ||||
|             </div> | ||||
|           </> | ||||
|  | ||||
| @ -60,20 +60,20 @@ export const prettyinlineTitle = ( | ||||
| export const prettyItemType = ( | ||||
|   metadata: any, | ||||
|   langui: AppStaticProps["langui"] | ||||
| ): string | null | undefined => { | ||||
| ): string => { | ||||
|   switch (metadata.__typename) { | ||||
|     case "ComponentMetadataAudio": | ||||
|       return langui.audio; | ||||
|       return langui.audio ?? "Audio"; | ||||
|     case "ComponentMetadataBooks": | ||||
|       return langui.textual; | ||||
|       return langui.textual ?? "Textual"; | ||||
|     case "ComponentMetadataGame": | ||||
|       return langui.game; | ||||
|       return langui.game ?? "Game"; | ||||
|     case "ComponentMetadataVideo": | ||||
|       return langui.video; | ||||
|       return langui.video ?? "Video"; | ||||
|     case "ComponentMetadataGroup": | ||||
|       return langui.group; | ||||
|       return langui.group ?? "Group"; | ||||
|     case "ComponentMetadataOther": | ||||
|       return langui.other; | ||||
|       return langui.other ?? "Other"; | ||||
|     default: | ||||
|       return ""; | ||||
|   } | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import { AppStaticProps } from "../graphql/getAppStaticProps"; | ||||
| import { SelectiveRequiredNonNullable } from "./types"; | ||||
| import { PathDot, SelectiveNonNullable } from "./types/SelectiveNonNullable"; | ||||
| import { | ||||
|   Enum_Componentsetstextset_Status, | ||||
|   GetLibraryItemQuery, | ||||
| @ -71,21 +71,29 @@ export const filterDefined = <T>(t: T[] | null | undefined): NonNullable<T>[] => | ||||
|     ? [] | ||||
|     : (t.filter((item) => isDefined(item)) as NonNullable<T>[]); | ||||
| 
 | ||||
| export const filterHasAttributes = <T, P extends keyof NonNullable<T>>( | ||||
| export const filterHasAttributes = <T, P extends PathDot<T>>( | ||||
|   t: T[] | null | undefined, | ||||
|   attributes?: P[] | ||||
| ): SelectiveRequiredNonNullable<NonNullable<T>, P>[] => | ||||
|   paths: readonly P[] | ||||
| ): SelectiveNonNullable<T, typeof paths[number]>[] => | ||||
|   isUndefined(t) | ||||
|     ? [] | ||||
|     : (t.filter((item) => { | ||||
|         if (isDefined(item)) { | ||||
|           const attributesToCheck = attributes ?? (Object.keys(item) as P[]); | ||||
|           return attributesToCheck.every((attribute) => | ||||
|             isDefined(item[attribute]) | ||||
|           ); | ||||
|         } | ||||
|         return false; | ||||
|       }) as unknown as SelectiveRequiredNonNullable<NonNullable<T>, P>[]); | ||||
|     : (t.filter((item) => | ||||
|         hasAttributes(item, paths) | ||||
|       ) as unknown as SelectiveNonNullable<T, typeof paths[number]>[]); | ||||
| 
 | ||||
| const hasAttributes = <T>(item: T, paths: readonly PathDot<T>[]): boolean => { | ||||
|   if (isDefined(item)) { | ||||
|     return paths.every((path) => { | ||||
|       const attributeToCheck = (path as string).split(".")[0]; | ||||
|       return ( | ||||
|         isDefined(attributeToCheck) && | ||||
|         Object.keys(item).includes(attributeToCheck) && | ||||
|         isDefined(item[attributeToCheck as keyof T]) | ||||
|       ); | ||||
|     }); | ||||
|   } | ||||
|   return false; | ||||
| }; | ||||
| 
 | ||||
| export const iterateMap = <K, V, U>( | ||||
|   map: Map<K, V>, | ||||
| @ -95,7 +103,6 @@ export const iterateMap = <K, V, U>( | ||||
|   const toList = [...map]; | ||||
|   if (isDefined(sortingFunction)) { | ||||
|     toList.sort(sortingFunction); | ||||
|     console.log(toList.sort(sortingFunction)); | ||||
|   } | ||||
|   return toList.map(([key, value], index) => callbackfn(key, value, index)); | ||||
| }; | ||||
|  | ||||
| @ -4,6 +4,8 @@ import { | ||||
|   GetWikiPageQuery, | ||||
| } from "graphql/generated"; | ||||
| 
 | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| type Post = NonNullable< | ||||
|   NonNullable<GetPostQuery["posts"]>["data"][number]["attributes"] | ||||
| >; | ||||
| @ -12,6 +14,8 @@ export interface PostWithTranslations extends Omit<Post, "translations"> { | ||||
|   translations: NonNullable<Post["translations"]>; | ||||
| } | ||||
| 
 | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export type Content = NonNullable< | ||||
|   NonNullable<GetContentTextQuery["contents"]>["data"][number]["attributes"] | ||||
| >; | ||||
| @ -20,6 +24,8 @@ export interface ContentWithTranslations extends Omit<Content, "translations"> { | ||||
|   translations: NonNullable<Content["translations"]>; | ||||
| } | ||||
| 
 | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| type WikiPage = NonNullable< | ||||
|   NonNullable<GetWikiPageQuery["wikiPages"]>["data"][number]["attributes"] | ||||
| >; | ||||
| @ -29,13 +35,13 @@ export interface WikiPageWithTranslations | ||||
|   translations: NonNullable<WikiPage["translations"]>; | ||||
| } | ||||
| 
 | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export type RequiredNonNullable<T> = { | ||||
|   [P in keyof T]-?: NonNullable<T[P]>; | ||||
| }; | ||||
| 
 | ||||
| export type SelectiveRequiredNonNullable<T, K extends keyof T> = Omit<T, K> & { | ||||
|   [P in K]-?: NonNullable<T[P]>; | ||||
| }; | ||||
| // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
 | ||||
| 
 | ||||
| export enum LibraryItemUserStatus { | ||||
|   None = 0, | ||||
|  | ||||
							
								
								
									
										47
									
								
								src/helpers/types/SelectiveNonNullable.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/helpers/types/SelectiveNonNullable.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| type JoinDot<K extends string, P extends string> = `${K}${"" extends K | ||||
|   ? "" | ||||
|   : "."}${P}`;
 | ||||
| 
 | ||||
| export type PathDot<T, Acc extends string = ""> = T extends object | ||||
|   ? { | ||||
|       [K in keyof T]: K extends string | ||||
|         ? JoinDot<Acc, K> | PathDot<T[K], JoinDot<Acc, K>> | ||||
|         : never; | ||||
|     }[keyof T] | ||||
|   : Acc; | ||||
| 
 | ||||
| type PathHead<T extends unknown[]> = T extends [infer head] | ||||
|   ? head | ||||
|   : T extends [infer head, ...infer rest] | ||||
|   ? head | ||||
|   : ""; | ||||
| 
 | ||||
| type PathRest<T extends unknown[]> = T extends [infer head, ...infer rest] | ||||
|   ? rest extends [] | ||||
|     ? never | ||||
|     : rest | ||||
|   : never; | ||||
| 
 | ||||
| type PathLast<T extends unknown[]> = T["length"] extends 1 ? true : false; | ||||
| 
 | ||||
| type Recursive<T, Path extends unknown[]> = PathHead<Path> extends keyof T | ||||
|   ? Omit<T, PathHead<Path>> & { | ||||
|       [P in PathHead<Path>]-?: PathLast<Path> extends true | ||||
|         ? NonNullable<T[P]> | ||||
|         : Recursive<NonNullable<T[P]>, PathRest<Path>>; | ||||
|     } | ||||
|   : T; | ||||
| 
 | ||||
| type Split< | ||||
|   Str, | ||||
|   Cache extends string[] = [] | ||||
| > = Str extends `${infer Method}.${infer PathRest}` | ||||
|   ? Split<PathRest, [...Cache, Method]> | ||||
|   : Str extends `${infer PathLast}` | ||||
|   ? [...Cache, PathLast] | ||||
|   : never; | ||||
| 
 | ||||
| export type SelectiveNonNullable<T, P extends PathDot<T>> = Recursive< | ||||
|   NonNullable<T>, | ||||
|   Split<P> | ||||
| >; | ||||
| @ -78,7 +78,9 @@ const Channel = ({ langui, channel, ...otherProps }: Props): JSX.Element => { | ||||
|           className="grid items-start gap-8 border-b-[3px] border-dotted pb-12 last-of-type:border-0 | ||||
|         desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:grid-cols-2" | ||||
|         > | ||||
|           {filterHasAttributes(channel?.videos?.data).map((video) => ( | ||||
|           {filterHasAttributes(channel?.videos?.data, [ | ||||
|             "attributes", | ||||
|           ] as const).map((video) => ( | ||||
|             <Fragment key={video.id}> | ||||
|               <PreviewCard | ||||
|                 href={`/archives/videos/v/${video.attributes.uid}`} | ||||
| @ -152,7 +154,9 @@ export const getStaticPaths: GetStaticPaths = async (context) => { | ||||
|   const channels = await sdk.getVideoChannelsSlugs(); | ||||
|   const paths: GetStaticPathsResult["paths"] = []; | ||||
|   if (channels.videoChannels?.data) | ||||
|     filterHasAttributes(channels.videoChannels.data).map((channel) => { | ||||
|     filterHasAttributes(channels.videoChannels.data, [ | ||||
|       "attributes", | ||||
|     ] as const).map((channel) => { | ||||
|       context.locales?.map((local) => { | ||||
|         paths.push({ | ||||
|           params: { uid: channel.attributes.uid }, | ||||
|  | ||||
| @ -92,7 +92,7 @@ const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => { | ||||
|     () => ( | ||||
|       <ContentPanel width={ContentPanelWidthSizes.Full}> | ||||
|         <SmartList | ||||
|           items={filterHasAttributes(videos)} | ||||
|           items={filterHasAttributes(videos, ["id", "attributes"] as const)} | ||||
|           getItemId={(item) => item.id} | ||||
|           renderItem={({ item }) => ( | ||||
|             <> | ||||
|  | ||||
| @ -238,11 +238,13 @@ export const getStaticPaths: GetStaticPaths = async (context) => { | ||||
|   const videos = await sdk.getVideosSlugs(); | ||||
|   const paths: GetStaticPathsResult["paths"] = []; | ||||
|   if (videos.videos?.data) | ||||
|     filterHasAttributes(videos.videos.data).map((video) => { | ||||
|       context.locales?.map((local) => { | ||||
|         paths.push({ params: { uid: video.attributes.uid }, locale: local }); | ||||
|       }); | ||||
|     }); | ||||
|     filterHasAttributes(videos.videos.data, ["attributes"] as const).map( | ||||
|       (video) => { | ||||
|         context.locales?.map((local) => { | ||||
|           paths.push({ params: { uid: video.attributes.uid }, locale: local }); | ||||
|         }); | ||||
|       } | ||||
|     ); | ||||
|   return { | ||||
|     paths, | ||||
|     fallback: "blocking", | ||||
|  | ||||
| @ -114,13 +114,13 @@ const Content = ({ | ||||
|                 <p className="font-headers font-bold"> | ||||
|                   {langui.source_language}: | ||||
|                 </p> | ||||
|                 <Chip> | ||||
|                   {prettyLanguage( | ||||
|                 <Chip | ||||
|                   text={prettyLanguage( | ||||
|                     selectedTranslation.text_set.source_language.data.attributes | ||||
|                       .code, | ||||
|                     languages | ||||
|                   )} | ||||
|                 </Chip> | ||||
|                 /> | ||||
|               </div> | ||||
|             )} | ||||
| 
 | ||||
| @ -134,7 +134,7 @@ const Content = ({ | ||||
|                 )} | ||||
|                 maxWidth={"20rem"} | ||||
|               > | ||||
|                 <Chip>{selectedTranslation.text_set.status}</Chip> | ||||
|                 <Chip text={selectedTranslation.text_set.status} /> | ||||
|               </ToolTip> | ||||
|             </div> | ||||
| 
 | ||||
| @ -146,7 +146,8 @@ const Content = ({ | ||||
|                   </p> | ||||
|                   <div className="grid place-content-center place-items-center gap-2"> | ||||
|                     {filterHasAttributes( | ||||
|                       selectedTranslation.text_set.transcribers.data | ||||
|                       selectedTranslation.text_set.transcribers.data, | ||||
|                       ["attributes", "id"] as const | ||||
|                     ).map((recorder) => ( | ||||
|                       <Fragment key={recorder.id}> | ||||
|                         <RecorderChip | ||||
| @ -167,7 +168,8 @@ const Content = ({ | ||||
|                   </p> | ||||
|                   <div className="grid place-content-center place-items-center gap-2"> | ||||
|                     {filterHasAttributes( | ||||
|                       selectedTranslation.text_set.translators.data | ||||
|                       selectedTranslation.text_set.translators.data, | ||||
|                       ["attributes", "id"] as const | ||||
|                     ).map((recorder) => ( | ||||
|                       <Fragment key={recorder.id}> | ||||
|                         <RecorderChip | ||||
| @ -188,7 +190,8 @@ const Content = ({ | ||||
|                   </p> | ||||
|                   <div className="grid place-content-center place-items-center gap-2"> | ||||
|                     {filterHasAttributes( | ||||
|                       selectedTranslation.text_set.proofreaders.data | ||||
|                       selectedTranslation.text_set.proofreaders.data, | ||||
|                       ["attributes", "id"] as const | ||||
|                     ).map((recorder) => ( | ||||
|                       <Fragment key={recorder.id}> | ||||
|                         <RecorderChip | ||||
| @ -221,59 +224,60 @@ const Content = ({ | ||||
|                   {langui.source} | ||||
|                 </p> | ||||
|                 <div className="mt-6 grid place-items-center gap-6 text-left"> | ||||
|                   {content.ranged_contents.data.map((rangedContent) => { | ||||
|                   {filterHasAttributes(content.ranged_contents.data, [ | ||||
|                     "attributes.library_item.data.attributes", | ||||
|                     "attributes.library_item.data.id", | ||||
|                   ] as const).map((rangedContent) => { | ||||
|                     const libraryItem = | ||||
|                       rangedContent.attributes?.library_item?.data; | ||||
|                     if (libraryItem?.attributes && libraryItem.id) { | ||||
|                       return ( | ||||
|                         <div | ||||
|                           key={libraryItem.attributes.slug} | ||||
|                           className="mobile:w-[80%]" | ||||
|                         > | ||||
|                           <PreviewCard | ||||
|                             href={`/library/${libraryItem.attributes.slug}`} | ||||
|                             title={libraryItem.attributes.title} | ||||
|                             subtitle={libraryItem.attributes.subtitle} | ||||
|                             thumbnail={ | ||||
|                               libraryItem.attributes.thumbnail?.data?.attributes | ||||
|                             } | ||||
|                             thumbnailAspectRatio="21/29.7" | ||||
|                             thumbnailRounded={false} | ||||
|                             topChips={ | ||||
|                               libraryItem.attributes.metadata && | ||||
|                               libraryItem.attributes.metadata.length > 0 && | ||||
|                               libraryItem.attributes.metadata[0] | ||||
|                                 ? [ | ||||
|                                     prettyItemSubType( | ||||
|                                       libraryItem.attributes.metadata[0] | ||||
|                                     ), | ||||
|                                   ] | ||||
|                                 : [] | ||||
|                             } | ||||
|                             bottomChips={libraryItem.attributes.categories?.data.map( | ||||
|                               (category) => category.attributes?.short ?? "" | ||||
|                             )} | ||||
|                             metadata={{ | ||||
|                               currencies: currencies, | ||||
|                               release_date: libraryItem.attributes.release_date, | ||||
|                               price: libraryItem.attributes.price, | ||||
|                               position: "Bottom", | ||||
|                             }} | ||||
|                             infoAppend={ | ||||
|                               !isUntangibleGroupItem( | ||||
|                                 libraryItem.attributes.metadata?.[0] | ||||
|                               ) && ( | ||||
|                                 <PreviewCardCTAs | ||||
|                                   id={libraryItem.id} | ||||
|                                   langui={langui} | ||||
|                                 /> | ||||
|                               ) | ||||
|                             } | ||||
|                           /> | ||||
|                         </div> | ||||
|                       ); | ||||
|                     } | ||||
|                     return <></>; | ||||
|                       rangedContent.attributes.library_item.data; | ||||
|                     return ( | ||||
|                       <div | ||||
|                         key={libraryItem.attributes.slug} | ||||
|                         className="mobile:w-[80%]" | ||||
|                       > | ||||
|                         <PreviewCard | ||||
|                           href={`/library/${libraryItem.attributes.slug}`} | ||||
|                           title={libraryItem.attributes.title} | ||||
|                           subtitle={libraryItem.attributes.subtitle} | ||||
|                           thumbnail={ | ||||
|                             libraryItem.attributes.thumbnail?.data?.attributes | ||||
|                           } | ||||
|                           thumbnailAspectRatio="21/29.7" | ||||
|                           thumbnailRounded={false} | ||||
|                           topChips={ | ||||
|                             libraryItem.attributes.metadata && | ||||
|                             libraryItem.attributes.metadata.length > 0 && | ||||
|                             libraryItem.attributes.metadata[0] | ||||
|                               ? [ | ||||
|                                   prettyItemSubType( | ||||
|                                     libraryItem.attributes.metadata[0] | ||||
|                                   ), | ||||
|                                 ] | ||||
|                               : [] | ||||
|                           } | ||||
|                           bottomChips={filterHasAttributes( | ||||
|                             libraryItem.attributes.categories?.data, | ||||
|                             ["attributes"] as const | ||||
|                           ).map((category) => category.attributes.short)} | ||||
|                           metadata={{ | ||||
|                             currencies: currencies, | ||||
|                             release_date: libraryItem.attributes.release_date, | ||||
|                             price: libraryItem.attributes.price, | ||||
|                             position: "Bottom", | ||||
|                           }} | ||||
|                           infoAppend={ | ||||
|                             !isUntangibleGroupItem( | ||||
|                               libraryItem.attributes.metadata?.[0] | ||||
|                             ) && ( | ||||
|                               <PreviewCardCTAs | ||||
|                                 id={libraryItem.id} | ||||
|                                 langui={langui} | ||||
|                               /> | ||||
|                             ) | ||||
|                           } | ||||
|                         /> | ||||
|                       </div> | ||||
|                     ); | ||||
|                   })} | ||||
|                 </div> | ||||
|               </div> | ||||
| @ -507,14 +511,16 @@ export const getStaticPaths: GetStaticPaths = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const contents = await sdk.getContentsSlugs(); | ||||
|   const paths: GetStaticPathsResult["paths"] = []; | ||||
|   filterHasAttributes(contents.contents?.data).map((item) => { | ||||
|     context.locales?.map((local) => { | ||||
|       paths.push({ | ||||
|         params: { slug: item.attributes.slug }, | ||||
|         locale: local, | ||||
|   filterHasAttributes(contents.contents?.data, ["attributes"] as const).map( | ||||
|     (item) => { | ||||
|       context.locales?.map((local) => { | ||||
|         paths.push({ | ||||
|           params: { slug: item.attributes.slug }, | ||||
|           locale: local, | ||||
|         }); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
|     } | ||||
|   ); | ||||
|   return { | ||||
|     paths, | ||||
|     fallback: "blocking", | ||||
|  | ||||
| @ -21,8 +21,8 @@ import { Icon } from "components/Ico"; | ||||
| import { filterDefined, filterHasAttributes } from "helpers/others"; | ||||
| import { GetContentsQuery } from "graphql/generated"; | ||||
| import { SmartList } from "components/SmartList"; | ||||
| import { SelectiveRequiredNonNullable } from "helpers/types"; | ||||
| import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder"; | ||||
| import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable"; | ||||
| 
 | ||||
| /* | ||||
|  *                                         ╭─────────────╮ | ||||
| @ -73,7 +73,7 @@ const Contents = ({ | ||||
| 
 | ||||
|   const groupingFunction = useCallback( | ||||
|     ( | ||||
|       item: SelectiveRequiredNonNullable< | ||||
|       item: SelectiveNonNullable< | ||||
|         NonNullable<GetContentsQuery["contents"]>["data"][number], | ||||
|         "attributes" | "id" | ||||
|       > | ||||
| @ -81,7 +81,8 @@ const Contents = ({ | ||||
|       switch (groupingMethod) { | ||||
|         case 0: { | ||||
|           const categories = filterHasAttributes( | ||||
|             item.attributes.categories?.data | ||||
|             item.attributes.categories?.data, | ||||
|             ["attributes"] as const | ||||
|           ); | ||||
|           if (categories.length > 0) { | ||||
|             return categories.map((category) => category.attributes.name); | ||||
| @ -106,10 +107,7 @@ const Contents = ({ | ||||
| 
 | ||||
|   const filteringFunction = useCallback( | ||||
|     ( | ||||
|       item: SelectiveRequiredNonNullable< | ||||
|         Props["contents"][number], | ||||
|         "attributes" | "id" | ||||
|       > | ||||
|       item: SelectiveNonNullable<Props["contents"][number], "attributes" | "id"> | ||||
|     ) => { | ||||
|       if ( | ||||
|         effectiveCombineRelatedContent && | ||||
| @ -217,7 +215,7 @@ const Contents = ({ | ||||
|     () => ( | ||||
|       <ContentPanel width={ContentPanelWidthSizes.Full}> | ||||
|         <SmartList | ||||
|           items={filterHasAttributes(contents)} | ||||
|           items={filterHasAttributes(contents, ["attributes", "id"] as const)} | ||||
|           getItemId={(item) => item.id} | ||||
|           renderItem={({ item }) => ( | ||||
|             <> | ||||
|  | ||||
| @ -60,7 +60,7 @@ const CheckupContents = ({ contents, ...otherProps }: Props): JSX.Element => { | ||||
|             /> | ||||
|             <p>{line.subitems.join(" -> ")}</p> | ||||
|             <p>{line.name}</p> | ||||
|             <Chip>{line.type}</Chip> | ||||
|             <Chip text={line.type} /> | ||||
|             <Chip | ||||
|               className={ | ||||
|                 line.severity === "Very High" | ||||
| @ -71,9 +71,8 @@ const CheckupContents = ({ contents, ...otherProps }: Props): JSX.Element => { | ||||
|                   ? "bg-[#fff344] !opacity-100" | ||||
|                   : "" | ||||
|               } | ||||
|             > | ||||
|               {line.severity} | ||||
|             </Chip> | ||||
|               text={line.severity} | ||||
|             /> | ||||
|             <ToolTip content={line.recommandation} placement="left"> | ||||
|               <p>{line.description}</p> | ||||
|             </ToolTip> | ||||
| @ -138,320 +137,322 @@ const testingContent = (contents: Props["contents"]): Report => { | ||||
|     lines: [], | ||||
|   }; | ||||
| 
 | ||||
|   filterHasAttributes(contents.contents?.data).map((content) => { | ||||
|     const backendUrl = `${process.env.NEXT_PUBLIC_URL_CMS}/admin/content-manager/collectionType/api::content.content/${content.id}`; | ||||
|     const frontendUrl = `${process.env.NEXT_PUBLIC_URL_SELF}/contents/${content.attributes.slug}`; | ||||
|   filterHasAttributes(contents.contents?.data, ["attributes"] as const).map( | ||||
|     (content) => { | ||||
|       const backendUrl = `${process.env.NEXT_PUBLIC_URL_CMS}/admin/content-manager/collectionType/api::content.content/${content.id}`; | ||||
|       const frontendUrl = `${process.env.NEXT_PUBLIC_URL_SELF}/contents/${content.attributes.slug}`; | ||||
| 
 | ||||
|     if (content.attributes.categories?.data.length === 0) { | ||||
|       report.lines.push({ | ||||
|         subitems: [content.attributes.slug], | ||||
|         name: "No Category", | ||||
|         type: "Missing", | ||||
|         severity: "Medium", | ||||
|         description: "The Content has no Category.", | ||||
|         recommandation: "Select a Category in relation with the Content", | ||||
|         backendUrl: backendUrl, | ||||
|         frontendUrl: frontendUrl, | ||||
|       }); | ||||
|     } | ||||
|       if (content.attributes.categories?.data.length === 0) { | ||||
|         report.lines.push({ | ||||
|           subitems: [content.attributes.slug], | ||||
|           name: "No Category", | ||||
|           type: "Missing", | ||||
|           severity: "Medium", | ||||
|           description: "The Content has no Category.", | ||||
|           recommandation: "Select a Category in relation with the Content", | ||||
|           backendUrl: backendUrl, | ||||
|           frontendUrl: frontendUrl, | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|     if (!content.attributes.type?.data?.id) { | ||||
|       report.lines.push({ | ||||
|         subitems: [content.attributes.slug], | ||||
|         name: "No Category", | ||||
|         type: "Missing", | ||||
|         severity: "Medium", | ||||
|         description: "The Content has no Type.", | ||||
|         recommandation: 'If unsure, use the "Other" Type.', | ||||
|         backendUrl: backendUrl, | ||||
|         frontendUrl: frontendUrl, | ||||
|       }); | ||||
|     } | ||||
|       if (!content.attributes.type?.data?.id) { | ||||
|         report.lines.push({ | ||||
|           subitems: [content.attributes.slug], | ||||
|           name: "No Category", | ||||
|           type: "Missing", | ||||
|           severity: "Medium", | ||||
|           description: "The Content has no Type.", | ||||
|           recommandation: 'If unsure, use the "Other" Type.', | ||||
|           backendUrl: backendUrl, | ||||
|           frontendUrl: frontendUrl, | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|     if (content.attributes.ranged_contents?.data.length === 0) { | ||||
|       report.lines.push({ | ||||
|         subitems: [content.attributes.slug], | ||||
|         name: "No Ranged Content", | ||||
|         type: "Improvement", | ||||
|         severity: "Low", | ||||
|         description: "The Content has no Ranged Content.", | ||||
|         recommandation: | ||||
|           "If this Content is available in one or multiple Library Item(s), create a Range Content to connect the Content to its Library Item(s).", | ||||
|         backendUrl: backendUrl, | ||||
|         frontendUrl: frontendUrl, | ||||
|       }); | ||||
|     } | ||||
|       if (content.attributes.ranged_contents?.data.length === 0) { | ||||
|         report.lines.push({ | ||||
|           subitems: [content.attributes.slug], | ||||
|           name: "No Ranged Content", | ||||
|           type: "Improvement", | ||||
|           severity: "Low", | ||||
|           description: "The Content has no Ranged Content.", | ||||
|           recommandation: | ||||
|             "If this Content is available in one or multiple Library Item(s), create a Range Content to connect the Content to its Library Item(s).", | ||||
|           backendUrl: backendUrl, | ||||
|           frontendUrl: frontendUrl, | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|     if (!content.attributes.thumbnail?.data?.id) { | ||||
|       report.lines.push({ | ||||
|         subitems: [content.attributes.slug], | ||||
|         name: "No Thumbnail", | ||||
|         type: "Missing", | ||||
|         severity: "High", | ||||
|         description: "The Content has no Thumbnail.", | ||||
|         recommandation: "", | ||||
|         backendUrl: backendUrl, | ||||
|         frontendUrl: frontendUrl, | ||||
|       }); | ||||
|     } | ||||
|       if (!content.attributes.thumbnail?.data?.id) { | ||||
|         report.lines.push({ | ||||
|           subitems: [content.attributes.slug], | ||||
|           name: "No Thumbnail", | ||||
|           type: "Missing", | ||||
|           severity: "High", | ||||
|           description: "The Content has no Thumbnail.", | ||||
|           recommandation: "", | ||||
|           backendUrl: backendUrl, | ||||
|           frontendUrl: frontendUrl, | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|     if (content.attributes.translations?.length === 0) { | ||||
|       report.lines.push({ | ||||
|         subitems: [content.attributes.slug], | ||||
|         name: "No Titles", | ||||
|         type: "Missing", | ||||
|         severity: "High", | ||||
|         description: "The Content has no Titles.", | ||||
|         recommandation: "", | ||||
|         backendUrl: backendUrl, | ||||
|         frontendUrl: frontendUrl, | ||||
|       }); | ||||
|     } else { | ||||
|       const titleLanguages: string[] = []; | ||||
|       if (content.attributes.translations?.length === 0) { | ||||
|         report.lines.push({ | ||||
|           subitems: [content.attributes.slug], | ||||
|           name: "No Titles", | ||||
|           type: "Missing", | ||||
|           severity: "High", | ||||
|           description: "The Content has no Titles.", | ||||
|           recommandation: "", | ||||
|           backendUrl: backendUrl, | ||||
|           frontendUrl: frontendUrl, | ||||
|         }); | ||||
|       } else { | ||||
|         const titleLanguages: string[] = []; | ||||
| 
 | ||||
|       filterDefined(content.attributes.translations).map( | ||||
|         (translation, titleIndex) => { | ||||
|           if (translation.language?.data?.id) { | ||||
|             if (translation.language.data.id in titleLanguages) { | ||||
|         filterDefined(content.attributes.translations).map( | ||||
|           (translation, titleIndex) => { | ||||
|             if (translation.language?.data?.id) { | ||||
|               if (translation.language.data.id in titleLanguages) { | ||||
|                 report.lines.push({ | ||||
|                   subitems: [ | ||||
|                     content.attributes.slug, | ||||
|                     `Title ${titleIndex.toString()}`, | ||||
|                   ], | ||||
|                   name: "Duplicate Language", | ||||
|                   type: "Error", | ||||
|                   severity: "High", | ||||
|                   description: "", | ||||
|                   recommandation: "", | ||||
|                   backendUrl: backendUrl, | ||||
|                   frontendUrl: frontendUrl, | ||||
|                 }); | ||||
|               } else { | ||||
|                 titleLanguages.push(translation.language.data.id); | ||||
|               } | ||||
|             } else { | ||||
|               report.lines.push({ | ||||
|                 subitems: [ | ||||
|                   content.attributes.slug, | ||||
|                   `Title ${titleIndex.toString()}`, | ||||
|                 ], | ||||
|                 name: "Duplicate Language", | ||||
|                 name: "No Language", | ||||
|                 type: "Error", | ||||
|                 severity: "High", | ||||
|                 severity: "Very High", | ||||
|                 description: "", | ||||
|                 recommandation: "", | ||||
|                 backendUrl: backendUrl, | ||||
|                 frontendUrl: frontendUrl, | ||||
|               }); | ||||
|             } | ||||
|             if (!translation.description) { | ||||
|               report.lines.push({ | ||||
|                 subitems: [ | ||||
|                   content.attributes.slug, | ||||
|                   `Title ${titleIndex.toString()}`, | ||||
|                 ], | ||||
|                 name: "No Description", | ||||
|                 type: "Missing", | ||||
|                 severity: "Medium", | ||||
|                 description: "", | ||||
|                 recommandation: "", | ||||
|                 backendUrl: backendUrl, | ||||
|                 frontendUrl: frontendUrl, | ||||
|               }); | ||||
|             } else { | ||||
|               titleLanguages.push(translation.language.data.id); | ||||
|             } | ||||
|           } else { | ||||
|             report.lines.push({ | ||||
|               subitems: [ | ||||
|                 content.attributes.slug, | ||||
|                 `Title ${titleIndex.toString()}`, | ||||
|               ], | ||||
|               name: "No Language", | ||||
|               type: "Error", | ||||
|               severity: "Very High", | ||||
|               description: "", | ||||
|               recommandation: "", | ||||
|               backendUrl: backendUrl, | ||||
|               frontendUrl: frontendUrl, | ||||
|             }); | ||||
|           } | ||||
|           if (!translation.description) { | ||||
|             report.lines.push({ | ||||
|               subitems: [ | ||||
|                 content.attributes.slug, | ||||
|                 `Title ${titleIndex.toString()}`, | ||||
|               ], | ||||
|               name: "No Description", | ||||
|               type: "Missing", | ||||
|               severity: "Medium", | ||||
|               description: "", | ||||
|               recommandation: "", | ||||
|               backendUrl: backendUrl, | ||||
|               frontendUrl: frontendUrl, | ||||
|             }); | ||||
|           } | ||||
| 
 | ||||
|           if (translation.text_set) { | ||||
|             if (translation.text_set) { | ||||
|               report.lines.push({ | ||||
|                 subitems: [content.attributes.slug], | ||||
|                 name: "No Text Set", | ||||
|                 type: "Missing", | ||||
|                 severity: "Medium", | ||||
|                 description: "The Content has no Text Set.", | ||||
|                 recommandation: "", | ||||
|                 backendUrl: backendUrl, | ||||
|                 frontendUrl: frontendUrl, | ||||
|               }); | ||||
|             } else { | ||||
|               /* | ||||
|                *const textSetLanguages: string[] = []; | ||||
|                *if (content.attributes && textSet) { | ||||
|                *  if (textSet.language?.data?.id) { | ||||
|                *    if (textSet.language.data.id in textSetLanguages) { | ||||
|                *      report.lines.push({ | ||||
|                *        subitems: [ | ||||
|                *          content.attributes.slug, | ||||
|                *          `TextSet ${textSetIndex.toString()}`, | ||||
|                *        ], | ||||
|                *        name: "Duplicate Language", | ||||
|                *        type: "Error", | ||||
|                *        severity: "High", | ||||
|                *        description: "", | ||||
|                *        recommandation: "", | ||||
|                *        backendUrl: backendUrl, | ||||
|                *        frontendUrl: frontendUrl, | ||||
|                *      }); | ||||
|                *    } else { | ||||
|                *      textSetLanguages.push(textSet.language.data.id); | ||||
|                *    } | ||||
|                *  } else { | ||||
|                *    report.lines.push({ | ||||
|                *      subitems: [ | ||||
|                *        content.attributes.slug, | ||||
|                *        `TextSet ${textSetIndex.toString()}`, | ||||
|                *      ], | ||||
|                *      name: "No Language", | ||||
|                *      type: "Error", | ||||
|                *      severity: "Very High", | ||||
|                *      description: "", | ||||
|                *      recommandation: "", | ||||
|                *      backendUrl: backendUrl, | ||||
|                *      frontendUrl: frontendUrl, | ||||
|                *    }); | ||||
|                *  } | ||||
|                * | ||||
|                *  if (!textSet.source_language?.data?.id) { | ||||
|                *    report.lines.push({ | ||||
|                *      subitems: [ | ||||
|                *        content.attributes.slug, | ||||
|                *        `TextSet ${textSetIndex.toString()}`, | ||||
|                *      ], | ||||
|                *      name: "No Source Language", | ||||
|                *      type: "Error", | ||||
|                *      severity: "High", | ||||
|                *      description: "", | ||||
|                *      recommandation: "", | ||||
|                *      backendUrl: backendUrl, | ||||
|                *      frontendUrl: frontendUrl, | ||||
|                *    }); | ||||
|                *  } | ||||
|                * | ||||
|                *  if (textSet.status !== Enum_Componentsetstextset_Status.Done) { | ||||
|                *    report.lines.push({ | ||||
|                *      subitems: [ | ||||
|                *        content.attributes.slug, | ||||
|                *        `TextSet ${textSetIndex.toString()}`, | ||||
|                *      ], | ||||
|                *      name: "Not Done Status", | ||||
|                *      type: "Improvement", | ||||
|                *      severity: "Low", | ||||
|                *      description: "", | ||||
|                *      recommandation: "", | ||||
|                *      backendUrl: backendUrl, | ||||
|                *      frontendUrl: frontendUrl, | ||||
|                *    }); | ||||
|                *  } | ||||
|                * | ||||
|                *  if (!textSet.text || textSet.text.length < 10) { | ||||
|                *    report.lines.push({ | ||||
|                *      subitems: [ | ||||
|                *        content.attributes.slug, | ||||
|                *        `TextSet ${textSetIndex.toString()}`, | ||||
|                *      ], | ||||
|                *      name: "No Text", | ||||
|                *      type: "Missing", | ||||
|                *      severity: "Medium", | ||||
|                *      description: "", | ||||
|                *      recommandation: "", | ||||
|                *      backendUrl: backendUrl, | ||||
|                *      frontendUrl: frontendUrl, | ||||
|                *    }); | ||||
|                *  } | ||||
|                * | ||||
|                *  if ( | ||||
|                *    textSet.source_language?.data?.id === | ||||
|                *    textSet.language?.data?.id | ||||
|                *  ) { | ||||
|                *    if (textSet.transcribers?.data.length === 0) { | ||||
|                *      report.lines.push({ | ||||
|                *        subitems: [ | ||||
|                *          content.attributes.slug, | ||||
|                *          `TextSet ${textSetIndex.toString()}`, | ||||
|                *        ], | ||||
|                *        name: "No Transcribers", | ||||
|                *        type: "Missing", | ||||
|                *        severity: "High", | ||||
|                *        description: | ||||
|                *          "The Content is a Transcription but doesn't credit any Transcribers.", | ||||
|                *        recommandation: "Add the appropriate Transcribers.", | ||||
|                *        backendUrl: backendUrl, | ||||
|                *        frontendUrl: frontendUrl, | ||||
|                *      }); | ||||
|                *    } | ||||
|                *    if ( | ||||
|                *      textSet.translators?.data && | ||||
|                *      textSet.translators.data.length > 0 | ||||
|                *    ) { | ||||
|                *      report.lines.push({ | ||||
|                *        subitems: [ | ||||
|                *          content.attributes.slug, | ||||
|                *          `TextSet ${textSetIndex.toString()}`, | ||||
|                *        ], | ||||
|                *        name: "Credited Translators", | ||||
|                *        type: "Error", | ||||
|                *        severity: "High", | ||||
|                *        description: | ||||
|                *          "The Content is a Transcription but credits one or more Translators.", | ||||
|                *        recommandation: | ||||
|                *          "If appropriate, create a Translation Text Set with the Translator credited there.", | ||||
|                *        backendUrl: backendUrl, | ||||
|                *        frontendUrl: frontendUrl, | ||||
|                *      }); | ||||
|                *    } | ||||
|                *  } else { | ||||
|                *    if (textSet.translators?.data.length === 0) { | ||||
|                *      report.lines.push({ | ||||
|                *        subitems: [ | ||||
|                *          content.attributes.slug, | ||||
|                *          `TextSet ${textSetIndex.toString()}`, | ||||
|                *        ], | ||||
|                *        name: "No Translators", | ||||
|                *        type: "Missing", | ||||
|                *        severity: "High", | ||||
|                *        description: | ||||
|                *          "The Content is a Transcription but doesn't credit any Translators.", | ||||
|                *        recommandation: "Add the appropriate Translators.", | ||||
|                *        backendUrl: backendUrl, | ||||
|                *        frontendUrl: frontendUrl, | ||||
|                *      }); | ||||
|                *    } | ||||
|                *    if ( | ||||
|                *      textSet.transcribers?.data && | ||||
|                *      textSet.transcribers.data.length > 0 | ||||
|                *    ) { | ||||
|                *      report.lines.push({ | ||||
|                *        subitems: [ | ||||
|                *          content.attributes.slug, | ||||
|                *          `TextSet ${textSetIndex.toString()}`, | ||||
|                *        ], | ||||
|                *        name: "Credited Transcribers", | ||||
|                *        type: "Error", | ||||
|                *        severity: "High", | ||||
|                *        description: | ||||
|                *          "The Content is a Translation but credits one or more Transcribers.", | ||||
|                *        recommandation: | ||||
|                *          "If appropriate, create a Transcription Text Set with the Transcribers credited there.", | ||||
|                *        backendUrl: backendUrl, | ||||
|                *        frontendUrl: frontendUrl, | ||||
|                *      }); | ||||
|                *    } | ||||
|                *  } | ||||
|                *} | ||||
|                */ | ||||
|             } | ||||
| 
 | ||||
|             report.lines.push({ | ||||
|               subitems: [content.attributes.slug], | ||||
|               name: "No Text Set", | ||||
|               name: "No Sets", | ||||
|               type: "Missing", | ||||
|               severity: "Medium", | ||||
|               description: "The Content has no Text Set.", | ||||
|               description: "The Content has no Sets.", | ||||
|               recommandation: "", | ||||
|               backendUrl: backendUrl, | ||||
|               frontendUrl: frontendUrl, | ||||
|             }); | ||||
|           } else { | ||||
|             /* | ||||
|              *const textSetLanguages: string[] = []; | ||||
|              *if (content.attributes && textSet) { | ||||
|              *  if (textSet.language?.data?.id) { | ||||
|              *    if (textSet.language.data.id in textSetLanguages) { | ||||
|              *      report.lines.push({ | ||||
|              *        subitems: [ | ||||
|              *          content.attributes.slug, | ||||
|              *          `TextSet ${textSetIndex.toString()}`, | ||||
|              *        ], | ||||
|              *        name: "Duplicate Language", | ||||
|              *        type: "Error", | ||||
|              *        severity: "High", | ||||
|              *        description: "", | ||||
|              *        recommandation: "", | ||||
|              *        backendUrl: backendUrl, | ||||
|              *        frontendUrl: frontendUrl, | ||||
|              *      }); | ||||
|              *    } else { | ||||
|              *      textSetLanguages.push(textSet.language.data.id); | ||||
|              *    } | ||||
|              *  } else { | ||||
|              *    report.lines.push({ | ||||
|              *      subitems: [ | ||||
|              *        content.attributes.slug, | ||||
|              *        `TextSet ${textSetIndex.toString()}`, | ||||
|              *      ], | ||||
|              *      name: "No Language", | ||||
|              *      type: "Error", | ||||
|              *      severity: "Very High", | ||||
|              *      description: "", | ||||
|              *      recommandation: "", | ||||
|              *      backendUrl: backendUrl, | ||||
|              *      frontendUrl: frontendUrl, | ||||
|              *    }); | ||||
|              *  } | ||||
|              * | ||||
|              *  if (!textSet.source_language?.data?.id) { | ||||
|              *    report.lines.push({ | ||||
|              *      subitems: [ | ||||
|              *        content.attributes.slug, | ||||
|              *        `TextSet ${textSetIndex.toString()}`, | ||||
|              *      ], | ||||
|              *      name: "No Source Language", | ||||
|              *      type: "Error", | ||||
|              *      severity: "High", | ||||
|              *      description: "", | ||||
|              *      recommandation: "", | ||||
|              *      backendUrl: backendUrl, | ||||
|              *      frontendUrl: frontendUrl, | ||||
|              *    }); | ||||
|              *  } | ||||
|              * | ||||
|              *  if (textSet.status !== Enum_Componentsetstextset_Status.Done) { | ||||
|              *    report.lines.push({ | ||||
|              *      subitems: [ | ||||
|              *        content.attributes.slug, | ||||
|              *        `TextSet ${textSetIndex.toString()}`, | ||||
|              *      ], | ||||
|              *      name: "Not Done Status", | ||||
|              *      type: "Improvement", | ||||
|              *      severity: "Low", | ||||
|              *      description: "", | ||||
|              *      recommandation: "", | ||||
|              *      backendUrl: backendUrl, | ||||
|              *      frontendUrl: frontendUrl, | ||||
|              *    }); | ||||
|              *  } | ||||
|              * | ||||
|              *  if (!textSet.text || textSet.text.length < 10) { | ||||
|              *    report.lines.push({ | ||||
|              *      subitems: [ | ||||
|              *        content.attributes.slug, | ||||
|              *        `TextSet ${textSetIndex.toString()}`, | ||||
|              *      ], | ||||
|              *      name: "No Text", | ||||
|              *      type: "Missing", | ||||
|              *      severity: "Medium", | ||||
|              *      description: "", | ||||
|              *      recommandation: "", | ||||
|              *      backendUrl: backendUrl, | ||||
|              *      frontendUrl: frontendUrl, | ||||
|              *    }); | ||||
|              *  } | ||||
|              * | ||||
|              *  if ( | ||||
|              *    textSet.source_language?.data?.id === | ||||
|              *    textSet.language?.data?.id | ||||
|              *  ) { | ||||
|              *    if (textSet.transcribers?.data.length === 0) { | ||||
|              *      report.lines.push({ | ||||
|              *        subitems: [ | ||||
|              *          content.attributes.slug, | ||||
|              *          `TextSet ${textSetIndex.toString()}`, | ||||
|              *        ], | ||||
|              *        name: "No Transcribers", | ||||
|              *        type: "Missing", | ||||
|              *        severity: "High", | ||||
|              *        description: | ||||
|              *          "The Content is a Transcription but doesn't credit any Transcribers.", | ||||
|              *        recommandation: "Add the appropriate Transcribers.", | ||||
|              *        backendUrl: backendUrl, | ||||
|              *        frontendUrl: frontendUrl, | ||||
|              *      }); | ||||
|              *    } | ||||
|              *    if ( | ||||
|              *      textSet.translators?.data && | ||||
|              *      textSet.translators.data.length > 0 | ||||
|              *    ) { | ||||
|              *      report.lines.push({ | ||||
|              *        subitems: [ | ||||
|              *          content.attributes.slug, | ||||
|              *          `TextSet ${textSetIndex.toString()}`, | ||||
|              *        ], | ||||
|              *        name: "Credited Translators", | ||||
|              *        type: "Error", | ||||
|              *        severity: "High", | ||||
|              *        description: | ||||
|              *          "The Content is a Transcription but credits one or more Translators.", | ||||
|              *        recommandation: | ||||
|              *          "If appropriate, create a Translation Text Set with the Translator credited there.", | ||||
|              *        backendUrl: backendUrl, | ||||
|              *        frontendUrl: frontendUrl, | ||||
|              *      }); | ||||
|              *    } | ||||
|              *  } else { | ||||
|              *    if (textSet.translators?.data.length === 0) { | ||||
|              *      report.lines.push({ | ||||
|              *        subitems: [ | ||||
|              *          content.attributes.slug, | ||||
|              *          `TextSet ${textSetIndex.toString()}`, | ||||
|              *        ], | ||||
|              *        name: "No Translators", | ||||
|              *        type: "Missing", | ||||
|              *        severity: "High", | ||||
|              *        description: | ||||
|              *          "The Content is a Transcription but doesn't credit any Translators.", | ||||
|              *        recommandation: "Add the appropriate Translators.", | ||||
|              *        backendUrl: backendUrl, | ||||
|              *        frontendUrl: frontendUrl, | ||||
|              *      }); | ||||
|              *    } | ||||
|              *    if ( | ||||
|              *      textSet.transcribers?.data && | ||||
|              *      textSet.transcribers.data.length > 0 | ||||
|              *    ) { | ||||
|              *      report.lines.push({ | ||||
|              *        subitems: [ | ||||
|              *          content.attributes.slug, | ||||
|              *          `TextSet ${textSetIndex.toString()}`, | ||||
|              *        ], | ||||
|              *        name: "Credited Transcribers", | ||||
|              *        type: "Error", | ||||
|              *        severity: "High", | ||||
|              *        description: | ||||
|              *          "The Content is a Translation but credits one or more Transcribers.", | ||||
|              *        recommandation: | ||||
|              *          "If appropriate, create a Transcription Text Set with the Transcribers credited there.", | ||||
|              *        backendUrl: backendUrl, | ||||
|              *        frontendUrl: frontendUrl, | ||||
|              *      }); | ||||
|              *    } | ||||
|              *  } | ||||
|              *} | ||||
|              */ | ||||
|           } | ||||
| 
 | ||||
|           report.lines.push({ | ||||
|             subitems: [content.attributes.slug], | ||||
|             name: "No Sets", | ||||
|             type: "Missing", | ||||
|             severity: "Medium", | ||||
|             description: "The Content has no Sets.", | ||||
|             recommandation: "", | ||||
|             backendUrl: backendUrl, | ||||
|             frontendUrl: frontendUrl, | ||||
|           }); | ||||
|         } | ||||
|       ); | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
|   ); | ||||
|   return report; | ||||
| }; | ||||
|  | ||||
| @ -65,7 +65,7 @@ const CheckupLibraryItems = ({ | ||||
|             /> | ||||
|             <p>{line.subitems.join(" -> ")}</p> | ||||
|             <p>{line.name}</p> | ||||
|             <Chip>{line.type}</Chip> | ||||
|             <Chip text={line.type} /> | ||||
|             <Chip | ||||
|               className={ | ||||
|                 line.severity === "Very High" | ||||
| @ -76,9 +76,8 @@ const CheckupLibraryItems = ({ | ||||
|                   ? "bg-[#fff344] !opacity-100" | ||||
|                   : "" | ||||
|               } | ||||
|             > | ||||
|               {line.severity} | ||||
|             </Chip> | ||||
|               text={line.severity} | ||||
|             /> | ||||
|             <ToolTip content={line.recommandation} placement="left"> | ||||
|               <p>{line.description}</p> | ||||
|             </ToolTip> | ||||
|  | ||||
| @ -221,15 +221,17 @@ const LibrarySlug = ({ | ||||
|                   {item.urls?.length ? ( | ||||
|                     <div className="flex flex-row place-items-center gap-3"> | ||||
|                       <p>{langui.available_at}</p> | ||||
|                       {filterHasAttributes(item.urls).map((url, index) => ( | ||||
|                         <Fragment key={index}> | ||||
|                           <Button | ||||
|                             href={url.url} | ||||
|                             target={"_blank"} | ||||
|                             text={prettyURL(url.url)} | ||||
|                           /> | ||||
|                         </Fragment> | ||||
|                       ))} | ||||
|                       {filterHasAttributes(item.urls, ["url"] as const).map( | ||||
|                         (url, index) => ( | ||||
|                           <Fragment key={index}> | ||||
|                             <Button | ||||
|                               href={url.url} | ||||
|                               target={"_blank"} | ||||
|                               text={prettyURL(url.url)} | ||||
|                             /> | ||||
|                           </Fragment> | ||||
|                         ) | ||||
|                       )} | ||||
|                     </div> | ||||
|                   ) : ( | ||||
|                     <p>{langui.item_not_available}</p> | ||||
| @ -246,33 +248,32 @@ const LibrarySlug = ({ | ||||
|                 className="grid w-full grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-end | ||||
|               gap-8" | ||||
|               > | ||||
|                 {filterHasAttributes(item.gallery.data).map( | ||||
|                   (galleryItem, index) => ( | ||||
|                     <Fragment key={galleryItem.id}> | ||||
|                       <div | ||||
|                         className="relative aspect-square cursor-pointer | ||||
|                 {filterHasAttributes(item.gallery.data, [ | ||||
|                   "id", | ||||
|                   "attributes", | ||||
|                 ] as const).map((galleryItem, index) => ( | ||||
|                   <Fragment key={galleryItem.id}> | ||||
|                     <div | ||||
|                       className="relative aspect-square cursor-pointer | ||||
|                       transition-transform hover:scale-[1.02]" | ||||
|                         onClick={() => { | ||||
|                           const images: string[] = filterHasAttributes( | ||||
|                             item.gallery?.data | ||||
|                           ).map((image) => | ||||
|                             getAssetURL( | ||||
|                               image.attributes.url, | ||||
|                               ImageQuality.Large | ||||
|                             ) | ||||
|                           ); | ||||
|                           openLightBox(images, index); | ||||
|                         }} | ||||
|                       > | ||||
|                         <Img | ||||
|                           className="h-full w-full rounded-lg | ||||
|                       onClick={() => { | ||||
|                         const images: string[] = filterHasAttributes( | ||||
|                           item.gallery?.data, | ||||
|                           ["attributes"] as const | ||||
|                         ).map((image) => | ||||
|                           getAssetURL(image.attributes.url, ImageQuality.Large) | ||||
|                         ); | ||||
|                         openLightBox(images, index); | ||||
|                       }} | ||||
|                     > | ||||
|                       <Img | ||||
|                         className="h-full w-full rounded-lg | ||||
|                         bg-light object-cover drop-shadow-shade-md" | ||||
|                           image={galleryItem.attributes} | ||||
|                         /> | ||||
|                       </div> | ||||
|                     </Fragment> | ||||
|                   ) | ||||
|                 )} | ||||
|                         image={galleryItem.attributes} | ||||
|                       /> | ||||
|                     </div> | ||||
|                   </Fragment> | ||||
|                 ))} | ||||
|               </div> | ||||
|             </div> | ||||
|           )} | ||||
| @ -288,9 +289,9 @@ const LibrarySlug = ({ | ||||
|                   <div className="grid place-content-start place-items-center"> | ||||
|                     <h3 className="text-xl">{langui.type}</h3> | ||||
|                     <div className="grid grid-flow-col gap-1"> | ||||
|                       <Chip>{prettyItemType(item.metadata[0], langui)}</Chip> | ||||
|                       <Chip text={prettyItemType(item.metadata[0], langui)} /> | ||||
|                       {"›"} | ||||
|                       <Chip>{prettyItemSubType(item.metadata[0])}</Chip> | ||||
|                       <Chip text={prettyItemSubType(item.metadata[0])} /> | ||||
|                     </div> | ||||
|                   </div> | ||||
|                 )} | ||||
| @ -331,8 +332,10 @@ const LibrarySlug = ({ | ||||
|                 <div className="flex flex-col place-items-center gap-2"> | ||||
|                   <h3 className="text-xl">{langui.categories}</h3> | ||||
|                   <div className="flex flex-row flex-wrap place-content-center gap-2"> | ||||
|                     {item.categories.data.map((category) => ( | ||||
|                       <Chip key={category.id}>{category.attributes?.name}</Chip> | ||||
|                     {filterHasAttributes(item.categories.data, [ | ||||
|                       "attributes", | ||||
|                     ] as const).map((category) => ( | ||||
|                       <Chip key={category.id} text={category.attributes.name} /> | ||||
|                     ))} | ||||
|                   </div> | ||||
|                 </div> | ||||
| @ -458,7 +461,10 @@ const LibrarySlug = ({ | ||||
|                 className="grid w-full grid-cols-[repeat(auto-fill,minmax(15rem,1fr))] | ||||
|               items-end gap-8 mobile:grid-cols-2 thin:grid-cols-1" | ||||
|               > | ||||
|                 {filterHasAttributes(item.subitems.data).map((subitem) => ( | ||||
|                 {filterHasAttributes(item.subitems.data, [ | ||||
|                   "id", | ||||
|                   "attributes", | ||||
|                 ] as const).map((subitem) => ( | ||||
|                   <Fragment key={subitem.id}> | ||||
|                     <PreviewCard | ||||
|                       href={`/library/${subitem.attributes.slug}`} | ||||
| @ -506,56 +512,57 @@ const LibrarySlug = ({ | ||||
|                 /> | ||||
|               )} | ||||
|               <div className="grid w-full gap-4"> | ||||
|                 {filterHasAttributes(item.contents.data).map( | ||||
|                   (rangedContent) => ( | ||||
|                     <ContentLine | ||||
|                       content={ | ||||
|                         rangedContent.attributes.content?.data?.attributes | ||||
|                           ? { | ||||
|                               translations: filterDefined( | ||||
|                 {filterHasAttributes(item.contents.data, [ | ||||
|                   "attributes", | ||||
|                 ] as const).map((rangedContent) => ( | ||||
|                   <ContentLine | ||||
|                     content={ | ||||
|                       rangedContent.attributes.content?.data?.attributes | ||||
|                         ? { | ||||
|                             translations: filterDefined( | ||||
|                               rangedContent.attributes.content.data.attributes | ||||
|                                 .translations | ||||
|                             ).map((translation) => ({ | ||||
|                               pre_title: translation.pre_title, | ||||
|                               title: translation.title, | ||||
|                               subtitle: translation.subtitle, | ||||
|                               language: | ||||
|                                 translation.language?.data?.attributes?.code, | ||||
|                             })), | ||||
|                             categories: filterHasAttributes( | ||||
|                               rangedContent.attributes.content.data.attributes | ||||
|                                 .categories?.data, | ||||
|                               ["attributes"] | ||||
|                             ).map((category) => category.attributes.short), | ||||
|                             type: | ||||
|                               rangedContent.attributes.content.data.attributes | ||||
|                                 .type?.data?.attributes?.titles?.[0]?.title ?? | ||||
|                               prettySlug( | ||||
|                                 rangedContent.attributes.content.data.attributes | ||||
|                                   .translations | ||||
|                               ).map((translation) => ({ | ||||
|                                 pre_title: translation.pre_title, | ||||
|                                 title: translation.title, | ||||
|                                 subtitle: translation.subtitle, | ||||
|                                 language: | ||||
|                                   translation.language?.data?.attributes?.code, | ||||
|                               })), | ||||
|                               categories: filterHasAttributes( | ||||
|                                 rangedContent.attributes.content.data.attributes | ||||
|                                   .categories?.data | ||||
|                               ).map((category) => category.attributes.short), | ||||
|                               type: | ||||
|                                 rangedContent.attributes.content.data.attributes | ||||
|                                   .type?.data?.attributes?.titles?.[0]?.title ?? | ||||
|                                 prettySlug( | ||||
|                                   rangedContent.attributes.content.data | ||||
|                                     .attributes.type?.data?.attributes?.slug | ||||
|                                 ), | ||||
|                               slug: rangedContent.attributes.content.data | ||||
|                                 .attributes.slug, | ||||
|                             } | ||||
|                           : undefined | ||||
|                       } | ||||
|                       langui={langui} | ||||
|                       rangeStart={ | ||||
|                         rangedContent.attributes.range[0]?.__typename === | ||||
|                         "ComponentRangePageRange" | ||||
|                           ? `${rangedContent.attributes.range[0].starting_page}` | ||||
|                           : "" | ||||
|                       } | ||||
|                       slug={rangedContent.attributes.slug} | ||||
|                       parentSlug={item.slug} | ||||
|                       key={rangedContent.id} | ||||
|                       languages={languages} | ||||
|                       hasScanSet={ | ||||
|                         isDefined(rangedContent.attributes.scan_set) && | ||||
|                         rangedContent.attributes.scan_set.length > 0 | ||||
|                       } | ||||
|                     /> | ||||
|                   ) | ||||
|                 )} | ||||
|                                   .type?.data?.attributes?.slug | ||||
|                               ), | ||||
|                             slug: rangedContent.attributes.content.data | ||||
|                               .attributes.slug, | ||||
|                           } | ||||
|                         : undefined | ||||
|                     } | ||||
|                     langui={langui} | ||||
|                     rangeStart={ | ||||
|                       rangedContent.attributes.range[0]?.__typename === | ||||
|                       "ComponentRangePageRange" | ||||
|                         ? `${rangedContent.attributes.range[0].starting_page}` | ||||
|                         : "" | ||||
|                     } | ||||
|                     slug={rangedContent.attributes.slug} | ||||
|                     parentSlug={item.slug} | ||||
|                     key={rangedContent.id} | ||||
|                     languages={languages} | ||||
|                     hasScanSet={ | ||||
|                       isDefined(rangedContent.attributes.scan_set) && | ||||
|                       rangedContent.attributes.scan_set.length > 0 | ||||
|                     } | ||||
|                   /> | ||||
|                 ))} | ||||
|               </div> | ||||
|             </div> | ||||
|           )} | ||||
| @ -643,7 +650,9 @@ export const getStaticPaths: GetStaticPaths = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const libraryItems = await sdk.getLibraryItemsSlugs(); | ||||
|   const paths: GetStaticPathsResult["paths"] = []; | ||||
|   filterHasAttributes(libraryItems.libraryItems?.data).map((item) => { | ||||
|   filterHasAttributes(libraryItems.libraryItems?.data, [ | ||||
|     "attributes", | ||||
|   ] as const).map((item) => { | ||||
|     context.locales?.map((local) => | ||||
|       paths.push({ params: { slug: item.attributes.slug }, locale: local }) | ||||
|     ); | ||||
| @ -702,8 +711,6 @@ const ContentLine = ({ | ||||
|     ), | ||||
|   }); | ||||
| 
 | ||||
|   console.log(prettySlug(slug, parentSlug)); | ||||
| 
 | ||||
|   return ( | ||||
|     <div | ||||
|       className={cJoin( | ||||
| @ -730,13 +737,13 @@ const ContentLine = ({ | ||||
|         </a> | ||||
|         <div className="flex flex-row flex-wrap gap-1"> | ||||
|           {content?.categories?.map((category, index) => ( | ||||
|             <Chip key={index}>{category}</Chip> | ||||
|             <Chip key={index} text={category} /> | ||||
|           ))} | ||||
|         </div> | ||||
|         <p className="h-4 w-full border-b-2 border-dotted border-black opacity-30"></p> | ||||
|         <p>{rangeStart}</p> | ||||
|         {content?.type && ( | ||||
|           <Chip className="justify-self-end thin:hidden">{content.type}</Chip> | ||||
|           <Chip className="justify-self-end thin:hidden" text={content.type} /> | ||||
|         )} | ||||
|       </div> | ||||
|       <div | ||||
|  | ||||
| @ -177,7 +177,9 @@ export const getStaticPaths: GetStaticPaths = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const libraryItems = await sdk.getLibraryItemsSlugs({}); | ||||
|   const paths: GetStaticPathsResult["paths"] = []; | ||||
|   filterHasAttributes(libraryItems.libraryItems?.data).map((item) => { | ||||
|   filterHasAttributes(libraryItems.libraryItems?.data, [ | ||||
|     "attributes", | ||||
|   ] as const).map((item) => { | ||||
|     context.locales?.map((local) => | ||||
|       paths.push({ params: { slug: item.attributes.slug }, locale: local }) | ||||
|     ); | ||||
|  | ||||
| @ -17,10 +17,7 @@ import { | ||||
|   prettyinlineTitle, | ||||
|   prettyItemSubType, | ||||
| } from "helpers/formatters"; | ||||
| import { | ||||
|   LibraryItemUserStatus, | ||||
|   SelectiveRequiredNonNullable, | ||||
| } from "helpers/types"; | ||||
| import { LibraryItemUserStatus } from "helpers/types"; | ||||
| import { Icon } from "components/Ico"; | ||||
| import { WithLabel } from "components/Inputs/WithLabel"; | ||||
| import { TextInput } from "components/Inputs/TextInput"; | ||||
| @ -35,6 +32,7 @@ import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholde | ||||
| import { useAppLayout } from "contexts/AppLayoutContext"; | ||||
| import { convertPrice } from "helpers/numbers"; | ||||
| import { SmartList } from "components/SmartList"; | ||||
| import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable"; | ||||
| 
 | ||||
| /* | ||||
|  *                                         ╭─────────────╮ | ||||
| @ -97,10 +95,7 @@ const Library = ({ | ||||
| 
 | ||||
|   const filteringFunction = useCallback( | ||||
|     ( | ||||
|       item: SelectiveRequiredNonNullable< | ||||
|         Props["items"][number], | ||||
|         "attributes" | "id" | ||||
|       > | ||||
|       item: SelectiveNonNullable<Props["items"][number], "attributes" | "id"> | ||||
|     ) => { | ||||
|       if (!showSubitems && !item.attributes.root_item) return false; | ||||
|       if ( | ||||
| @ -143,14 +138,8 @@ const Library = ({ | ||||
| 
 | ||||
|   const sortingFunction = useCallback( | ||||
|     ( | ||||
|       a: SelectiveRequiredNonNullable< | ||||
|         Props["items"][number], | ||||
|         "attributes" | "id" | ||||
|       >, | ||||
|       b: SelectiveRequiredNonNullable< | ||||
|         Props["items"][number], | ||||
|         "attributes" | "id" | ||||
|       > | ||||
|       a: SelectiveNonNullable<Props["items"][number], "attributes" | "id">, | ||||
|       b: SelectiveNonNullable<Props["items"][number], "attributes" | "id"> | ||||
|     ) => { | ||||
|       switch (sortingMethod) { | ||||
|         case 0: { | ||||
| @ -193,15 +182,13 @@ const Library = ({ | ||||
| 
 | ||||
|   const groupingFunction = useCallback( | ||||
|     ( | ||||
|       item: SelectiveRequiredNonNullable< | ||||
|         Props["items"][number], | ||||
|         "attributes" | "id" | ||||
|       > | ||||
|       item: SelectiveNonNullable<Props["items"][number], "attributes" | "id"> | ||||
|     ): string[] => { | ||||
|       switch (groupingMethod) { | ||||
|         case 0: { | ||||
|           const categories = filterHasAttributes( | ||||
|             item.attributes.categories?.data | ||||
|             item.attributes.categories?.data, | ||||
|             ["attributes"] as const | ||||
|           ); | ||||
|           if (categories.length > 0) { | ||||
|             return categories.map((category) => category.attributes.name); | ||||
| @ -406,7 +393,7 @@ const Library = ({ | ||||
|     () => ( | ||||
|       <ContentPanel width={ContentPanelWidthSizes.Full}> | ||||
|         <SmartList | ||||
|           items={filterHasAttributes(items)} | ||||
|           items={filterHasAttributes(items, ["id", "attributes"] as const)} | ||||
|           getItemId={(item) => item.id} | ||||
|           renderItem={({ item }) => ( | ||||
|             <PreviewCard | ||||
|  | ||||
| @ -47,11 +47,13 @@ export const getStaticPaths: GetStaticPaths = async (context) => { | ||||
|   const posts = await sdk.getPostsSlugs(); | ||||
|   const paths: GetStaticPathsResult["paths"] = []; | ||||
| 
 | ||||
|   filterHasAttributes(posts.posts?.data).map((item) => { | ||||
|     context.locales?.map((local) => | ||||
|       paths.push({ params: { slug: item.attributes.slug }, locale: local }) | ||||
|     ); | ||||
|   }); | ||||
|   filterHasAttributes(posts.posts?.data, ["attributes"] as const).map( | ||||
|     (item) => { | ||||
|       context.locales?.map((local) => | ||||
|         paths.push({ params: { slug: item.attributes.slug }, locale: local }) | ||||
|       ); | ||||
|     } | ||||
|   ); | ||||
|   return { | ||||
|     paths, | ||||
|     fallback: "blocking", | ||||
|  | ||||
| @ -97,7 +97,7 @@ const News = ({ | ||||
|     () => ( | ||||
|       <ContentPanel width={ContentPanelWidthSizes.Full}> | ||||
|         <SmartList | ||||
|           items={filterHasAttributes(posts)} | ||||
|           items={filterHasAttributes(posts, ["attributes", "id"] as const)} | ||||
|           getItemId={(post) => post.id} | ||||
|           langui={langui} | ||||
|           renderItem={({ item: post }) => ( | ||||
|  | ||||
| @ -100,8 +100,10 @@ const WikiPage = ({ | ||||
|                   {langui.categories} | ||||
|                 </p> | ||||
|                 <div className="flex flex-row flex-wrap place-content-center gap-2"> | ||||
|                   {page.categories?.data.map((category) => ( | ||||
|                     <Chip key={category.id}>{category.attributes?.name}</Chip> | ||||
|                   {filterHasAttributes(page.categories?.data, [ | ||||
|                     "attributes", | ||||
|                   ] as const).map((category) => ( | ||||
|                     <Chip key={category.id} text={category.attributes.name} /> | ||||
|                   ))} | ||||
|                 </div> | ||||
|               </div> | ||||
| @ -116,30 +118,28 @@ const WikiPage = ({ | ||||
|               </div> | ||||
|             )} | ||||
| 
 | ||||
|             {filterHasAttributes(page.definitions, ["translations"]).map( | ||||
|               (definition, index) => ( | ||||
|                 <> | ||||
|                   <DefinitionCard | ||||
|                     key={index} | ||||
|                     source={definition.source?.data?.attributes?.name} | ||||
|                     translations={filterHasAttributes( | ||||
|                       definition.translations | ||||
|                     ).map((translation) => ({ | ||||
|                       language: translation.language.data?.attributes?.code, | ||||
|                       definition: translation.definition, | ||||
|                       status: translation.status, | ||||
|                     }))} | ||||
|                     index={index + 1} | ||||
|                     languages={languages} | ||||
|                     langui={langui} | ||||
|                     categories={filterHasAttributes( | ||||
|                       definition.categories?.data | ||||
|                     ).map((category) => category.attributes.short)} | ||||
|                   /> | ||||
|                   <br /> | ||||
|                 </> | ||||
|               ) | ||||
|             )} | ||||
|             {filterHasAttributes(page.definitions, [ | ||||
|               "translations", | ||||
|             ] as const).map((definition, index) => ( | ||||
|               <> | ||||
|                 <DefinitionCard | ||||
|                   key={index} | ||||
|                   source={definition.source?.data?.attributes?.name} | ||||
|                   translations={definition.translations.map((translation) => ({ | ||||
|                     language: translation?.language?.data?.attributes?.code, | ||||
|                     definition: translation?.definition, | ||||
|                     status: translation?.status, | ||||
|                   }))} | ||||
|                   index={index + 1} | ||||
|                   languages={languages} | ||||
|                   langui={langui} | ||||
|                   categories={filterHasAttributes(definition.categories?.data, [ | ||||
|                     "attributes", | ||||
|                   ] as const).map((category) => category.attributes.short)} | ||||
|                 /> | ||||
|                 <br /> | ||||
|               </> | ||||
|             ))} | ||||
|           </div> | ||||
|         )} | ||||
|       </ContentPanel> | ||||
| @ -194,14 +194,16 @@ export const getStaticPaths: GetStaticPaths = async (context) => { | ||||
|   const sdk = getReadySdk(); | ||||
|   const contents = await sdk.getWikiPagesSlugs(); | ||||
|   const paths: GetStaticPathsResult["paths"] = []; | ||||
|   filterHasAttributes(contents.wikiPages?.data).map((wikiPage) => { | ||||
|     context.locales?.map((local) => | ||||
|       paths.push({ | ||||
|         params: { slug: wikiPage.attributes.slug }, | ||||
|         locale: local, | ||||
|       }) | ||||
|     ); | ||||
|   }); | ||||
|   filterHasAttributes(contents.wikiPages?.data, ["attributes"] as const).map( | ||||
|     (wikiPage) => { | ||||
|       context.locales?.map((local) => | ||||
|         paths.push({ | ||||
|           params: { slug: wikiPage.attributes.slug }, | ||||
|           locale: local, | ||||
|         }) | ||||
|       ); | ||||
|     } | ||||
|   ); | ||||
|   return { | ||||
|     paths, | ||||
|     fallback: "blocking", | ||||
|  | ||||
| @ -74,22 +74,24 @@ const Chronology = ({ | ||||
|           horizontalLine | ||||
|         /> | ||||
| 
 | ||||
|         {filterHasAttributes(chronologyEras).map((era) => ( | ||||
|           <Fragment key={era.id}> | ||||
|             <NavOption | ||||
|               url={`#${era.attributes.slug}`} | ||||
|               title={ | ||||
|                 era.attributes.title && | ||||
|                 era.attributes.title.length > 0 && | ||||
|                 era.attributes.title[0] | ||||
|                   ? era.attributes.title[0].title | ||||
|                   : prettySlug(era.attributes.slug) | ||||
|               } | ||||
|               subtitle={`${era.attributes.starting_year} → ${era.attributes.ending_year}`} | ||||
|               border | ||||
|             /> | ||||
|           </Fragment> | ||||
|         ))} | ||||
|         {filterHasAttributes(chronologyEras, ["attributes", "id"] as const).map( | ||||
|           (era) => ( | ||||
|             <Fragment key={era.id}> | ||||
|               <NavOption | ||||
|                 url={`#${era.attributes.slug}`} | ||||
|                 title={ | ||||
|                   era.attributes.title && | ||||
|                   era.attributes.title.length > 0 && | ||||
|                   era.attributes.title[0] | ||||
|                     ? era.attributes.title[0].title | ||||
|                     : prettySlug(era.attributes.slug) | ||||
|                 } | ||||
|                 subtitle={`${era.attributes.starting_year} → ${era.attributes.ending_year}`} | ||||
|                 border | ||||
|               /> | ||||
|             </Fragment> | ||||
|           ) | ||||
|         )} | ||||
|       </SubPanel> | ||||
|     ), | ||||
|     [chronologyEras, langui] | ||||
|  | ||||
| @ -120,36 +120,37 @@ const Wiki = ({ | ||||
|               icon={Icon.ChevronLeft} | ||||
|             /> | ||||
|           )} | ||||
|           {filterHasAttributes(filteredPages).map((page) => ( | ||||
|           {filterHasAttributes(filteredPages, [ | ||||
|             "id", | ||||
|             "attributes.translations", | ||||
|           ] as const).map((page) => ( | ||||
|             <Fragment key={page.id}> | ||||
|               {page.attributes.translations && ( | ||||
|                 <TranslatedPreviewCard | ||||
|                   href={`/wiki/${page.attributes.slug}`} | ||||
|                   translations={page.attributes.translations.map( | ||||
|                     (translation) => ({ | ||||
|                       title: translation?.title, | ||||
|                       subtitle: | ||||
|                         translation?.aliases && translation.aliases.length > 0 | ||||
|                           ? translation.aliases | ||||
|                               .map((alias) => alias?.alias) | ||||
|                               .join(" | ") | ||||
|                           : undefined, | ||||
|                       description: translation?.summary, | ||||
|                       language: translation?.language?.data?.attributes?.code, | ||||
|                     }) | ||||
|                   )} | ||||
|                   thumbnail={page.attributes.thumbnail?.data?.attributes} | ||||
|                   thumbnailAspectRatio={"4/3"} | ||||
|                   thumbnailRounded | ||||
|                   thumbnailForceAspectRatio | ||||
|                   languages={languages} | ||||
|                   slug={page.attributes.slug} | ||||
|                   keepInfoVisible={keepInfoVisible} | ||||
|                   bottomChips={page.attributes.categories?.data.map( | ||||
|                     (category) => category.attributes?.short ?? "" | ||||
|                   )} | ||||
|                 /> | ||||
|               )} | ||||
|               <TranslatedPreviewCard | ||||
|                 href={`/wiki/${page.attributes.slug}`} | ||||
|                 translations={page.attributes.translations.map( | ||||
|                   (translation) => ({ | ||||
|                     title: translation?.title, | ||||
|                     subtitle: | ||||
|                       translation?.aliases && translation.aliases.length > 0 | ||||
|                         ? translation.aliases | ||||
|                             .map((alias) => alias?.alias) | ||||
|                             .join(" | ") | ||||
|                         : undefined, | ||||
|                     description: translation?.summary, | ||||
|                     language: translation?.language?.data?.attributes?.code, | ||||
|                   }) | ||||
|                 )} | ||||
|                 thumbnail={page.attributes.thumbnail?.data?.attributes} | ||||
|                 thumbnailAspectRatio={"4/3"} | ||||
|                 thumbnailRounded | ||||
|                 thumbnailForceAspectRatio | ||||
|                 languages={languages} | ||||
|                 slug={page.attributes.slug} | ||||
|                 keepInfoVisible={keepInfoVisible} | ||||
|                 bottomChips={page.attributes.categories?.data.map( | ||||
|                   (category) => category.attributes?.short ?? "" | ||||
|                 )} | ||||
|               /> | ||||
|             </Fragment> | ||||
|           ))} | ||||
|         </div> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DrMint
						DrMint