feat: implemente authorization type to client
parent
e8cdc558ff
commit
5bbafd983f
|
@ -40,7 +40,7 @@ class UpdateProduct(Resource):
|
|||
@billing_ns.route('/products')
|
||||
class ListProducts(Resource):
|
||||
@jwt_required()
|
||||
@role_required('admin', 'user')
|
||||
@role_required('admin', 'user', 'client')
|
||||
def get(self):
|
||||
response = requests.get(url=f'{BILLING_API_URL}/billing/products', headers=HEADERS)
|
||||
return response.json(), response.status_code
|
|
@ -28,7 +28,7 @@ class TranscriptionExport(Resource):
|
|||
@usage_ns.response(400, 'Validation error')
|
||||
@usage_ns.response(404, 'File not found')
|
||||
@jwt_required()
|
||||
@role_required('admin', 'user')
|
||||
@role_required('admin', 'user', 'client')
|
||||
def get(self):
|
||||
"""
|
||||
Export transcription report in XLSX.
|
||||
|
@ -88,7 +88,7 @@ class TranscriptionUsageData(Resource):
|
|||
@usage_ns.response(200, 'success')
|
||||
@usage_ns.response(400, 'Validation error')
|
||||
@jwt_required()
|
||||
@role_required('admin', 'user')
|
||||
@role_required('admin', 'user', 'client')
|
||||
def get(self):
|
||||
"""
|
||||
Get transcription report data.
|
||||
|
|
|
@ -4,4 +4,4 @@ from typing import List, Literal
|
|||
class SigUpRequest(BaseModel):
|
||||
email: str
|
||||
password: str
|
||||
roles: List[Literal["admin", "user"]]
|
||||
roles: List[Literal["admin", "user", "client"]]
|
|
@ -10,7 +10,7 @@ import TranscriptionTable from "@/components/transcription-table"
|
|||
import ModelPricesTable from "@/components/model-prices-table"
|
||||
import ProductManagement from "@/components/product-management"
|
||||
import UserManagement from "@/components/user-management"
|
||||
import { isAdmin, isTokenExpired, getCurrentUserEmail, getUserRoles } from "@/lib/auth"
|
||||
import { isAdmin, isTokenExpired, getCurrentUserEmail, getUserRoles, isClient } from "@/lib/auth"
|
||||
|
||||
export default function Dashboard() {
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
||||
|
@ -18,6 +18,7 @@ export default function Dashboard() {
|
|||
const [userIsAdmin, setUserIsAdmin] = useState(false)
|
||||
const [userEmail, setUserEmail] = useState<string | null>(null)
|
||||
const [userRoles, setUserRoles] = useState<string[]>([])
|
||||
const [userIsClient, setUserIsClient] = useState(false)
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -34,10 +35,12 @@ export default function Dashboard() {
|
|||
const email = getCurrentUserEmail()
|
||||
const roles = getUserRoles()
|
||||
const adminStatus = isAdmin()
|
||||
const clientStatus = isClient()
|
||||
|
||||
setUserEmail(email)
|
||||
setUserRoles(roles)
|
||||
setUserIsAdmin(adminStatus)
|
||||
setUserIsClient(clientStatus)
|
||||
setIsAuthenticated(true)
|
||||
setIsLoading(false)
|
||||
}, [router])
|
||||
|
@ -83,9 +86,11 @@ export default function Dashboard() {
|
|||
|
||||
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
||||
<Tabs defaultValue="transcription" className="space-y-6">
|
||||
<TabsList className={`grid w-full ${userIsAdmin ? "grid-cols-4" : "grid-cols-3"}`}>
|
||||
<TabsList
|
||||
className={`grid w-full ${userIsAdmin ? "grid-cols-4" : userIsClient ? "grid-cols-2" : "grid-cols-3"}`}
|
||||
>
|
||||
<TabsTrigger value="transcription">Dados de Transcrição</TabsTrigger>
|
||||
<TabsTrigger value="models">Preços dos Modelos</TabsTrigger>
|
||||
{!userIsClient && <TabsTrigger value="models">Preços dos Modelos</TabsTrigger>}
|
||||
<TabsTrigger value="products">Produtos</TabsTrigger>
|
||||
{userIsAdmin && <TabsTrigger value="users">Usuários</TabsTrigger>}
|
||||
</TabsList>
|
||||
|
@ -102,6 +107,7 @@ export default function Dashboard() {
|
|||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
{!userIsClient && (
|
||||
<TabsContent value="models">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
|
@ -113,6 +119,7 @@ export default function Dashboard() {
|
|||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
)}
|
||||
|
||||
<TabsContent value="products">
|
||||
<Card>
|
||||
|
|
|
@ -9,12 +9,13 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@
|
|||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert"
|
||||
import { Download, Search, Loader2, DollarSign, TrendingUp, Calculator } from "lucide-react"
|
||||
import { isClient } from "@/lib/auth"
|
||||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000/api/v1"
|
||||
|
||||
function formatDateBr(dateStr: string) {
|
||||
const [year, month, day] = dateStr.split("-");
|
||||
return `${day}/${month}/${year}`;
|
||||
const [year, month, day] = dateStr.split("-")
|
||||
return `${day}/${month}/${year}`
|
||||
}
|
||||
|
||||
export function formatDateTime(raw?: string): string {
|
||||
|
@ -64,7 +65,7 @@ interface HitTranscriptionData {
|
|||
tts_cost: number // Custo total dos minutos do audio em texto
|
||||
tts_usage: number // Tempo total de trascrição do audio em segundos
|
||||
|
||||
total_min: number, // Minutos de ligação
|
||||
total_min: number // Minutos de ligação
|
||||
custo_hit: string // Custo HIT
|
||||
client_total_cost: string // Custo Cliente
|
||||
client_price: string // Preço Cliente por Minuto
|
||||
|
@ -140,6 +141,8 @@ export default function TranscriptionTable() {
|
|||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(20)
|
||||
|
||||
const [userIsClient, setUserIsClient] = useState(false)
|
||||
|
||||
const getAuthHeaders = () => {
|
||||
const token = localStorage.getItem("access_token")
|
||||
return {
|
||||
|
@ -295,12 +298,19 @@ export default function TranscriptionTable() {
|
|||
// Buscar cotação do dólar quando o componente for montado
|
||||
useEffect(() => {
|
||||
fetchExchangeRate()
|
||||
const clientStatus = isClient()
|
||||
setUserIsClient(clientStatus)
|
||||
|
||||
// Forçar "client" para usuários client
|
||||
if (clientStatus) {
|
||||
setWho("client")
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Card de Informações de Custo */}
|
||||
{costInfo && exchangeRate && (
|
||||
{!userIsClient && costInfo && exchangeRate && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
|
@ -389,7 +399,7 @@ export default function TranscriptionTable() {
|
|||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="client">Cliente</SelectItem>
|
||||
<SelectItem value="hit">HIT</SelectItem>
|
||||
{!userIsClient && <SelectItem value="hit">HIT</SelectItem>}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
@ -482,8 +492,8 @@ export default function TranscriptionTable() {
|
|||
<TableHead>Preço Final LLM ($)</TableHead>
|
||||
<TableHead>Provider LLM</TableHead>
|
||||
{/* <TableHead>Model TTS</TableHead> */}
|
||||
<TableHead>Provider TTS</TableHead>
|
||||
<TableHead>Preço Final TTS ($)</TableHead>
|
||||
<TableHead>Provider STT</TableHead>
|
||||
<TableHead>Preço Final STT ($)</TableHead>
|
||||
<TableHead>Segundos Transcritos</TableHead>
|
||||
</>
|
||||
) : (
|
||||
|
@ -563,8 +573,6 @@ export default function TranscriptionTable() {
|
|||
? `R$ ${Number((item as ClientTranscriptionData).client_total_cost).toFixed(2)}`
|
||||
: "-"}
|
||||
</TableCell>
|
||||
|
||||
|
||||
</>
|
||||
)}
|
||||
</TableRow>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
"use client"
|
||||
|
||||
import { DialogTrigger } from "@/components/ui/dialog"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
|
@ -9,14 +11,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|||
import { Alert, AlertDescription } from "@/components/ui/alert"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||
import { Edit2, Plus, Save, X, Loader2, RefreshCw, Users, Shield, Trash2 } from "lucide-react"
|
||||
import { isAdmin, getCurrentUserEmail } from "@/lib/auth"
|
||||
|
||||
|
@ -47,10 +42,21 @@ interface UpdateUserRequest {
|
|||
roles: string[]
|
||||
}
|
||||
|
||||
// interface DeleteUserResponse {
|
||||
// success: boolean
|
||||
// message: string
|
||||
// }
|
||||
|
||||
// interface UpdateUserResponse {
|
||||
// success: boolean
|
||||
// message: string
|
||||
// }
|
||||
|
||||
// Roles disponíveis no sistema
|
||||
const AVAILABLE_ROLES = [
|
||||
{ id: "admin", label: "Administrador", description: "Acesso total ao sistema" },
|
||||
{ id: "user", label: "Usuário", description: "Acesso parcial, não pode gerenciar usuários" },
|
||||
{ id: "client", label: "Cliente", description: "Acesso limitado, apenas visualização de dados de cliente" },
|
||||
]
|
||||
|
||||
export default function UserManagement() {
|
||||
|
@ -105,8 +111,7 @@ export default function UserManagement() {
|
|||
const errorData = await response.json()
|
||||
setError(errorData.message || "Erro ao buscar usuários")
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
console.log(err)
|
||||
} catch {
|
||||
setError("Erro de conexão com o servidor")
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
|
@ -131,6 +136,7 @@ export default function UserManagement() {
|
|||
})
|
||||
|
||||
if (response.ok) {
|
||||
// const result = await response.json()
|
||||
setSuccess("Usuário criado com sucesso!")
|
||||
setCreateForm({ email: "", password: "", roles: [] })
|
||||
setIsCreateDialogOpen(false)
|
||||
|
@ -139,8 +145,7 @@ export default function UserManagement() {
|
|||
const errorData = await response.json()
|
||||
setError(errorData.message || "Erro ao criar usuário")
|
||||
}
|
||||
} catch (err:unknown) {
|
||||
console.log(err)
|
||||
} catch {
|
||||
setError("Erro de conexão com o servidor")
|
||||
} finally {
|
||||
setIsCreating(false)
|
||||
|
@ -175,6 +180,7 @@ export default function UserManagement() {
|
|||
})
|
||||
|
||||
if (response.ok) {
|
||||
// const result: UpdateUserResponse = await response.json()
|
||||
setSuccess("Usuário atualizado com sucesso!")
|
||||
setEditingUser(null)
|
||||
setEditForm({ email: "", password: "", roles: [] })
|
||||
|
@ -183,8 +189,7 @@ export default function UserManagement() {
|
|||
const errorData = await response.json()
|
||||
setError(errorData.message || "Erro ao atualizar usuário")
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
} catch {
|
||||
setError("Erro de conexão com o servidor")
|
||||
} finally {
|
||||
setIsUpdating(false)
|
||||
|
@ -207,14 +212,14 @@ export default function UserManagement() {
|
|||
})
|
||||
|
||||
if (response.ok) {
|
||||
// const result: DeleteUserResponse = await response.json()
|
||||
setSuccess("Usuário excluído com sucesso!")
|
||||
await fetchUsers()
|
||||
} else {
|
||||
const errorData = await response.json()
|
||||
setError(errorData.message || "Erro ao excluir usuário")
|
||||
}
|
||||
} catch (err:unknown) {
|
||||
console.log(err)
|
||||
} catch {
|
||||
setError("Erro de conexão com o servidor")
|
||||
} finally {
|
||||
setIsDeleting(null)
|
||||
|
@ -259,6 +264,8 @@ export default function UserManagement() {
|
|||
return "default" as const
|
||||
case "user":
|
||||
return "secondary" as const
|
||||
case "client":
|
||||
return "outline" as const
|
||||
default:
|
||||
return "outline" as const
|
||||
}
|
||||
|
|
|
@ -61,6 +61,10 @@ export function isAdmin(): boolean {
|
|||
return hasRole("admin")
|
||||
}
|
||||
|
||||
export function isClient(): boolean {
|
||||
return hasRole("client")
|
||||
}
|
||||
|
||||
export function getCurrentUserEmail(): string | null {
|
||||
try {
|
||||
const token = localStorage.getItem("access_token")
|
||||
|
|
Loading…
Reference in New Issue