feat: Initial Diabetix application commit
- Add authentication with NextAuth v5 (credentials + email verification) - Implement dashboard with glycemia tracking and AI analysis - Add PDF report generation for Premium users - Implement Stripe integration for Premium subscriptions - Add responsive UI with Tailwind CSS and shadcn components - Database schema with Prisma ORM and PostgreSQL support - Real-time glycemia visualization with Recharts - Mobile-optimized entry form - User profile management with medical information - Subscription lifecycle management (create, cancel, webhook) - Email notifications with Resend - Feature gates for Free vs Premium plans Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
110
src/app/dashboard/rapports/page.tsx
Normal file
110
src/app/dashboard/rapports/page.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { format, subMonths, startOfMonth, endOfMonth } from "date-fns";
|
||||
import { fr } from "date-fns/locale";
|
||||
import { Download, Lock } from "lucide-react";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const metadata = { title: "Mes Rapports — Diabetix" };
|
||||
|
||||
export default async function RapportsPage() {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) redirect("/auth/login");
|
||||
|
||||
const userId = session.user.id;
|
||||
const user = await prisma.user.findUnique({ where: { id: userId } });
|
||||
|
||||
const isPremium = user?.plan === "PREMIUM";
|
||||
|
||||
const months = Array.from({ length: 12 }, (_, i) => {
|
||||
return subMonths(startOfMonth(new Date()), i);
|
||||
});
|
||||
|
||||
const readingsByMonth = await Promise.all(
|
||||
months.map(async (month) => {
|
||||
const count = await prisma.reading.count({
|
||||
where: {
|
||||
userId,
|
||||
measuredAt: {
|
||||
gte: month,
|
||||
lte: endOfMonth(month),
|
||||
},
|
||||
},
|
||||
});
|
||||
return { month, count };
|
||||
})
|
||||
);
|
||||
|
||||
// Filter to only show months with readings
|
||||
const monthsWithReadings = readingsByMonth.filter(({ count }) => count > 0);
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-5xl space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold text-slate-900">Mes Rapports</h1>
|
||||
<p className="mt-1 text-sm text-slate-500">
|
||||
Téléchargez vos rapports glycémie mensuels en PDF
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{!isPremium && (
|
||||
<div className="rounded-lg border border-blue-200 bg-blue-50 p-4">
|
||||
<div className="flex gap-2">
|
||||
<Lock className="h-5 w-5 text-blue-600 flex-shrink-0" />
|
||||
<div className="text-sm text-blue-800">
|
||||
<p className="font-medium">Feature Premium</p>
|
||||
<p className="text-blue-700">
|
||||
Les rapports PDF sont exclusifs au plan Premium. Passer à Premium pour y accéder.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
{monthsWithReadings.length > 0 ? (
|
||||
monthsWithReadings.map(({ month, count }) => (
|
||||
<div
|
||||
key={month.toISOString()}
|
||||
className="flex items-center justify-between rounded-lg border border-slate-200 bg-white p-4 hover:border-slate-300"
|
||||
>
|
||||
<div>
|
||||
<p className="font-medium text-slate-900">
|
||||
{format(month, "MMMM yyyy", { locale: fr })}
|
||||
</p>
|
||||
<p className="text-sm text-slate-500">{count} mesure(s)</p>
|
||||
</div>
|
||||
|
||||
{count > 0 && isPremium && (
|
||||
<a
|
||||
href={`/api/reports/generate-pdf?month=${month.toISOString()}`}
|
||||
download
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-teal-600 px-3 py-2 text-sm font-medium text-white hover:bg-teal-700"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">Télécharger</span>
|
||||
<span className="sm:hidden">PDF</span>
|
||||
</a>
|
||||
)}
|
||||
|
||||
{count > 0 && !isPremium && (
|
||||
<div className="inline-flex items-center gap-2 rounded-lg bg-slate-100 px-3 py-2 text-sm font-medium text-slate-500">
|
||||
<Lock className="h-4 w-4" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{count === 0 && (
|
||||
<p className="text-sm text-slate-400">Aucune donnée</p>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="text-center rounded-lg border border-slate-200 bg-white p-8">
|
||||
<p className="text-sm text-slate-500">Aucun mois avec des données.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user