import { Link } from 'react-router-dom' import { Icon } from '../components/Icon' import type { IconName } from '../components/Icon' import { Badge } from '../components/ui' import type { CourseLevel, LessonType, PublicCourseCard } from '../api/types' /** * Drobne czesci wspolne dla stron Akademii. * * Kafelek kursu pokazuje sie na stronie glownej, na liscie kursow i w cenniku, * a pasek postepu na pulpicie i w odtwarzaczu. Trzy kopie tego samego kafelka * rozjechalyby sie przy pierwszej poprawce. */ export const LEVEL_LABEL: Record = { BASIC: 'Podstawowy', INTERMEDIATE: 'Średniozaawansowany', ADVANCED: 'Zaawansowany', } export const LESSON_TYPE_LABEL: Record = { VIDEO: 'Nagranie', TEXT: 'Materiał do czytania', FILE: 'Plik do pobrania', WEBINAR: 'Webinar', QUIZ: 'Sprawdzian', } export const LESSON_TYPE_ICON: Record = { VIDEO: 'play', TEXT: 'file-text', FILE: 'download', WEBINAR: 'presentation', QUIZ: 'list-checks', } /** * Cena brutto albo oznaczenie kursu darmowego. * * Brak ceny znaczy "darmowy", a nie "cena zero": to dwie rozne decyzje, * rozstrzygniete po stronie backendu przez `Course.isFree()`. */ export function formatPrice(price: string | number | null, currency: string) { if (price === null || price === undefined) { return 'Bezpłatny' } const value = typeof price === 'string' ? Number(price) : price if (!Number.isFinite(value)) { return 'Bezpłatny' } return new Intl.NumberFormat('pl-PL', { style: 'currency', currency: currency || 'PLN', minimumFractionDigits: 2, }).format(value) } /** Czas trwania w formie, w ktorej mysli czlowiek: godziny i minuty. */ export function formatCourseDuration(minutes: number | null) { if (!minutes || minutes <= 0) { return null } const hours = Math.floor(minutes / 60) const rest = minutes % 60 if (hours === 0) { return `${rest} min` } return rest === 0 ? `${hours} godz.` : `${hours} godz. ${rest} min` } /** Adres okladki. Pusty identyfikator oznacza kafelek bez obrazka. */ export function mediaUrl(mediaId: number | null) { return mediaId ? `/api/public/media/${mediaId}` : null } export function CourseCover({ mediaId, title, className = '', }: { mediaId: number | null title: string className?: string }) { const url = mediaUrl(mediaId) if (!url) { return ( ) } return ( {`Okładka ) } /** Kafelek kursu na liscie. */ export function CourseCard({ course }: { course: PublicCourseCard }) { const duration = formatCourseDuration(course.durationMinutes) return (
{course.category && {course.category}} {course.free ? ( Bezpłatny ) : ( {formatPrice(course.priceGross, course.currency)} )}

{course.title}

{course.subtitle && (

{course.subtitle}

)}
{course.lessonCount} {lessonWord(course.lessonCount)} {duration && ( {duration} )} {course.level && {LEVEL_LABEL[course.level]}} {course.certificateEnabled && ( Certyfikat )}
) } /** Odmiana slowa "lekcja" przez liczbe: 1 lekcja, 2 lekcje, 5 lekcji. */ export function lessonWord(count: number) { if (count === 1) return 'lekcja' const rest10 = count % 10 const rest100 = count % 100 if (rest10 >= 2 && rest10 <= 4 && (rest100 < 12 || rest100 > 14)) return 'lekcje' return 'lekcji' } /** Pasek postepu kursu. Liczba pochodzi z backendu, z lekcji wymaganych. */ export function ProgressBar({ percent, label, className = '', }: { percent: number label?: string className?: string }) { const safe = Math.max(0, Math.min(100, percent)) return (
{label && (
{label} {safe}%
)}
) }