From be754557f4dee8a7c123d8b4bc317c7b77d973c1 Mon Sep 17 00:00:00 2001 From: Gabriel Lemes Date: Wed, 19 Nov 2025 12:40:04 -0300 Subject: [PATCH 1/2] feat(referrals): add referrals dashboard components and state management --- .../encaminhados/_cards/mock-data.ts | 11 +++ .../encaminhados/_cards/patients-location.tsx | 68 +++++++++++++++++++ .../_cards/referrals-by-specialist.tsx | 47 +++++++++++++ .../encaminhados/_cards/referrals-summary.tsx | 66 ++++++++++++++++++ src/app/(dashboard)/encaminhados/page.tsx | 32 +++++++-- .../encaminhados/referrals-tab-buttons.tsx | 44 ++++++++++++ src/components/ui/tab-select.tsx | 4 +- src/store/referrals-filter-store.ts | 13 ++++ 8 files changed, 278 insertions(+), 7 deletions(-) create mode 100644 src/app/(dashboard)/encaminhados/_cards/mock-data.ts create mode 100644 src/app/(dashboard)/encaminhados/_cards/patients-location.tsx create mode 100644 src/app/(dashboard)/encaminhados/_cards/referrals-by-specialist.tsx create mode 100644 src/app/(dashboard)/encaminhados/_cards/referrals-summary.tsx create mode 100644 src/app/(dashboard)/encaminhados/referrals-tab-buttons.tsx create mode 100644 src/store/referrals-filter-store.ts diff --git a/src/app/(dashboard)/encaminhados/_cards/mock-data.ts b/src/app/(dashboard)/encaminhados/_cards/mock-data.ts new file mode 100644 index 00000000..f5fa66a2 --- /dev/null +++ b/src/app/(dashboard)/encaminhados/_cards/mock-data.ts @@ -0,0 +1,11 @@ +export function getMockReferralsCount() { + return { + total: 125, + } +} + +export function getMockReferredPatients() { + return { + percentage: 71, + } +} diff --git a/src/app/(dashboard)/encaminhados/_cards/patients-location.tsx b/src/app/(dashboard)/encaminhados/_cards/patients-location.tsx new file mode 100644 index 00000000..1dc5c329 --- /dev/null +++ b/src/app/(dashboard)/encaminhados/_cards/patients-location.tsx @@ -0,0 +1,68 @@ +'use client' + +import { ChartPieIcon } from 'lucide-react' + +import { PieChart } from '@/components/charts/pie' +import { DashboardCardChart } from '@/components/dashboard/cards/chart' + +const PIE_COLORS = [ + '#E255F2', + '#DF1C41', + '#0F37E0', + '#008B62', + '#F17B2C', + '#F2AE40', + '#3AB795', + '#8C6DFD', +] + +const MOCK_DATA = { + cities: [ + { city: 'São Paulo', percentage: 45 }, + { city: 'Rio de Janeiro', percentage: 25 }, + { city: 'Belo Horizonte', percentage: 15 }, + { city: 'Salvador', percentage: 8 }, + { city: 'Fortaleza', percentage: 5 }, + { city: 'Curitiba', percentage: 5 }, + { city: 'Recife', percentage: 34 }, + { city: 'Porto Alegre', percentage: 18 }, + ], +} + +const data = MOCK_DATA.cities.map((item, index) => ({ + label: item.city, + value: item.percentage, + color: PIE_COLORS[index], +})) + +export function PatientsLocation(props: Readonly>) { + return ( + +
+ + +
+ {data.map((city) => { + return ( +
+
+ {city.label} + {city.value}% +
+ ) + })} +
+
+ + ) +} diff --git a/src/app/(dashboard)/encaminhados/_cards/referrals-by-specialist.tsx b/src/app/(dashboard)/encaminhados/_cards/referrals-by-specialist.tsx new file mode 100644 index 00000000..ceba370b --- /dev/null +++ b/src/app/(dashboard)/encaminhados/_cards/referrals-by-specialist.tsx @@ -0,0 +1,47 @@ +'use client' + +import { ChartBarDecreasingIcon } from 'lucide-react' + +import { BarChart } from '@/components/charts/bar' +import { DashboardCardChart } from '@/components/dashboard/cards/chart' + +const MOCK_DATA = [ + { label: 'Advogado', value: 15 }, + { label: 'Enfermeiro', value: 30 }, + { label: 'Fisioterapeuta', value: 22 }, + { label: 'Médico', value: 45 }, + { label: 'Neurologista', value: 15 }, + { label: 'Nutricionista', value: 18 }, + { label: 'Oftalmologista', value: 11 }, + { label: 'Psicólogo', value: 35 }, + { label: 'Psiquiatra', value: 55 }, + { label: 'Serviço social', value: 8 }, +] + +const isEmpty = MOCK_DATA.length === 0 + +export function ReferralsBySpecialist( + props: Readonly>, +) { + return ( + +
+ {!isEmpty && ( +
+ +
+ )} + + {isEmpty && ( +

+ Nenhum encaminhamento por especialista registrado. +

+ )} +
+
+ ) +} diff --git a/src/app/(dashboard)/encaminhados/_cards/referrals-summary.tsx b/src/app/(dashboard)/encaminhados/_cards/referrals-summary.tsx new file mode 100644 index 00000000..e23fde0d --- /dev/null +++ b/src/app/(dashboard)/encaminhados/_cards/referrals-summary.tsx @@ -0,0 +1,66 @@ +import { HeartPulse, Waypoints } from 'lucide-react' + +import { + getMockReferralsCount, + getMockReferredPatients, +} from '@/app/(dashboard)/encaminhados/_cards/mock-data' +import { Card } from '@/components/ui/card' + +type StatKey = 'referrals' | 'patients' + +export async function ReferralsSummary() { + const referrals = getMockReferralsCount() + const referredPatients = getMockReferredPatients() + + const statistics = [ + { + value: referrals.total, + label: 'referrals', + }, + { + value: `${referredPatients.percentage}%`, + label: 'patients', + }, + ] + + const STATISTICS_MAPPING = { + referrals: { + title: ( + <> + Total de encaminhamentos + + ), + icon: Waypoints, + }, + patients: { + title: ( + <> + Total de pacientes com encaminhamento + + ), + icon: HeartPulse, + }, + } + + return statistics.map((statistic) => { + const label = statistic.label as StatKey + const data = STATISTICS_MAPPING[label] + + return ( + +
+ {statistic.value} +
+ +
+
+

{data.title}

+
+ ) + }) +} diff --git a/src/app/(dashboard)/encaminhados/page.tsx b/src/app/(dashboard)/encaminhados/page.tsx index b85d5387..e341b104 100644 --- a/src/app/(dashboard)/encaminhados/page.tsx +++ b/src/app/(dashboard)/encaminhados/page.tsx @@ -1,9 +1,31 @@ -import type { Metadata } from 'next' +import { Suspense } from 'react' -export const metadata: Metadata = { - title: 'Encaminhados', -} +import { Skeleton } from '@/components/ui/skeleton' + +import { PatientsLocation } from './_cards/patients-location' +import { ReferralsBySpecialist } from './_cards/referrals-by-specialist' +import { ReferralsSummary } from './_cards/referrals-summary' +import { ReferralsTabButtons } from './referrals-tab-buttons' + +export const dynamic = 'force-dynamic' export default function Page() { - return

Dados Gerais

+ return ( +
+
+ +
+ + + } + > + + + + + +
+ ) } diff --git a/src/app/(dashboard)/encaminhados/referrals-tab-buttons.tsx b/src/app/(dashboard)/encaminhados/referrals-tab-buttons.tsx new file mode 100644 index 00000000..edf45cb6 --- /dev/null +++ b/src/app/(dashboard)/encaminhados/referrals-tab-buttons.tsx @@ -0,0 +1,44 @@ +'use client' + +import { TabSelect } from '@/components/ui/tab-select' +import { + type Period, + useReferralsFilterStore, +} from '@/store/referrals-filter-store' + +export function ReferralsTabButtons() { + const { period, setPeriod } = useReferralsFilterStore() + + const filterOptions = [ + { + label: 'Hoje', + value: 'today', + isActive: period === 'today', + }, + { + label: 'Na última semana', + value: 'last_week', + isActive: period === 'last_week', + }, + { + label: 'No último mês', + value: 'last_month', + isActive: period === 'last_month', + }, + { + label: 'No último ano', + value: 'last_year', + isActive: period === 'last_year', + }, + ] + + return ( + ({ + ...option, + onClick: () => setPeriod(option.value as Period), + }))} + className='w-full' + /> + ) +} diff --git a/src/components/ui/tab-select.tsx b/src/components/ui/tab-select.tsx index fa23d0bd..f79eb28d 100644 --- a/src/components/ui/tab-select.tsx +++ b/src/components/ui/tab-select.tsx @@ -20,7 +20,7 @@ export function TabSelect({ }: Readonly) { return (
{buttons.map(({ label, isActive, className, ...buttonProps }) => ( @@ -29,7 +29,7 @@ export function TabSelect({ variant='ghost' data-active={isActive} className={cn( - 'text-disabled hover:bg-background hover:text-foreground h-auto min-h-auto rounded-md text-sm hover:shadow', + 'text-disabled hover:bg-background hover:text-foreground h-auto min-h-auto flex-1 rounded-md text-sm hover:shadow', 'data-[active=true]:bg-background data-[active=true]:text-foreground data-[active=true]:pointer-events-none data-[active=true]:shadow', className, )} diff --git a/src/store/referrals-filter-store.ts b/src/store/referrals-filter-store.ts new file mode 100644 index 00000000..7679bacb --- /dev/null +++ b/src/store/referrals-filter-store.ts @@ -0,0 +1,13 @@ +import { create } from 'zustand' + +export type Period = 'today' | 'last_week' | 'last_month' | 'last_year' + +interface ReferralsFilterState { + period: Period + setPeriod: (period: Period) => void +} + +export const useReferralsFilterStore = create((set) => ({ + period: 'today', + setPeriod: (period) => set({ period }), +})) From e8e60f4c3100c84210d94e130c802684ecb7dcfb Mon Sep 17 00:00:00 2001 From: Juliano Sill Date: Sun, 23 Nov 2025 23:41:07 -0300 Subject: [PATCH 2/2] refactor: restructure referrals screen components and modules - Move referrals cards to dedicated modules/referrals directory - Rename referrals-filter-store.ts to period.ts for better naming - Update referrals page layout and component imports - Modify sidebar container and patients-by-city card styling - Improve tab-select component functionality - Update copilot instructions for better guidance --- .github/copilot-instructions.md | 9 ++++-- .../(dashboard)/_cards/patients-by-city.tsx | 2 +- src/app/(dashboard)/_sidebar/container.tsx | 2 +- src/app/(dashboard)/encaminhados/page.tsx | 32 ++++++------------- src/components/ui/tab-select.tsx | 4 +-- .../referrals/by-category-card.tsx} | 6 ++-- .../referrals/by-city-cards.tsx} | 19 +++++++---- .../referrals/period-tab.tsx} | 18 +++++------ .../referrals/summary-card.tsx} | 20 +++++------- .../{referrals-filter-store.ts => period.ts} | 4 +-- 10 files changed, 53 insertions(+), 63 deletions(-) rename src/{app/(dashboard)/encaminhados/_cards/referrals-by-specialist.tsx => modules/referrals/by-category-card.tsx} (88%) rename src/{app/(dashboard)/encaminhados/_cards/patients-location.tsx => modules/referrals/by-city-cards.tsx} (77%) rename src/{app/(dashboard)/encaminhados/referrals-tab-buttons.tsx => modules/referrals/period-tab.tsx} (67%) rename src/{app/(dashboard)/encaminhados/_cards/referrals-summary.tsx => modules/referrals/summary-card.tsx} (79%) rename src/store/{referrals-filter-store.ts => period.ts} (67%) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index c62967db..bbc6bde4 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -53,6 +53,9 @@ Remember: Clear writing is clear thinking. If you can't write it simply, you don - Always write commits in English. - Follow the conventional commit standard for all commits: eg: `feat(scope): message` -- Always specify the commit type and write the corresponding commit message -- Detect the appropriate commit type and write the corresponding commit message -- Try to summarize the commit message in a single line +- Always check `git status` before committing to review staged files +- Create a main resume line highlighting the **most impactful change**, not implementation details +- Add a detailed description after a blank line with bullet points for supporting changes +- List secondary changes as bullet points, ordered by importance +- If multiple files are changed, only commit the files you explicitly staged (never include unrelated changes) +- Apply the Zinsser brevity principles: remove unnecessary words, use active voice, and write clearly diff --git a/src/app/(dashboard)/_cards/patients-by-city.tsx b/src/app/(dashboard)/_cards/patients-by-city.tsx index a98c4a6b..aec84449 100644 --- a/src/app/(dashboard)/_cards/patients-by-city.tsx +++ b/src/app/(dashboard)/_cards/patients-by-city.tsx @@ -67,7 +67,7 @@ export function DashboardOverviewPatientsByCity( {isLoading && } {!isLoading && !isEmpty && ( -
+
-
- -
+
+ - - } - > - - + - - + +
) } diff --git a/src/components/ui/tab-select.tsx b/src/components/ui/tab-select.tsx index f79eb28d..29513c94 100644 --- a/src/components/ui/tab-select.tsx +++ b/src/components/ui/tab-select.tsx @@ -20,7 +20,7 @@ export function TabSelect({ }: Readonly) { return (
{buttons.map(({ label, isActive, className, ...buttonProps }) => ( @@ -29,7 +29,7 @@ export function TabSelect({ variant='ghost' data-active={isActive} className={cn( - 'text-disabled hover:bg-background hover:text-foreground h-auto min-h-auto flex-1 rounded-md text-sm hover:shadow', + 'text-foreground-soft hover:bg-background hover:text-foreground h-auto min-h-auto flex-1 rounded-md hover:shadow', 'data-[active=true]:bg-background data-[active=true]:text-foreground data-[active=true]:pointer-events-none data-[active=true]:shadow', className, )} diff --git a/src/app/(dashboard)/encaminhados/_cards/referrals-by-specialist.tsx b/src/modules/referrals/by-category-card.tsx similarity index 88% rename from src/app/(dashboard)/encaminhados/_cards/referrals-by-specialist.tsx rename to src/modules/referrals/by-category-card.tsx index ceba370b..20372b2b 100644 --- a/src/app/(dashboard)/encaminhados/_cards/referrals-by-specialist.tsx +++ b/src/modules/referrals/by-category-card.tsx @@ -20,16 +20,16 @@ const MOCK_DATA = [ const isEmpty = MOCK_DATA.length === 0 -export function ReferralsBySpecialist( +export function ReferralsByCategoryCard( props: Readonly>, ) { return ( -
+
{!isEmpty && (
diff --git a/src/app/(dashboard)/encaminhados/_cards/patients-location.tsx b/src/modules/referrals/by-city-cards.tsx similarity index 77% rename from src/app/(dashboard)/encaminhados/_cards/patients-location.tsx rename to src/modules/referrals/by-city-cards.tsx index 1dc5c329..de4cee99 100644 --- a/src/app/(dashboard)/encaminhados/_cards/patients-location.tsx +++ b/src/modules/referrals/by-city-cards.tsx @@ -20,7 +20,7 @@ const MOCK_DATA = { cities: [ { city: 'São Paulo', percentage: 45 }, { city: 'Rio de Janeiro', percentage: 25 }, - { city: 'Belo Horizonte', percentage: 15 }, + { city: 'São José do Rio Preto', percentage: 15 }, { city: 'Salvador', percentage: 8 }, { city: 'Fortaleza', percentage: 5 }, { city: 'Curitiba', percentage: 5 }, @@ -35,22 +35,29 @@ const data = MOCK_DATA.cities.map((item, index) => ({ color: PIE_COLORS[index], })) -export function PatientsLocation(props: Readonly>) { +export function ReferralsByCityCards( + props: Readonly>, +) { return ( -
- +
+ -
+
{data.map((city) => { return (
({ - ...option, - onClick: () => setPeriod(option.value as Period), + label: option.label, + isActive: option.value === period, + onClick: () => setPeriod(option.value), }))} - className='w-full' + className='col-span-full' /> ) } diff --git a/src/app/(dashboard)/encaminhados/_cards/referrals-summary.tsx b/src/modules/referrals/summary-card.tsx similarity index 79% rename from src/app/(dashboard)/encaminhados/_cards/referrals-summary.tsx rename to src/modules/referrals/summary-card.tsx index e23fde0d..d177a1fc 100644 --- a/src/app/(dashboard)/encaminhados/_cards/referrals-summary.tsx +++ b/src/modules/referrals/summary-card.tsx @@ -6,9 +6,7 @@ import { } from '@/app/(dashboard)/encaminhados/_cards/mock-data' import { Card } from '@/components/ui/card' -type StatKey = 'referrals' | 'patients' - -export async function ReferralsSummary() { +export function ReferralsSummaryCard() { const referrals = getMockReferralsCount() const referredPatients = getMockReferredPatients() @@ -21,7 +19,7 @@ export async function ReferralsSummary() { value: `${referredPatients.percentage}%`, label: 'patients', }, - ] + ] as const const STATISTICS_MAPPING = { referrals: { @@ -40,23 +38,21 @@ export async function ReferralsSummary() { ), icon: HeartPulse, }, - } + } as const return statistics.map((statistic) => { - const label = statistic.label as StatKey - const data = STATISTICS_MAPPING[label] + const data = STATISTICS_MAPPING[statistic.label] + const Icon = data.icon return (
{statistic.value} -
- +
+

{data.title}

diff --git a/src/store/referrals-filter-store.ts b/src/store/period.ts similarity index 67% rename from src/store/referrals-filter-store.ts rename to src/store/period.ts index 7679bacb..6d428dfa 100644 --- a/src/store/referrals-filter-store.ts +++ b/src/store/period.ts @@ -2,12 +2,12 @@ import { create } from 'zustand' export type Period = 'today' | 'last_week' | 'last_month' | 'last_year' -interface ReferralsFilterState { +interface PeriodState { period: Period setPeriod: (period: Period) => void } -export const useReferralsFilterStore = create((set) => ({ +export const usePeriodStore = create((set) => ({ period: 'today', setPeriod: (period) => set({ period }), }))