diff --git a/app/home/[account]/_components/dashboard-demo-charts.tsx b/app/home/[account]/_components/dashboard-demo-charts.tsx deleted file mode 100644 index 436853b..0000000 --- a/app/home/[account]/_components/dashboard-demo-charts.tsx +++ /dev/null @@ -1,896 +0,0 @@ -'use client'; - -import { useMemo, useState } from 'react'; - -import { ArrowDown, ArrowUp, Menu, TrendingUp } from 'lucide-react'; -import { - Area, - AreaChart, - Bar, - BarChart, - CartesianGrid, - Line, - LineChart, - XAxis, -} from 'recharts'; - -import { Badge } from '@kit/ui/badge'; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from '@kit/ui/card'; -import { - ChartConfig, - ChartContainer, - ChartTooltip, - ChartTooltipContent, -} from '@kit/ui/chart'; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from '@kit/ui/table'; - -export default function DashboardDemo() { - const mrr = useMemo(() => generateDemoData(), []); - const netRevenue = useMemo(() => generateDemoData(), []); - const fees = useMemo(() => generateDemoData(), []); - const newCustomers = useMemo(() => generateDemoData(), []); - - return ( -
-
- - - - MRR - 20% - - - - Monthly recurring revenue - - -
-
{`$${mrr[1]}`}
-
-
- - - - -
- - - - - Revenue - 12% - - - - Total revenue including fees - - -
-
{`$${netRevenue[1]}`}
-
-
- - - - -
- - - - - Fees - 9% - - - - Total fees collected - - -
-
{`$${fees[1]}`}
-
-
- - - - -
- - - - - New Customers - -25% - - - - Customers who signed up this month - - -
-
{`${Number(newCustomers[1]).toFixed(0)}`}
-
-
- - - - -
-
- - - - - -
- - - Best Customers - Showing the top customers by MRR - - - - - - -
-
- ); -} - -function generateDemoData() { - const today = new Date(); - const formatter = new Intl.DateTimeFormat('en-us', { - month: 'long', - year: '2-digit', - }); - - const data: { value: string; name: string }[] = []; - - for (let n = 8; n > 0; n -= 1) { - const date = new Date(today.getFullYear(), today.getMonth() - n, 1); - - data.push({ - name: formatter.format(date), - value: (Math.random() * 10).toFixed(1), - }); - } - - const lastValue = data[data.length - 1]?.value; - - return [data, lastValue] as [typeof data, string]; -} - -function Chart( - props: React.PropsWithChildren<{ data: { value: string; name: string }[] }>, -) { - const chartConfig = { - desktop: { - label: 'Desktop', - color: 'var(--chart-1)', - }, - mobile: { - label: 'Mobile', - color: 'var(--chart-2)', - }, - } satisfies ChartConfig; - - return ( - - - - - } - /> - - - - ); -} - -function CustomersTable() { - const customers = [ - { - name: 'John Doe', - email: 'john@makerkit.dev', - plan: 'Pro', - mrr: '$120.5', - logins: 1020, - status: 'Healthy', - trend: 'up', - }, - { - name: 'Emma Smith', - email: 'emma@makerit.dev', - plan: 'Basic', - mrr: '$65.4', - logins: 570, - status: 'Possible Churn', - trend: 'stale', - }, - { - name: 'Robert Johnson', - email: 'robert@makerkit.dev', - plan: 'Pro', - mrr: '$500.1', - logins: 2050, - status: 'Healthy', - trend: 'up', - }, - { - name: 'Olivia Brown', - email: 'olivia@makerkit.dev', - plan: 'Basic', - mrr: '$10', - logins: 50, - status: 'Churn', - trend: 'down', - }, - { - name: 'Michael Davis', - email: 'michael@makerkit.dev', - plan: 'Pro', - mrr: '$300.2', - logins: 1520, - status: 'Healthy', - trend: 'up', - }, - { - name: 'Emily Jones', - email: 'emily@makerkit.dev', - plan: 'Pro', - mrr: '$75.7', - logins: 780, - status: 'Healthy', - trend: 'up', - }, - { - name: 'Daniel Garcia', - email: 'daniel@makerkit.dev', - plan: 'Basic', - mrr: '$50', - logins: 320, - status: 'Possible Churn', - trend: 'stale', - }, - { - name: 'Liam Miller', - email: 'liam@makerkit.dev', - plan: 'Pro', - mrr: '$90.8', - logins: 1260, - status: 'Healthy', - trend: 'up', - }, - { - name: 'Emma Clark', - email: 'emma@makerkit.dev', - plan: 'Basic', - mrr: '$0', - logins: 20, - status: 'Churn', - trend: 'down', - }, - { - name: 'Elizabeth Rodriguez', - email: 'liz@makerkit.dev', - plan: 'Pro', - mrr: '$145.3', - logins: 1380, - status: 'Healthy', - trend: 'up', - }, - { - name: 'James Martinez', - email: 'james@makerkit.dev', - plan: 'Pro', - mrr: '$120.5', - logins: 940, - status: 'Healthy', - trend: 'up', - }, - { - name: 'Charlotte Ryan', - email: 'carlotte@makerkit.dev', - plan: 'Basic', - mrr: '$80.6', - logins: 460, - status: 'Possible Churn', - trend: 'stale', - }, - { - name: 'Lucas Evans', - email: 'lucas@makerkit.dev', - plan: 'Pro', - mrr: '$210.3', - logins: 1850, - status: 'Healthy', - trend: 'up', - }, - { - name: 'Sophia Wilson', - email: 'sophia@makerkit.dev', - plan: 'Basic', - mrr: '$10', - logins: 35, - status: 'Churn', - trend: 'down', - }, - { - name: 'William Kelly', - email: 'will@makerkit.dev', - plan: 'Pro', - mrr: '$350.2', - logins: 1760, - status: 'Healthy', - trend: 'up', - }, - { - name: 'Oliver Thomas', - email: 'olly@makerkit.dev', - plan: 'Pro', - mrr: '$145.6', - logins: 1350, - status: 'Healthy', - trend: 'up', - }, - { - name: 'Samantha White', - email: 'sam@makerkit.dev', - plan: 'Basic', - mrr: '$60.3', - logins: 425, - status: 'Possible Churn', - trend: 'stale', - }, - { - name: 'Benjamin Lewis', - email: 'ben@makerkit.dev', - plan: 'Pro', - mrr: '$175.8', - logins: 1600, - status: 'Healthy', - trend: 'up', - }, - { - name: 'Zoe Harris', - email: 'zoe@makerkit.dev', - plan: 'Basic', - mrr: '$0', - logins: 18, - status: 'Churn', - trend: 'down', - }, - { - name: 'Zachary Nelson', - email: 'zac@makerkit.dev', - plan: 'Pro', - mrr: '$255.9', - logins: 1785, - status: 'Healthy', - trend: 'up', - }, - ]; - - return ( - - - - Customer - Plan - MRR - Logins - Status - - - - {customers.map((customer) => ( - - - {customer.name} - - {customer.email} - - - {customer.plan} - {customer.mrr} - {customer.logins} - - - {customer.status} - - - - ))} - -
- ); -} - -function BadgeWithTrend(props: React.PropsWithChildren<{ trend: string }>) { - const className = useMemo(() => { - switch (props.trend) { - case 'up': - return 'text-green-500'; - case 'down': - return 'text-destructive'; - case 'stale': - return 'text-orange-500'; - } - }, [props.trend]); - - return ( - - {props.children} - - ); -} - -function Figure(props: React.PropsWithChildren) { - return ( -
- {props.children} -
- ); -} - -function Trend( - props: React.PropsWithChildren<{ - trend: 'up' | 'down' | 'stale'; - }>, -) { - const Icon = useMemo(() => { - switch (props.trend) { - case 'up': - return ; - case 'down': - return ; - case 'stale': - return ; - } - }, [props.trend]); - - return ( -
- - - {Icon} - {props.children} - - -
- ); -} - -export function VisitorsChart() { - const chartData = useMemo( - () => [ - { date: '2024-04-01', desktop: 222, mobile: 150 }, - { date: '2024-04-02', desktop: 97, mobile: 180 }, - { date: '2024-04-03', desktop: 167, mobile: 120 }, - { date: '2024-04-04', desktop: 242, mobile: 260 }, - { date: '2024-04-05', desktop: 373, mobile: 290 }, - { date: '2024-04-06', desktop: 301, mobile: 340 }, - { date: '2024-04-07', desktop: 245, mobile: 180 }, - { date: '2024-04-08', desktop: 409, mobile: 320 }, - { date: '2024-04-09', desktop: 59, mobile: 110 }, - { date: '2024-04-10', desktop: 261, mobile: 190 }, - { date: '2024-04-11', desktop: 327, mobile: 350 }, - { date: '2024-04-12', desktop: 292, mobile: 210 }, - { date: '2024-04-13', desktop: 342, mobile: 380 }, - { date: '2024-04-14', desktop: 137, mobile: 220 }, - { date: '2024-04-15', desktop: 120, mobile: 170 }, - { date: '2024-04-16', desktop: 138, mobile: 190 }, - { date: '2024-04-17', desktop: 446, mobile: 360 }, - { date: '2024-04-18', desktop: 364, mobile: 410 }, - { date: '2024-04-19', desktop: 243, mobile: 180 }, - { date: '2024-04-20', desktop: 89, mobile: 150 }, - { date: '2024-04-21', desktop: 137, mobile: 200 }, - { date: '2024-04-22', desktop: 224, mobile: 170 }, - { date: '2024-04-23', desktop: 138, mobile: 230 }, - { date: '2024-04-24', desktop: 387, mobile: 290 }, - { date: '2024-04-25', desktop: 215, mobile: 250 }, - { date: '2024-04-26', desktop: 75, mobile: 130 }, - { date: '2024-04-27', desktop: 383, mobile: 420 }, - { date: '2024-04-28', desktop: 122, mobile: 180 }, - { date: '2024-04-29', desktop: 315, mobile: 240 }, - { date: '2024-04-30', desktop: 454, mobile: 380 }, - { date: '2024-05-01', desktop: 165, mobile: 220 }, - { date: '2024-05-02', desktop: 293, mobile: 310 }, - { date: '2024-05-03', desktop: 247, mobile: 190 }, - { date: '2024-05-04', desktop: 385, mobile: 420 }, - { date: '2024-05-05', desktop: 481, mobile: 390 }, - { date: '2024-05-06', desktop: 498, mobile: 520 }, - { date: '2024-05-07', desktop: 388, mobile: 300 }, - { date: '2024-05-08', desktop: 149, mobile: 210 }, - { date: '2024-05-09', desktop: 227, mobile: 180 }, - { date: '2024-05-10', desktop: 293, mobile: 330 }, - { date: '2024-05-11', desktop: 335, mobile: 270 }, - { date: '2024-05-12', desktop: 197, mobile: 240 }, - { date: '2024-05-13', desktop: 197, mobile: 160 }, - { date: '2024-05-14', desktop: 448, mobile: 490 }, - { date: '2024-05-15', desktop: 473, mobile: 380 }, - { date: '2024-05-16', desktop: 338, mobile: 400 }, - { date: '2024-05-17', desktop: 499, mobile: 420 }, - { date: '2024-05-18', desktop: 315, mobile: 350 }, - { date: '2024-05-19', desktop: 235, mobile: 180 }, - { date: '2024-05-20', desktop: 177, mobile: 230 }, - { date: '2024-05-21', desktop: 82, mobile: 140 }, - { date: '2024-05-22', desktop: 81, mobile: 120 }, - { date: '2024-05-23', desktop: 252, mobile: 290 }, - { date: '2024-05-24', desktop: 294, mobile: 220 }, - { date: '2024-05-25', desktop: 201, mobile: 250 }, - { date: '2024-05-26', desktop: 213, mobile: 170 }, - { date: '2024-05-27', desktop: 420, mobile: 460 }, - { date: '2024-05-28', desktop: 233, mobile: 190 }, - { date: '2024-05-29', desktop: 78, mobile: 130 }, - { date: '2024-05-30', desktop: 340, mobile: 280 }, - { date: '2024-05-31', desktop: 178, mobile: 230 }, - { date: '2024-06-01', desktop: 178, mobile: 200 }, - { date: '2024-06-02', desktop: 470, mobile: 410 }, - { date: '2024-06-03', desktop: 103, mobile: 160 }, - { date: '2024-06-04', desktop: 439, mobile: 380 }, - { date: '2024-06-05', desktop: 88, mobile: 140 }, - { date: '2024-06-06', desktop: 294, mobile: 250 }, - { date: '2024-06-07', desktop: 323, mobile: 370 }, - { date: '2024-06-08', desktop: 385, mobile: 320 }, - { date: '2024-06-09', desktop: 438, mobile: 480 }, - { date: '2024-06-10', desktop: 155, mobile: 200 }, - { date: '2024-06-11', desktop: 92, mobile: 150 }, - { date: '2024-06-12', desktop: 492, mobile: 420 }, - { date: '2024-06-13', desktop: 81, mobile: 130 }, - { date: '2024-06-14', desktop: 426, mobile: 380 }, - { date: '2024-06-15', desktop: 307, mobile: 350 }, - { date: '2024-06-16', desktop: 371, mobile: 310 }, - { date: '2024-06-17', desktop: 475, mobile: 520 }, - { date: '2024-06-18', desktop: 107, mobile: 170 }, - { date: '2024-06-19', desktop: 341, mobile: 290 }, - { date: '2024-06-20', desktop: 408, mobile: 450 }, - { date: '2024-06-21', desktop: 169, mobile: 210 }, - { date: '2024-06-22', desktop: 317, mobile: 270 }, - { date: '2024-06-23', desktop: 480, mobile: 530 }, - { date: '2024-06-24', desktop: 132, mobile: 180 }, - { date: '2024-06-25', desktop: 141, mobile: 190 }, - { date: '2024-06-26', desktop: 434, mobile: 380 }, - { date: '2024-06-27', desktop: 448, mobile: 490 }, - { date: '2024-06-28', desktop: 149, mobile: 200 }, - { date: '2024-06-29', desktop: 103, mobile: 160 }, - { date: '2024-06-30', desktop: 446, mobile: 400 }, - ], - [], - ); - - const chartConfig = { - visitors: { - label: 'Visitors', - }, - desktop: { - label: 'Desktop', - color: 'var(--chart-1)', - }, - mobile: { - label: 'Mobile', - color: 'var(--chart-2)', - }, - } satisfies ChartConfig; - - return ( - - - Visitors - - Showing total visitors for the last 6 months - - - - - - - - - - - - - - - - - - value.slice(0, 3)} - /> - } - /> - - - - - - - -
-
-
- Trending up by 5.2% this month -
-
- January - June 2024 -
-
-
-
-
- ); -} - -export function PageViewsChart() { - const [activeChart, setActiveChart] = - useState('desktop'); - - const chartData = [ - { date: '2024-04-01', desktop: 222, mobile: 150 }, - { date: '2024-04-02', desktop: 97, mobile: 180 }, - { date: '2024-04-03', desktop: 167, mobile: 120 }, - { date: '2024-04-04', desktop: 242, mobile: 260 }, - { date: '2024-04-05', desktop: 373, mobile: 290 }, - { date: '2024-04-06', desktop: 301, mobile: 340 }, - { date: '2024-04-07', desktop: 245, mobile: 180 }, - { date: '2024-04-08', desktop: 409, mobile: 320 }, - { date: '2024-04-09', desktop: 59, mobile: 110 }, - { date: '2024-04-10', desktop: 261, mobile: 190 }, - { date: '2024-04-11', desktop: 327, mobile: 350 }, - { date: '2024-04-12', desktop: 292, mobile: 210 }, - { date: '2024-04-13', desktop: 342, mobile: 380 }, - { date: '2024-04-14', desktop: 137, mobile: 220 }, - { date: '2024-04-15', desktop: 120, mobile: 170 }, - { date: '2024-04-16', desktop: 138, mobile: 190 }, - { date: '2024-04-17', desktop: 446, mobile: 360 }, - { date: '2024-04-18', desktop: 364, mobile: 410 }, - { date: '2024-04-19', desktop: 243, mobile: 180 }, - { date: '2024-04-20', desktop: 89, mobile: 150 }, - { date: '2024-04-21', desktop: 137, mobile: 200 }, - { date: '2024-04-22', desktop: 224, mobile: 170 }, - { date: '2024-04-23', desktop: 138, mobile: 230 }, - { date: '2024-04-24', desktop: 387, mobile: 290 }, - { date: '2024-04-25', desktop: 215, mobile: 250 }, - { date: '2024-04-26', desktop: 75, mobile: 130 }, - { date: '2024-04-27', desktop: 383, mobile: 420 }, - { date: '2024-04-28', desktop: 122, mobile: 180 }, - { date: '2024-04-29', desktop: 315, mobile: 240 }, - { date: '2024-04-30', desktop: 454, mobile: 380 }, - { date: '2024-05-01', desktop: 165, mobile: 220 }, - { date: '2024-05-02', desktop: 293, mobile: 310 }, - { date: '2024-05-03', desktop: 247, mobile: 190 }, - { date: '2024-05-04', desktop: 385, mobile: 420 }, - { date: '2024-05-05', desktop: 481, mobile: 390 }, - { date: '2024-05-06', desktop: 498, mobile: 520 }, - { date: '2024-05-07', desktop: 388, mobile: 300 }, - { date: '2024-05-08', desktop: 149, mobile: 210 }, - { date: '2024-05-09', desktop: 227, mobile: 180 }, - { date: '2024-05-10', desktop: 293, mobile: 330 }, - { date: '2024-05-11', desktop: 335, mobile: 270 }, - { date: '2024-05-12', desktop: 197, mobile: 240 }, - { date: '2024-05-13', desktop: 197, mobile: 160 }, - { date: '2024-05-14', desktop: 448, mobile: 490 }, - { date: '2024-05-15', desktop: 473, mobile: 380 }, - { date: '2024-05-16', desktop: 338, mobile: 400 }, - { date: '2024-05-17', desktop: 499, mobile: 420 }, - { date: '2024-05-18', desktop: 315, mobile: 350 }, - { date: '2024-05-19', desktop: 235, mobile: 180 }, - { date: '2024-05-20', desktop: 177, mobile: 230 }, - { date: '2024-05-21', desktop: 82, mobile: 140 }, - { date: '2024-05-22', desktop: 81, mobile: 120 }, - { date: '2024-05-23', desktop: 252, mobile: 290 }, - { date: '2024-05-24', desktop: 294, mobile: 220 }, - { date: '2024-05-25', desktop: 201, mobile: 250 }, - { date: '2024-05-26', desktop: 213, mobile: 170 }, - { date: '2024-05-27', desktop: 420, mobile: 460 }, - { date: '2024-05-28', desktop: 233, mobile: 190 }, - { date: '2024-05-29', desktop: 78, mobile: 130 }, - { date: '2024-05-30', desktop: 340, mobile: 280 }, - { date: '2024-05-31', desktop: 178, mobile: 230 }, - { date: '2024-06-01', desktop: 178, mobile: 200 }, - { date: '2024-06-02', desktop: 470, mobile: 410 }, - { date: '2024-06-03', desktop: 103, mobile: 160 }, - { date: '2024-06-04', desktop: 439, mobile: 380 }, - { date: '2024-06-05', desktop: 88, mobile: 140 }, - { date: '2024-06-06', desktop: 294, mobile: 250 }, - { date: '2024-06-07', desktop: 323, mobile: 370 }, - { date: '2024-06-08', desktop: 385, mobile: 320 }, - { date: '2024-06-09', desktop: 438, mobile: 480 }, - { date: '2024-06-10', desktop: 155, mobile: 200 }, - { date: '2024-06-11', desktop: 92, mobile: 150 }, - { date: '2024-06-12', desktop: 492, mobile: 420 }, - { date: '2024-06-13', desktop: 81, mobile: 130 }, - { date: '2024-06-14', desktop: 426, mobile: 380 }, - { date: '2024-06-15', desktop: 307, mobile: 350 }, - { date: '2024-06-16', desktop: 371, mobile: 310 }, - { date: '2024-06-17', desktop: 475, mobile: 520 }, - { date: '2024-06-18', desktop: 107, mobile: 170 }, - { date: '2024-06-19', desktop: 341, mobile: 290 }, - { date: '2024-06-20', desktop: 408, mobile: 450 }, - { date: '2024-06-21', desktop: 169, mobile: 210 }, - { date: '2024-06-22', desktop: 317, mobile: 270 }, - { date: '2024-06-23', desktop: 480, mobile: 530 }, - { date: '2024-06-24', desktop: 132, mobile: 180 }, - { date: '2024-06-25', desktop: 141, mobile: 190 }, - { date: '2024-06-26', desktop: 434, mobile: 380 }, - { date: '2024-06-27', desktop: 448, mobile: 490 }, - { date: '2024-06-28', desktop: 149, mobile: 200 }, - { date: '2024-06-29', desktop: 103, mobile: 160 }, - { date: '2024-06-30', desktop: 446, mobile: 400 }, - ]; - - const chartConfig = { - views: { - label: 'Page Views', - }, - desktop: { - label: 'Desktop', - color: 'var(--chart-1)', - }, - mobile: { - label: 'Mobile', - color: 'var(--chart-2)', - }, - } satisfies ChartConfig; - - const total = useMemo( - () => ({ - desktop: chartData.reduce((acc, curr) => acc + curr.desktop, 0), - mobile: chartData.reduce((acc, curr) => acc + curr.mobile, 0), - }), - [], - ); - - return ( - - -
- Page Views - - - Showing total visitors for the last 3 months - -
- -
- {['desktop', 'mobile'].map((key) => { - const chart = key as keyof typeof chartConfig; - return ( - - ); - })} -
-
- - - - - - { - const date = new Date(value); - return date.toLocaleDateString('en-US', { - month: 'short', - day: 'numeric', - }); - }} - /> - { - return new Date(value).toLocaleDateString('en-US', { - month: 'short', - day: 'numeric', - year: 'numeric', - }); - }} - /> - } - /> - - - - -
- ); -} diff --git a/app/home/[account]/_components/dashboard-demo.tsx b/app/home/[account]/_components/dashboard.tsx similarity index 77% rename from app/home/[account]/_components/dashboard-demo.tsx rename to app/home/[account]/_components/dashboard.tsx index 63528f4..67208ba 100644 --- a/app/home/[account]/_components/dashboard-demo.tsx +++ b/app/home/[account]/_components/dashboard.tsx @@ -4,7 +4,7 @@ import dynamic from 'next/dynamic'; import { LoadingOverlay } from '@kit/ui/loading-overlay'; -export const DashboardDemo = dynamic(() => import('./dashboard-demo-charts'), { +export const Dashboard = dynamic(() => import('./team-account-statistics'), { ssr: false, loading: () => ( { + return {children}; +}; + +const StatisticsCardTitle = ({ children }: { children: React.ReactNode }) => { + return {children}; +}; + +const StatisticsDescription = ({ children }: { children: React.ReactNode }) => { + return

{children}

; +}; + +const StatisticsValue = ({ children }: { children: React.ReactNode }) => { + return

{children}

; +}; + +const TeamAccountBenefitStatistics = ({ + accountSlug, +}: TeamAccountBenefitStatisticsProps) => { + const { + i18n: { language }, + } = useTranslation(); + + return ( +
+ +
+ +
+ +
+

+ +

+

+ +

+ + + +
+
+ +
+ + + + + 18 % + + + + + + + + + 22 % + + + + + + + + + 20 % + + + + + + E-konsultatsioon + 17 % + + + + + + + + +
+
+ 23 % + + + +
+
+ 1800 € + + + +
+
+
+
+
+ ); +}; + +export default TeamAccountBenefitStatistics; diff --git a/app/home/[account]/_components/team-account-health-details.tsx b/app/home/[account]/_components/team-account-health-details.tsx new file mode 100644 index 0000000..904a2bc --- /dev/null +++ b/app/home/[account]/_components/team-account-health-details.tsx @@ -0,0 +1,43 @@ +import React from 'react'; + +import { Card } from '@kit/ui/card'; +import { Trans } from '@kit/ui/trans'; +import { cn } from '@kit/ui/utils'; + +import { + NormStatus, + getAccountHealthDetailsFields, +} from '../_lib/server/load-team-account-health-details'; +import { TeamAccountStatisticsProps } from './team-account-statistics'; + +const TeamAccountHealthDetails = ({ + memberParams, +}: { + memberParams: TeamAccountStatisticsProps['memberParams']; +}) => { + const accountHealthDetailsFields = + getAccountHealthDetailsFields(memberParams); + return ( +
+ {accountHealthDetailsFields.map(({ title, Icon, value, normStatus }) => ( + +
+ +
+
+ +
+ {value} +
+ ))} +
+ ); +}; + +export default TeamAccountHealthDetails; diff --git a/app/home/[account]/_components/team-account-layout-page-header.tsx b/app/home/[account]/_components/team-account-layout-page-header.tsx index cecd62b..7aac363 100644 --- a/app/home/[account]/_components/team-account-layout-page-header.tsx +++ b/app/home/[account]/_components/team-account-layout-page-header.tsx @@ -2,12 +2,13 @@ import { PageHeader } from '@kit/ui/page'; export function TeamAccountLayoutPageHeader( props: React.PropsWithChildren<{ - title: string | React.ReactNode; - description: string | React.ReactNode; - account: string; + title?: string | React.ReactNode; + description?: string | React.ReactNode; }>, ) { return ( - {props.children} + + {props.children} + ); } diff --git a/app/home/[account]/_components/team-account-layout-sidebar.tsx b/app/home/[account]/_components/team-account-layout-sidebar.tsx index 398fb9a..4208b62 100644 --- a/app/home/[account]/_components/team-account-layout-sidebar.tsx +++ b/app/home/[account]/_components/team-account-layout-sidebar.tsx @@ -52,15 +52,15 @@ function SidebarContainer(props: { return ( - -
+ +
-
+
[]; +} + +export default function TeamAccountStatistics({ + teamAccount, + memberParams, +}: TeamAccountStatisticsProps) { + return ( +
+ + +
+ +
+
+ + +
+ + redirect( + createPath( + pathsConfig.app.accountMembers, + teamAccount.slug || '', + ), + ) + } + > +
+
+ +
+
+ +
+ + + +

+ +

+
+
+ + + redirect( + createPath( + pathsConfig.app.accountBilling, + teamAccount.slug || '', + ), + ) + } + > +
+ +
+
+ +
+ + + +

+ +

+
+
+
+
+ ); +} diff --git a/app/home/[account]/_lib/server/load-team-account-health-details.ts b/app/home/[account]/_lib/server/load-team-account-health-details.ts new file mode 100644 index 0000000..6aeaa3c --- /dev/null +++ b/app/home/[account]/_lib/server/load-team-account-health-details.ts @@ -0,0 +1,79 @@ +import React from 'react'; + +import { Clock, TrendingUp, User } from 'lucide-react'; + +import { TeamAccountStatisticsProps } from '../../_components/team-account-statistics'; + +interface AccountHealthDetailsField { + title: string; + value: string | number; + Icon: React.ComponentType<{ + size?: number; + color?: string; + className?: string; + }>; + normStatus: NormStatus; +} + +export enum NormStatus { + CRITICAL = 'CRITICAL', + WARNING = 'WARNING', + NORMAL = 'NORMAL', +} + +export const getAccountHealthDetailsFields = ( + memberParams: TeamAccountStatisticsProps['memberParams'], +): AccountHealthDetailsField[] => { + const averageBMI = ( + memberParams.reduce((sum, { height, weight }) => { + const hMeters = height! / 100; + const bmi = weight! / (hMeters * hMeters); + return sum + bmi; + }, 0) / memberParams.length + ).toFixed(0); + + return [ + { + title: 'teams:healthDetails.women', + value: `50% (${memberParams.length})`, + Icon: User, + normStatus: NormStatus.NORMAL, + }, + { + title: 'teams:healthDetails.men', + value: `50% (${memberParams.length})`, + Icon: User, + normStatus: NormStatus.NORMAL, + }, + { + title: 'teams:healthDetails.avgAge', + value: '56', + Icon: Clock, + normStatus: NormStatus.NORMAL, + }, + { + title: 'teams:healthDetails.bmi', + value: averageBMI, + Icon: TrendingUp, + normStatus: NormStatus.WARNING, + }, + { + title: 'teams:healthDetails.cholesterol', + value: '6.1', + Icon: TrendingUp, + normStatus: NormStatus.WARNING, + }, + { + title: 'teams:healthDetails.vitaminD', + value: '76', + Icon: TrendingUp, + normStatus: NormStatus.NORMAL, + }, + { + title: 'teams:healthDetails.smokers', + value: '22%', + Icon: TrendingUp, + normStatus: NormStatus.CRITICAL, + }, + ]; +}; diff --git a/app/home/[account]/billing/page.tsx b/app/home/[account]/billing/page.tsx index a56b07a..9d2da4c 100644 --- a/app/home/[account]/billing/page.tsx +++ b/app/home/[account]/billing/page.tsx @@ -29,14 +29,14 @@ async function TeamAccountBillingPage({ params }: TeamAccountBillingPageProps) { const account = await api.getTeamAccount(accountSlug); const companyParams = await api.getTeamAccountParams(account.id); - const accounts = await api.getMembers(accountSlug); + const { members } = await api.getMembers(accountSlug); return ( ); diff --git a/app/home/[account]/page.tsx b/app/home/[account]/page.tsx index ad389bf..46a0c2b 100644 --- a/app/home/[account]/page.tsx +++ b/app/home/[account]/page.tsx @@ -1,15 +1,18 @@ +'use server'; + import { use } from 'react'; import { CompanyGuard } from '@/packages/features/team-accounts/src/components'; +import { createTeamAccountsApi } from '@/packages/features/team-accounts/src/server/api'; +import { getSupabaseServerClient } from '@/packages/supabase/src/clients/server-client'; -import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs'; import { PageBody } from '@kit/ui/page'; import { Trans } from '@kit/ui/trans'; import { createI18nServerInstance } from '~/lib/i18n/i18n.server'; import { withI18n } from '~/lib/i18n/with-i18n'; -import { DashboardDemo } from './_components/dashboard-demo'; +import { Dashboard } from './_components/dashboard'; import { TeamAccountLayoutPageHeader } from './_components/team-account-layout-page-header'; interface TeamAccountHomePageProps { @@ -27,17 +30,24 @@ export const generateMetadata = async () => { function TeamAccountHomePage({ params }: TeamAccountHomePageProps) { const account = use(params).account; - console.log('TeamAccountHomePage account', account); + const client = getSupabaseServerClient(); + const api = createTeamAccountsApi(client); + const teamAccount = use(api.getTeamAccount(account)); + const { memberParams } = use(api.getMembers(account)); + return ( <> } - description={} + title={ + + } /> - + ); diff --git a/config/team-account-navigation.config.tsx b/config/team-account-navigation.config.tsx index 1f5bdba..b3a7c6b 100644 --- a/config/team-account-navigation.config.tsx +++ b/config/team-account-navigation.config.tsx @@ -53,6 +53,6 @@ export function getTeamAccountSidebarConfig(account: string) { }); } -function createPath(path: string, account: string) { +export function createPath(path: string, account: string) { return path.replace('[account]', account); } diff --git a/lib/services/account.service.ts b/lib/services/account.service.ts index 29eb6cb..2bd7350 100644 --- a/lib/services/account.service.ts +++ b/lib/services/account.service.ts @@ -1,11 +1,12 @@ -import { getSupabaseServerClient } from "@kit/supabase/server-client"; -import { getSupabaseServerAdminClient } from "@kit/supabase/server-admin-client"; -import type { Tables } from "@/packages/supabase/src/database.types"; +import type { Tables } from '@/packages/supabase/src/database.types'; + +import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client'; +import { getSupabaseServerClient } from '@kit/supabase/server-client'; type Account = Tables<{ schema: 'medreport' }, 'accounts'>; type Membership = Tables<{ schema: 'medreport' }, 'accounts_memberships'>; -export type AccountWithMemberships = Account & { memberships: Membership[] } +export type AccountWithMemberships = Account & { memberships: Membership[] }; export async function getAccount(id: string): Promise { const { data } = await getSupabaseServerClient() @@ -28,6 +29,7 @@ export async function getAccountAdmin({ .schema('medreport') .from('accounts') .select('*, memberships: accounts_memberships (*)') + .eq('is_personal_account', true); if (primaryOwnerUserId) { query.eq('primary_owner_user_id', primaryOwnerUserId); diff --git a/packages/features/admin/src/components/admin-account-page.tsx b/packages/features/admin/src/components/admin-account-page.tsx index 1acb6a7..7fe9df9 100644 --- a/packages/features/admin/src/components/admin-account-page.tsx +++ b/packages/features/admin/src/components/admin-account-page.tsx @@ -164,7 +164,7 @@ async function TeamAccountPage(props: { account: Account & { memberships: Membership[] }; }) { const api = createTeamAccountsApi(getSupabaseServerClient()); - const members = await api.getMembers(props.account.slug ?? ''); + const { members } = await api.getMembers(props.account.slug ?? ''); return ( <> diff --git a/packages/features/team-accounts/src/server/api.ts b/packages/features/team-accounts/src/server/api.ts index a57b68d..44b0379 100644 --- a/packages/features/team-accounts/src/server/api.ts +++ b/packages/features/team-accounts/src/server/api.ts @@ -308,7 +308,32 @@ export class TeamAccountsApi { throw members.error; } - return members.data; + const memberIds = members.data?.map((member) => member.id) ?? []; + const memberParams = await this.client + .schema('medreport') + .from('account_params') + .select('weight, height') + .in('account_id', memberIds); + + if (memberParams.error) { + throw memberParams.error; + } + + return { members: members.data, memberParams: memberParams.data }; + } + + async getMemberParams(ids: string[]) { + const memberParams = await this.client + .schema('medreport') + .from('account_params') + .select('weight, height') + .in('account_id', ids); + + if (memberParams.error) { + throw memberParams.error; + } + + return memberParams.data; } } diff --git a/packages/supabase/src/database.types.ts b/packages/supabase/src/database.types.ts index 9c0ec2f..8142777 100644 --- a/packages/supabase/src/database.types.ts +++ b/packages/supabase/src/database.types.ts @@ -1640,9 +1640,7 @@ export type Database = { Returns: Json } create_team_account: { - Args: - | { account_name: string } - | { account_name: string; new_personal_code: string } + Args: { account_name: string; new_personal_code: string } Returns: { application_role: Database["medreport"]["Enums"]["application_role"] city: string | null diff --git a/packages/ui/src/shadcn/card.tsx b/packages/ui/src/shadcn/card.tsx index 25a7eef..fec5a6b 100644 --- a/packages/ui/src/shadcn/card.tsx +++ b/packages/ui/src/shadcn/card.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { VariantProps, cva } from 'class-variance-authority'; + import { cn } from '.'; const cardVariants = cva('text-card-foreground rounded-xl border', { diff --git a/public/locales/en/account.json b/public/locales/en/account.json index 43ecce4..2939d6f 100644 --- a/public/locales/en/account.json +++ b/public/locales/en/account.json @@ -126,4 +126,4 @@ "updateRoleSuccess": "Role updated", "updateRoleError": "Something went wrong, please try again", "updateRoleLoading": "Updating role..." -} \ No newline at end of file +} diff --git a/public/locales/en/teams.json b/public/locales/en/teams.json index 6a683e2..2c26931 100644 --- a/public/locales/en/teams.json +++ b/public/locales/en/teams.json @@ -1,6 +1,6 @@ { "home": { - "pageTitle": "Home" + "pageTitle": "Dashboard" }, "settings": { "pageTitle": "Settings", diff --git a/public/locales/et/common.json b/public/locales/et/common.json index a259c6a..4c2473a 100644 --- a/public/locales/et/common.json +++ b/public/locales/et/common.json @@ -77,7 +77,7 @@ "account": "Account", "members": "Members", "billing": "Billing", - "dashboard": "Dashboard", + "dashboard": "Ülevaade", "settings": "Settings", "profile": "Profile", "application": "Application" @@ -129,4 +129,4 @@ "expiredAt": "Kehtiv kuni {{expiredAt}}" }, "doctor": "Arst" -} \ No newline at end of file +} diff --git a/public/locales/et/teams.json b/public/locales/et/teams.json index 3953e7f..515d538 100644 --- a/public/locales/et/teams.json +++ b/public/locales/et/teams.json @@ -1,6 +1,12 @@ { "home": { - "pageTitle": "Home" + "pageTitle": "Ülevaade", + "headerTitle": "{{companyName}} tervise ülevaade", + "healthDetails": "Ettevõtte terviseandmed", + "membersSettingsButtonTitle": "Halda töötajaid", + "membersSettingsButtonDescription": "Lisa, muuda või eemalda töötajaid.", + "membersBillingButtonTitle": "Halda eelarvet", + "membersBillingButtonDescription": "Vali kuidas soovid eelarvet töötajate vahel jagada." }, "settings": { "pageTitle": "Settings", @@ -18,6 +24,31 @@ "billing": { "pageTitle": "Billing" }, + "benefitStatistics": { + "budget": { + "title": "Ettevõtte Tervisekassa seis", + "balance": "Eelarve jääk {{balance}}", + "volume": "Eelarve maht {{volume}}" + }, + "data": { + "reservations": "{{value}} broneeringut", + "analysis": "Analüüsid", + "doctorsAndSpecialists": "Eriarstid ja spetsialistid", + "researches": "Uuringud", + "healthResearchPlans": "Terviseuuringute paketid", + "serviceUsage": "{{value}} teenuse kasutust", + "serviceSum": "Teenuste summa" + } + }, + "healthDetails": { + "women": "Naised", + "men": "Mehed", + "avgAge": "Keskmine vanus", + "bmi": "KMI", + "cholesterol": "Üldkolesterool", + "vitaminD": "Vitamiin D", + "smokers": "Suitsetajad" + }, "yourTeams": "Your Companies ({{teamsCount}})", "createTeam": "Create a Company", "creatingTeam": "Creating Company...", diff --git a/supabase/migrations/20250818111200_team_account.sql b/supabase/migrations/20250818111200_team_account.sql new file mode 100644 index 0000000..56ab1ae --- /dev/null +++ b/supabase/migrations/20250818111200_team_account.sql @@ -0,0 +1,2 @@ +CREATE TRIGGER log_account_change AFTER DELETE OR UPDATE ON medreport.accounts_memberships FOR EACH ROW EXECUTE FUNCTION audit.log_audit_changes(); + diff --git a/supabase/migrations/20250818155200_mismatch_team_account_workspace_fix.sql b/supabase/migrations/20250818155200_mismatch_team_account_workspace_fix.sql new file mode 100644 index 0000000..ffc25e4 --- /dev/null +++ b/supabase/migrations/20250818155200_mismatch_team_account_workspace_fix.sql @@ -0,0 +1,50 @@ +DROP FUNCTION IF EXISTS medreport.team_account_workspace(text); + +CREATE FUNCTION medreport.team_account_workspace(account_slug text) + RETURNS TABLE( + id uuid, + name character varying, + picture_url character varying, + slug text, + role character varying, + role_hierarchy_level integer, + primary_owner_user_id uuid, + subscription_status medreport.subscription_status, + permissions medreport.app_permissions[], + account_role varchar, + application_role medreport.application_role + ) + LANGUAGE plpgsql + SET search_path TO '' +AS $function$begin + return QUERY + select + accounts.id, + accounts.name, + accounts.picture_url, + accounts.slug, + roles.name, + roles.hierarchy_level, + accounts.primary_owner_user_id, + subscriptions.status, + array_agg(role_permissions.permission), + accounts_memberships.account_role, + accounts.application_role + from + medreport.accounts + join medreport.accounts_memberships on accounts.id = accounts_memberships.account_id + left join medreport.subscriptions on accounts.id = subscriptions.account_id + join medreport.roles on accounts_memberships.account_role = roles.name + left join medreport.role_permissions on accounts_memberships.account_role = role_permissions.role + where + accounts.slug = account_slug + and medreport.accounts_memberships.user_id = (select auth.uid()) + group by + accounts.id, + accounts_memberships.account_role, + subscriptions.status, + roles.hierarchy_level, + roles.name; +end;$function$; + +GRANT EXECUTE ON FUNCTION medreport.team_account_workspace(text) TO authenticated, service_role; \ No newline at end of file