Build fiscal assistance management systems with Next.js, TypeScript, Prisma, and NextAuth for accounting support centers (NAF)
A specialized skill for building comprehensive fiscal assistance management systems for Núcleos de Apoio Contábil e Fiscal (NAF) - Accounting and Fiscal Support Centers. This skill guides you through developing full-stack web applications that manage client interactions, student services, and coordinator oversight in educational accounting assistance programs.
This skill helps you build a complete NAF management system with:
The system manages 21+ fiscal services including:
Create Next.js project with TypeScript:
```bash
npx create-next-app@latest naf-system --typescript --tailwind --app
cd naf-system
```
```bash
npm install @prisma/client next-auth @auth/prisma-adapter
npm install nodemailer recharts
npm install -D prisma @types/node @types/nodemailer
```
Install Shadcn/ui components:
```bash
npx shadcn-ui@latest init
npx shadcn-ui@latest add button card input select table dialog
```
Initialize Prisma:
```bash
npx prisma init
```
Create schema with user roles, services, and appointments:
```prisma
model User {
id String @id @default(cuid())
name String?
email String @unique
role UserRole @default(STUDENT)
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
appointments Appointment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum UserRole {
STUDENT
PROFESSOR
COORDINATOR
}
model Service {
id String @id @default(cuid())
name String
description String
category String
active Boolean @default(true)
appointments Appointment[]
createdAt DateTime @default(now())
}
model Appointment {
id String @id @default(cuid())
userId String
serviceId String
professorId String?
date DateTime
status AppointmentStatus @default(PENDING)
notes String?
user User @relation(fields: [userId], references: [id])
service Service @relation(fields: [serviceId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum AppointmentStatus {
PENDING
CONFIRMED
COMPLETED
CANCELLED
}
```
Create NextAuth configuration at `app/api/auth/[...nextauth]/route.ts`:
```typescript
import NextAuth from "next-auth"
import { PrismaAdapter } from "@auth/prisma-adapter"
import CredentialsProvider from "next-auth/providers/credentials"
import { prisma } from "@/lib/prisma"
const handler = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
// Implement authentication logic
const user = await prisma.user.findUnique({
where: { email: credentials?.email }
})
// Add password verification
return user
}
})
],
callbacks: {
async session({ session, token }) {
if (token && session.user) {
session.user.id = token.sub
session.user.role = token.role
}
return session
},
async jwt({ token, user }) {
if (user) {
token.role = user.role
}
return token
}
},
pages: {
signIn: "/auth/signin"
}
})
export { handler as GET, handler as POST }
```
Create dashboard page at `app/dashboard/coordinator/page.tsx`:
```typescript
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from "recharts"
export default async function CoordinatorDashboard() {
// Fetch statistics
const stats = await getStatistics()
return (
<div className="space-y-6">
<h1 className="text-3xl font-bold">Dashboard do Coordenador</h1>
<div className="grid gap-4 md:grid-cols-3">
<Card>
<CardHeader>
<CardTitle>Total de Alunos</CardTitle>
</CardHeader>
<CardContent>
<p className="text-4xl font-bold">{stats.totalStudents}</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Atendimentos do Mês</CardTitle>
</CardHeader>
<CardContent>
<p className="text-4xl font-bold">{stats.monthlyAppointments}</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Serviços Ativos</CardTitle>
</CardHeader>
<CardContent>
<p className="text-4xl font-bold">{stats.activeServices}</p>
</CardContent>
</Card>
</div>
<Card>
<CardHeader>
<CardTitle>Atendimentos por Serviço</CardTitle>
</CardHeader>
<CardContent>
<BarChart width={600} height={300} data={stats.serviceData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="count" fill="#8884d8" />
</BarChart>
</CardContent>
</Card>
</div>
)
}
```
Create service catalog at `app/services/page.tsx`:
```typescript
import { prisma } from "@/lib/prisma"
import { ServiceCard } from "@/components/service-card"
import { Button } from "@/components/ui/button"
export default async function ServicesPage() {
const services = await prisma.service.findMany({
where: { active: true },
orderBy: { name: "asc" }
})
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-3xl font-bold">Serviços NAF</h1>
<Button>Novo Serviço</Button>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{services.map(service => (
<ServiceCard key={service.id} service={service} />
))}
</div>
</div>
)
}
```
Build scheduling interface at `app/appointments/new/page.tsx`:
```typescript
"use client"
import { useState } from "react"
import { useSession } from "next-auth/react"
import { Calendar } from "@/components/ui/calendar"
import { Select } from "@/components/ui/select"
import { Button } from "@/components/ui/button"
export default function NewAppointment() {
const { data: session } = useSession()
const [selectedDate, setSelectedDate] = useState<Date>()
const [selectedService, setSelectedService] = useState<string>()
const handleSubmit = async () => {
const response = await fetch("/api/appointments", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
userId: session?.user?.id,
serviceId: selectedService,
date: selectedDate
})
})
if (response.ok) {
// Send confirmation email
await fetch("/api/email/confirmation", {
method: "POST",
body: JSON.stringify({ appointmentId: response.json().id })
})
}
}
return (
<div className="space-y-6">
<h1 className="text-3xl font-bold">Agendar Atendimento</h1>
<div className="grid gap-6 md:grid-cols-2">
<div>
<label>Selecione o Serviço</label>
<Select onValueChange={setSelectedService}>
{/* Service options */}
</Select>
</div>
<div>
<label>Selecione a Data</label>
<Calendar
mode="single"
selected={selectedDate}
onSelect={setSelectedDate}
/>
</div>
</div>
<Button onClick={handleSubmit}>Confirmar Agendamento</Button>
</div>
)
}
```
Create email service at `lib/email.ts`:
```typescript
import nodemailer from "nodemailer"
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT || "587"),
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
}
})
export async function sendAppointmentConfirmation(
email: string,
appointment: any
) {
await transporter.sendMail({
from: process.env.SMTP_FROM,
to: email,
subject: "Confirmação de Agendamento - NAF",
html: `
<h1>Agendamento Confirmado</h1>
<p>Seu atendimento foi agendado para ${appointment.date}</p>
<p>Serviço: ${appointment.service.name}</p>
`
})
}
```
Create reports page at `app/reports/page.tsx`:
```typescript
import { prisma } from "@/lib/prisma"
import { Button } from "@/components/ui/button"
import { exportToExcel } from "@/lib/export"
export default async function ReportsPage() {
const appointments = await prisma.appointment.findMany({
include: {
user: true,
service: true
},
orderBy: { createdAt: "desc" }
})
const handleExport = () => {
exportToExcel(appointments, "relatorio-naf.xlsx")
}
return (
<div className="space-y-6">
<div className="flex justify-between">
<h1 className="text-3xl font-bold">Relatórios de Atendimento</h1>
<Button onClick={handleExport}>Exportar Excel</Button>
</div>
<div className="rounded-md border">
<table className="w-full">
<thead>
<tr>
<th>Data</th>
<th>Aluno</th>
<th>Serviço</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{appointments.map(apt => (
<tr key={apt.id}>
<td>{new Date(apt.date).toLocaleDateString()}</td>
<td>{apt.user.name}</td>
<td>{apt.service.name}</td>
<td>{apt.status}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}
```
Create API route at `app/api/forms/submit/route.ts`:
```typescript
import { NextRequest, NextResponse } from "next/server"
import { prisma } from "@/lib/prisma"
export async function POST(request: NextRequest) {
const data = await request.json()
// Process Office Forms submission
const student = await prisma.user.create({
data: {
name: data.name,
email: data.email,
role: "STUDENT"
}
})
// Create initial appointment if service selected
if (data.serviceId) {
await prisma.appointment.create({
data: {
userId: student.id,
serviceId: data.serviceId,
date: new Date(data.preferredDate),
status: "PENDING"
}
})
}
return NextResponse.json({ success: true, studentId: student.id })
}
```
Create `.env` file with:
```env
DATABASE_URL="postgresql://user:password@localhost:5432/naf_db"
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="your-secret-key"
SMTP_HOST="smtp.gmail.com"
SMTP_PORT="587"
SMTP_USER="[email protected]"
SMTP_PASS="your-app-password"
SMTP_FROM="NAF Sistema <[email protected]>"
```
```bash
npm run dev
```
```bash
npx prisma generate
npx prisma db push
```
```bash
npx prisma db seed
```
```bash
npm run build
npm start
```
1. **Authentication Flow**: Test login for all three user roles
2. **Service Catalog**: Verify all 21+ services are displayed correctly
3. **Appointment Scheduling**: Ensure date/time conflicts are handled
4. **Email Notifications**: Confirm emails are sent for appointments
5. **Dashboard Charts**: Verify statistics update in real-time
6. **Reports Export**: Test Excel export functionality
7. **Form Integration**: Validate external form submissions
8. **Role-Based Access**: Ensure coordinators see admin features only
After basic implementation:
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/naf-system-developer/raw