Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion src/app/(dashboard)/_cards/patients-by-city.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function DashboardOverviewPatientsByCity(
{isLoading && <Skeleton className='bg-border/75 size-full' />}

{!isLoading && !isEmpty && (
<div className='flex size-full items-center gap-6 2xl:gap-10'>
<div className='flex size-full items-center gap-6 xl:gap-10'>
<PieChart
data={data}
label='cidades'
Expand Down
2 changes: 1 addition & 1 deletion src/app/(dashboard)/_sidebar/container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function DashboardSidebarContainer({
variant='outline'
data-expanded={expanded}
className={cn(
'text-disabled hover:text-foreground-soft absolute top-20 -right-4 size-8 rounded-full transition-all delay-75 duration-300 [&_svg]:size-5',
'text-disabled hover:text-foreground-soft absolute top-20 -right-4 z-50 size-8 rounded-full transition-all delay-75 duration-300 [&_svg]:size-5',
'data-[expanded=true]:rotate-180',
)}
onClick={toogleSidebar}
Expand Down
11 changes: 11 additions & 0 deletions src/app/(dashboard)/encaminhados/_cards/mock-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export function getMockReferralsCount() {
return {
total: 125,
}
}

export function getMockReferredPatients() {
return {
percentage: 71,
}
}
20 changes: 14 additions & 6 deletions src/app/(dashboard)/encaminhados/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import type { Metadata } from 'next'

export const metadata: Metadata = {
title: 'Encaminhados',
}
import { ReferralsByCategoryCard } from '@/modules/referrals/by-category-card'
import { ReferralsByCityCards } from '@/modules/referrals/by-city-cards'
import { ReferralsPeriodTab } from '@/modules/referrals/period-tab'
import { ReferralsSummaryCard } from '@/modules/referrals/summary-card'

export default function Page() {
return <p>Dados Gerais</p>
return (
<div className='grid gap-6 sm:grid-cols-2'>
<ReferralsPeriodTab />

<ReferralsSummaryCard />

<ReferralsByCategoryCard />
<ReferralsByCityCards />
</div>
)
}
4 changes: 2 additions & 2 deletions src/components/ui/tab-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function TabSelect({
}: Readonly<TabSelectProps>) {
return (
<div
className={cn('bg-accent flex h-9 w-fit gap-1 rounded-lg p-1', className)}
className={cn('bg-border/40 flex h-10 gap-1 rounded-lg p-1', className)}
{...props}
>
{buttons.map(({ label, isActive, className, ...buttonProps }) => (
Expand All @@ -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-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,
)}
Expand Down
47 changes: 47 additions & 0 deletions src/modules/referrals/by-category-card.tsx
Original file line number Diff line number Diff line change
@@ -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 ReferralsByCategoryCard(
props: Readonly<React.ComponentProps<'div'>>,
) {
return (
<DashboardCardChart
icon={ChartBarDecreasingIcon}
title='Encaminhamentos por categoria'
{...props}
>
<div className='flex h-full min-h-72 items-center justify-center'>
{!isEmpty && (
<div className='size-full min-h-40'>
<BarChart data={MOCK_DATA} />
</div>
)}

{isEmpty && (
<p className='text-foreground-soft text-sm'>
Nenhum encaminhamento por especialista registrado.
</p>
)}
</div>
</DashboardCardChart>
)
}
75 changes: 75 additions & 0 deletions src/modules/referrals/by-city-cards.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
'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: 'São José do Rio Preto', 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 ReferralsByCityCards(
props: Readonly<React.ComponentProps<'div'>>,
) {
return (
<DashboardCardChart
icon={ChartPieIcon}
title='Localização dos pacientes'
{...props}
>
<div className='flex size-full items-center gap-6 xl:gap-10'>
<PieChart
data={data}
label='cidades'
total={10}
className='size-40 xl:size-48'
/>

<div className='divide-border min-w-0 flex-1 divide-y'>
{data.map((city) => {
return (
<div
key={city.label}
className='text-foreground-soft flex items-center gap-2 py-1 text-sm'
>
<div
className='size-2.5 shrink-0 rounded-full'
style={{ backgroundColor: city.color }}
/>
<span className='flex-1 truncate'>{city.label}</span>
<span className='font-semibold'>{city.value}%</span>
</div>
)
})}
</div>
</div>
</DashboardCardChart>
)
}
42 changes: 42 additions & 0 deletions src/modules/referrals/period-tab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use client'

import { TabSelect } from '@/components/ui/tab-select'
import { usePeriodStore } from '@/store/period'

export function ReferralsPeriodTab() {
const { period, setPeriod } = usePeriodStore()

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',
},
] as const

return (
<TabSelect
buttons={filterOptions.map((option) => ({
label: option.label,
isActive: option.value === period,
onClick: () => setPeriod(option.value),
}))}
className='col-span-full'
/>
)
}
62 changes: 62 additions & 0 deletions src/modules/referrals/summary-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { HeartPulse, Waypoints } from 'lucide-react'

import {
getMockReferralsCount,
getMockReferredPatients,
} from '@/app/(dashboard)/encaminhados/_cards/mock-data'
import { Card } from '@/components/ui/card'

export function ReferralsSummaryCard() {
const referrals = getMockReferralsCount()
const referredPatients = getMockReferredPatients()

const statistics = [
{
value: referrals.total,
label: 'referrals',
},
{
value: `${referredPatients.percentage}%`,
label: 'patients',
},
] as const

const STATISTICS_MAPPING = {
referrals: {
title: (
<>
Total de <strong>encaminhamentos</strong>
</>
),
icon: Waypoints,
},
patients: {
title: (
<>
Total de <strong>pacientes com encaminhamento</strong>
</>
),
icon: HeartPulse,
},
} as const

return statistics.map((statistic) => {
const data = STATISTICS_MAPPING[statistic.label]
const Icon = data.icon

return (
<Card
key={statistic.label}
className='flex min-h-28 flex-col justify-between gap-3 p-6'
>
<div className='flex items-center justify-between'>
<span className='text-4xl font-semibold'>{statistic.value}</span>
<div className='border-border rounded-full border p-2 [&_svg]:size-5'>
<Icon />
</div>
</div>
<p className='text-foreground-soft text-xs uppercase'>{data.title}</p>
</Card>
)
})
}
13 changes: 13 additions & 0 deletions src/store/period.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { create } from 'zustand'

export type Period = 'today' | 'last_week' | 'last_month' | 'last_year'

interface PeriodState {
period: Period
setPeriod: (period: Period) => void
}

export const usePeriodStore = create<PeriodState>((set) => ({
period: 'today',
setPeriod: (period) => set({ period }),
}))