UNPKG

analytica-frontend-lib

Version:

Repositório público dos componentes utilizados nas plataformas da Analytica Ensino

1 lines 16.9 kB
{"version":3,"sources":["../src/components/AccessReportModal/AccessReportModal.tsx"],"sourcesContent":["import { type ReactNode } from 'react';\nimport Modal from '../Modal/Modal';\nimport { MetricBox } from '../shared/MetricBox';\nimport { LegendPieCard, type PieSlice } from '../shared/ChartComponents';\nimport {\n SectionTitle,\n UserHeader,\n ErrorContent,\n} from '../shared/ModalComponents';\nimport { REPORT_MODAL_VARIANT } from '../../types/common';\nimport { SimpleBarChart } from '../SimpleBarChart/SimpleBarChart';\n\n// ─── API Types ────────────────────────────────────────────────\n\n/**\n * Time + percentage pair returned by the backend for platform/hours breakdowns.\n */\nexport interface AccessReportTimePercentage {\n time: string;\n percentage: number;\n}\n\n/**\n * User context returned by the backend inside every access report response.\n */\nexport interface AccessReportUser {\n id: string;\n name: string;\n profileType: string;\n school: string;\n group: string | null;\n class: string | null;\n year: number;\n}\n\n/**\n * Platform access breakdown — web vs mobile.\n */\nexport interface AccessReportByPlatform {\n web: AccessReportTimePercentage;\n mobile: AccessReportTimePercentage;\n}\n\n/**\n * Full 200 response for STUDENT access report.\n * Mirrors backend shape from GET /access-report/access/user/:userId (student variant).\n */\nexport interface AccessReportStudentData {\n user: AccessReportUser;\n accessData: {\n totalTime: string;\n activitiesTime: string;\n contentTime: string;\n recommendedLessonsTime: string;\n simulationsTime: string;\n questionnairesTime: string;\n accessCount: number;\n lastAccess: string | null;\n };\n accessByPlatform: AccessReportByPlatform;\n hoursByItem: {\n activities: AccessReportTimePercentage;\n content: AccessReportTimePercentage;\n simulations: AccessReportTimePercentage;\n questionnaires: AccessReportTimePercentage;\n recommendedLessons: AccessReportTimePercentage;\n };\n}\n\n/**\n * Full 200 response for TEACHER / UNIT_MANAGER / REGIONAL_MANAGER access report.\n * Mirrors backend shape from GET /access-report/access/user/:userId (professional variant).\n */\nexport interface AccessReportProfessionalData {\n user: AccessReportUser;\n accessData: {\n totalTime: string;\n activitiesTime: string;\n recommendedLessonsTime: string;\n accessCount: number;\n lastAccess: string | null;\n };\n accessByPlatform: AccessReportByPlatform;\n hoursByItem: {\n activities: AccessReportTimePercentage;\n recommendedLessons: AccessReportTimePercentage;\n };\n}\n\n/** @deprecated Use {@link REPORT_MODAL_VARIANT} instead. Re-exported for backwards compatibility. */\nexport { REPORT_MODAL_VARIANT as AccessReportModalVariant } from '../../types/common';\n\n/**\n * Access count by period item for bar chart\n */\nexport interface AccessCountByPeriodItem {\n label: string;\n count: number;\n}\n\ninterface AccessReportModalBaseProps {\n isOpen: boolean;\n onClose: () => void;\n /** Modal title (default: \"Relatório de acesso\") */\n title?: string;\n loading?: boolean;\n error?: string | null;\n /** Optional access count by period data for rendering the access count chart */\n accessCountByPeriod?: AccessCountByPeriodItem[];\n}\n\ntype AccessReportModalStudentProps = {\n variant: REPORT_MODAL_VARIANT.STUDENT;\n data: AccessReportStudentData | null;\n};\n\ntype AccessReportModalProfessionalProps = {\n variant: REPORT_MODAL_VARIANT.PROFESSIONAL;\n data: AccessReportProfessionalData | null;\n};\n\nexport type AccessReportModalProps = AccessReportModalBaseProps &\n (AccessReportModalStudentProps | AccessReportModalProfessionalProps);\n\n// ─── Pie slice builders ───────────────────────────────────────\n\nfunction buildPlatformSlices(platform: AccessReportByPlatform): PieSlice[] {\n // The pie is sized by the percentage; the legend reads the time next to each\n // platform (as in the design), not the percentage.\n return [\n {\n label: 'Web',\n value: platform.web.percentage,\n displayValue: platform.web.time,\n colorClass: 'bg-success-700',\n color: 'var(--Success-success700, #206F3E)',\n },\n {\n label: 'Celular',\n value: platform.mobile.percentage,\n displayValue: platform.mobile.time,\n colorClass: 'bg-success-300',\n color: 'var(--Success-success300, #66B584)',\n },\n ];\n}\n\nfunction buildStudentHoursSlices(\n hoursByItem: AccessReportStudentData['hoursByItem']\n): PieSlice[] {\n // Order, labels and colors mirror STUDENT_CATEGORIES from the reports page\n // TimeChart so the modal breakdown matches the page chart 1:1.\n return [\n {\n label: 'Atividades',\n value: hoursByItem.activities.percentage,\n colorClass: 'bg-success-700',\n },\n {\n label: 'Simulados',\n value: hoursByItem.simulations.percentage,\n colorClass: 'bg-warning-300',\n },\n {\n label: 'Questionários',\n value: hoursByItem.questionnaires.percentage,\n colorClass: 'bg-indicator-info',\n },\n {\n label: 'Videoaulas',\n value: hoursByItem.content.percentage,\n colorClass: 'bg-success-300',\n },\n {\n label: 'Aulas recomendadas',\n value: hoursByItem.recommendedLessons.percentage,\n colorClass: 'bg-indicator-positive',\n },\n ];\n}\n\n// ─── Modal content ────────────────────────────────────────────\n\nconst ReportContentLayout = ({\n user,\n metricBoxes,\n platformSlices,\n hoursSlices,\n accessCountByPeriod,\n}: {\n user: AccessReportUser;\n metricBoxes: ReactNode;\n platformSlices: PieSlice[];\n hoursSlices: PieSlice[];\n accessCountByPeriod?: AccessCountByPeriodItem[];\n}) => (\n <div className=\"flex flex-col gap-6\">\n <UserHeader\n name={user.name}\n school={user.school}\n className={user.class ?? ''}\n year={user.year}\n />\n\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3\">{metricBoxes}</div>\n\n {accessCountByPeriod && accessCountByPeriod.length > 0 && (\n <SimpleBarChart\n data={accessCountByPeriod.map((item) => ({\n label: item.label,\n value: item.count,\n }))}\n title=\"Quantidade de acessos por período\"\n barColor=\"bg-info-500\"\n chartHeight={150}\n />\n )}\n\n <div className=\"flex flex-col gap-3\">\n <SectionTitle>Dados de acesso por plataforma</SectionTitle>\n <LegendPieCard slices={platformSlices} />\n </div>\n\n <div className=\"flex flex-col gap-3\">\n <SectionTitle>Dados de horas por item</SectionTitle>\n <LegendPieCard slices={hoursSlices} />\n </div>\n </div>\n);\n\nconst StudentModalContent = ({\n data,\n accessCountByPeriod,\n}: {\n data: AccessReportStudentData;\n accessCountByPeriod?: AccessCountByPeriodItem[];\n}) => (\n <ReportContentLayout\n user={data.user}\n platformSlices={buildPlatformSlices(data.accessByPlatform)}\n hoursSlices={buildStudentHoursSlices(data.hoursByItem)}\n accessCountByPeriod={accessCountByPeriod}\n metricBoxes={\n <>\n <MetricBox label=\"Tempo total\" value={data.accessData.totalTime} />\n <MetricBox label=\"Videoaulas\" value={data.accessData.contentTime} />\n <MetricBox\n label=\"Aulas recomendadas\"\n value={data.accessData.recommendedLessonsTime}\n />\n <MetricBox label=\"Simulados\" value={data.accessData.simulationsTime} />\n <MetricBox\n label=\"Quantidade de acessos\"\n value={data.accessData.accessCount}\n />\n <MetricBox\n label=\"Último acesso\"\n value={data.accessData.lastAccess ?? '—'}\n />\n </>\n }\n />\n);\n\nconst ProfessionalModalContent = ({\n data,\n accessCountByPeriod,\n}: {\n data: AccessReportProfessionalData;\n accessCountByPeriod?: AccessCountByPeriodItem[];\n}) => (\n <div className=\"flex flex-col gap-6\">\n <UserHeader\n name={data.user.name}\n school={data.user.school}\n className={data.user.class ?? ''}\n year={data.user.year}\n />\n\n <div className=\"flex flex-col gap-3\">\n <SectionTitle>Dados de acesso</SectionTitle>\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3\">\n <MetricBox label=\"Tempo total\" value={data.accessData.totalTime} />\n <MetricBox label=\"Acessos\" value={data.accessData.accessCount} />\n <MetricBox\n label=\"Último acesso\"\n value={data.accessData.lastAccess ?? '—'}\n />\n </div>\n </div>\n\n {accessCountByPeriod && accessCountByPeriod.length > 0 && (\n <SimpleBarChart\n data={accessCountByPeriod.map((item) => ({\n label: item.label,\n value: item.count,\n }))}\n title=\"Quantidade de acessos por período\"\n barColor=\"bg-info-500\"\n chartHeight={150}\n />\n )}\n\n <div className=\"flex flex-col gap-3\">\n <SectionTitle>Dados de acesso por plataforma</SectionTitle>\n <LegendPieCard slices={buildPlatformSlices(data.accessByPlatform)} />\n </div>\n </div>\n);\n\nconst LoadingSkeleton = ({\n metricCount,\n showSecondaryChart = true,\n}: {\n metricCount: number;\n showSecondaryChart?: boolean;\n}) => (\n <div className=\"flex flex-col gap-4 animate-pulse\">\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3\">\n {Array.from({ length: metricCount }, (_, i) => (\n <div\n key={`skeleton-${String(i)}`}\n className=\"h-16 bg-background-200 rounded-xl\"\n />\n ))}\n </div>\n <div className=\"h-5 bg-background-200 rounded w-40 mt-2\" />\n <div className=\"h-32 bg-background-200 rounded-xl\" />\n {showSecondaryChart && (\n <>\n <div className=\"h-5 bg-background-200 rounded w-32\" />\n <div className=\"h-32 bg-background-200 rounded-xl\" />\n </>\n )}\n </div>\n);\n\n// ─── Main component ───────────────────────────────────────────\n\n/**\n * AccessReportModal component\n *\n * Displays a modal with access data for a user.\n *\n * Two variants driven by the profile type:\n * - `STUDENT` — 6 metric boxes (totalTime, contentTime, recommendedLessonsTime,\n * simulationsTime, accessCount, lastAccess) + platform pie chart + hours-by-item pie chart\n * - `PROFESSIONAL` — 3 metric boxes (totalTime, accessCount, lastAccess) +\n * platform pie chart only (no hours-by-item breakdown for professionals)\n *\n * @example\n * ```tsx\n * <AccessReportModal\n * isOpen={isOpen}\n * onClose={() => setIsOpen(false)}\n * variant={REPORT_MODAL_VARIANT.STUDENT}\n * data={apiResponse.data}\n * />\n * ```\n */\nexport const AccessReportModal = ({\n isOpen,\n onClose,\n title = 'Relatório de acesso',\n loading = false,\n error = null,\n accessCountByPeriod,\n ...variantProps\n}: AccessReportModalProps) => {\n let content: ReactNode;\n\n const isStudent = variantProps.variant === REPORT_MODAL_VARIANT.STUDENT;\n // Student: 6 metrics + 2 charts; Professional: 3 metrics + 1 chart\n const metricCount = isStudent ? 6 : 3;\n const showSecondaryChart = isStudent;\n\n if (loading) {\n content = (\n <LoadingSkeleton\n metricCount={metricCount}\n showSecondaryChart={showSecondaryChart}\n />\n );\n } else if (error) {\n content = <ErrorContent message={error} />;\n } else if (variantProps.data === null) {\n content = <ErrorContent message=\"Nenhum dado disponível.\" />;\n } else if (variantProps.variant === REPORT_MODAL_VARIANT.STUDENT) {\n content = (\n <StudentModalContent\n data={variantProps.data}\n accessCountByPeriod={accessCountByPeriod}\n />\n );\n } else if (variantProps.variant === REPORT_MODAL_VARIANT.PROFESSIONAL) {\n content = (\n <ProfessionalModalContent\n data={variantProps.data}\n accessCountByPeriod={accessCountByPeriod}\n />\n );\n } else {\n content = <ErrorContent message=\"Variante de relatório inválida.\" />;\n }\n\n return (\n <Modal\n isOpen={isOpen}\n onClose={onClose}\n title={title}\n size=\"lg\"\n contentClassName=\"max-h-[80vh] overflow-y-auto\"\n >\n {content}\n </Modal>\n );\n};\n\nexport default AccessReportModal;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAqMI,SA8CE,UA9CF,KAqBA,YArBA;AAvEJ,SAAS,oBAAoB,UAA8C;AAGzE,SAAO;AAAA,IACL;AAAA,MACE,OAAO;AAAA,MACP,OAAO,SAAS,IAAI;AAAA,MACpB,cAAc,SAAS,IAAI;AAAA,MAC3B,YAAY;AAAA,MACZ,OAAO;AAAA,IACT;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,OAAO,SAAS,OAAO;AAAA,MACvB,cAAc,SAAS,OAAO;AAAA,MAC9B,YAAY;AAAA,MACZ,OAAO;AAAA,IACT;AAAA,EACF;AACF;AAEA,SAAS,wBACP,aACY;AAGZ,SAAO;AAAA,IACL;AAAA,MACE,OAAO;AAAA,MACP,OAAO,YAAY,WAAW;AAAA,MAC9B,YAAY;AAAA,IACd;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,OAAO,YAAY,YAAY;AAAA,MAC/B,YAAY;AAAA,IACd;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,OAAO,YAAY,eAAe;AAAA,MAClC,YAAY;AAAA,IACd;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,OAAO,YAAY,QAAQ;AAAA,MAC3B,YAAY;AAAA,IACd;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,OAAO,YAAY,mBAAmB;AAAA,MACtC,YAAY;AAAA,IACd;AAAA,EACF;AACF;AAIA,IAAM,sBAAsB,CAAC;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAOE,qBAAC,SAAI,WAAU,uBACb;AAAA;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,KAAK;AAAA,MACX,QAAQ,KAAK;AAAA,MACb,WAAW,KAAK,SAAS;AAAA,MACzB,MAAM,KAAK;AAAA;AAAA,EACb;AAAA,EAEA,oBAAC,SAAI,WAAU,yCAAyC,uBAAY;AAAA,EAEnE,uBAAuB,oBAAoB,SAAS,KACnD;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,oBAAoB,IAAI,CAAC,UAAU;AAAA,QACvC,OAAO,KAAK;AAAA,QACZ,OAAO,KAAK;AAAA,MACd,EAAE;AAAA,MACF,OAAM;AAAA,MACN,UAAS;AAAA,MACT,aAAa;AAAA;AAAA,EACf;AAAA,EAGF,qBAAC,SAAI,WAAU,uBACb;AAAA,wBAAC,gBAAa,4CAA8B;AAAA,IAC5C,oBAAC,iBAAc,QAAQ,gBAAgB;AAAA,KACzC;AAAA,EAEA,qBAAC,SAAI,WAAU,uBACb;AAAA,wBAAC,gBAAa,qCAAuB;AAAA,IACrC,oBAAC,iBAAc,QAAQ,aAAa;AAAA,KACtC;AAAA,GACF;AAGF,IAAM,sBAAsB,CAAC;AAAA,EAC3B;AAAA,EACA;AACF,MAIE;AAAA,EAAC;AAAA;AAAA,IACC,MAAM,KAAK;AAAA,IACX,gBAAgB,oBAAoB,KAAK,gBAAgB;AAAA,IACzD,aAAa,wBAAwB,KAAK,WAAW;AAAA,IACrD;AAAA,IACA,aACE,iCACE;AAAA,0BAAC,aAAU,OAAM,eAAc,OAAO,KAAK,WAAW,WAAW;AAAA,MACjE,oBAAC,aAAU,OAAM,cAAa,OAAO,KAAK,WAAW,aAAa;AAAA,MAClE;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,OAAO,KAAK,WAAW;AAAA;AAAA,MACzB;AAAA,MACA,oBAAC,aAAU,OAAM,aAAY,OAAO,KAAK,WAAW,iBAAiB;AAAA,MACrE;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,OAAO,KAAK,WAAW;AAAA;AAAA,MACzB;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,OAAO,KAAK,WAAW,cAAc;AAAA;AAAA,MACvC;AAAA,OACF;AAAA;AAEJ;AAGF,IAAM,2BAA2B,CAAC;AAAA,EAChC;AAAA,EACA;AACF,MAIE,qBAAC,SAAI,WAAU,uBACb;AAAA;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,KAAK,KAAK;AAAA,MAChB,QAAQ,KAAK,KAAK;AAAA,MAClB,WAAW,KAAK,KAAK,SAAS;AAAA,MAC9B,MAAM,KAAK,KAAK;AAAA;AAAA,EAClB;AAAA,EAEA,qBAAC,SAAI,WAAU,uBACb;AAAA,wBAAC,gBAAa,6BAAe;AAAA,IAC7B,qBAAC,SAAI,WAAU,yCACb;AAAA,0BAAC,aAAU,OAAM,eAAc,OAAO,KAAK,WAAW,WAAW;AAAA,MACjE,oBAAC,aAAU,OAAM,WAAU,OAAO,KAAK,WAAW,aAAa;AAAA,MAC/D;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,OAAO,KAAK,WAAW,cAAc;AAAA;AAAA,MACvC;AAAA,OACF;AAAA,KACF;AAAA,EAEC,uBAAuB,oBAAoB,SAAS,KACnD;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,oBAAoB,IAAI,CAAC,UAAU;AAAA,QACvC,OAAO,KAAK;AAAA,QACZ,OAAO,KAAK;AAAA,MACd,EAAE;AAAA,MACF,OAAM;AAAA,MACN,UAAS;AAAA,MACT,aAAa;AAAA;AAAA,EACf;AAAA,EAGF,qBAAC,SAAI,WAAU,uBACb;AAAA,wBAAC,gBAAa,4CAA8B;AAAA,IAC5C,oBAAC,iBAAc,QAAQ,oBAAoB,KAAK,gBAAgB,GAAG;AAAA,KACrE;AAAA,GACF;AAGF,IAAM,kBAAkB,CAAC;AAAA,EACvB;AAAA,EACA,qBAAqB;AACvB,MAIE,qBAAC,SAAI,WAAU,qCACb;AAAA,sBAAC,SAAI,WAAU,yCACZ,gBAAM,KAAK,EAAE,QAAQ,YAAY,GAAG,CAAC,GAAG,MACvC;AAAA,IAAC;AAAA;AAAA,MAEC,WAAU;AAAA;AAAA,IADL,YAAY,OAAO,CAAC,CAAC;AAAA,EAE5B,CACD,GACH;AAAA,EACA,oBAAC,SAAI,WAAU,2CAA0C;AAAA,EACzD,oBAAC,SAAI,WAAU,qCAAoC;AAAA,EAClD,sBACC,iCACE;AAAA,wBAAC,SAAI,WAAU,sCAAqC;AAAA,IACpD,oBAAC,SAAI,WAAU,qCAAoC;AAAA,KACrD;AAAA,GAEJ;AA0BK,IAAM,oBAAoB,CAAC;AAAA,EAChC;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,QAAQ;AAAA,EACR;AAAA,EACA,GAAG;AACL,MAA8B;AAC5B,MAAI;AAEJ,QAAM,YAAY,aAAa;AAE/B,QAAM,cAAc,YAAY,IAAI;AACpC,QAAM,qBAAqB;AAE3B,MAAI,SAAS;AACX,cACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA;AAAA,IACF;AAAA,EAEJ,WAAW,OAAO;AAChB,cAAU,oBAAC,gBAAa,SAAS,OAAO;AAAA,EAC1C,WAAW,aAAa,SAAS,MAAM;AACrC,cAAU,oBAAC,gBAAa,SAAQ,8BAA0B;AAAA,EAC5D,WAAW,aAAa,qCAA0C;AAChE,cACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,aAAa;AAAA,QACnB;AAAA;AAAA,IACF;AAAA,EAEJ,WAAW,aAAa,+CAA+C;AACrE,cACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,aAAa;AAAA,QACnB;AAAA;AAAA,IACF;AAAA,EAEJ,OAAO;AACL,cAAU,oBAAC,gBAAa,SAAQ,yCAAkC;AAAA,EACpE;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAK;AAAA,MACL,kBAAiB;AAAA,MAEhB;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,4BAAQ;","names":[]}