Improve loan page and add company filter to USB bulk control

- Update Next.js to 16.0.7
- Fix accent on menu item "Emprestimos" to "Empréstimos"
- Standardize loan page with project patterns (DateRangeButton, cyan color scheme, ToggleGroup)
- Add company filter to USB bulk policy dialog
- Update CardDescription text in devices overview
- Fix useEffect dependency warning in desktop main.tsx

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
rever-tecnologia 2025-12-04 14:52:27 -03:00
parent 063c5dfde7
commit 38995b95c6
8 changed files with 1123 additions and 1184 deletions

View file

@ -1269,6 +1269,7 @@ export function AdminDevicesOverview({
const [selectedUsbPolicy, setSelectedUsbPolicy] = useState<"ALLOW" | "BLOCK_ALL" | "READONLY">("ALLOW")
const [isApplyingUsbPolicy, setIsApplyingUsbPolicy] = useState(false)
const [usbPolicySelection, setUsbPolicySelection] = useState<string[]>([])
const [usbCompanyFilter, setUsbCompanyFilter] = useState<string>("all")
const [isCreateDeviceOpen, setIsCreateDeviceOpen] = useState(false)
const [createDeviceLoading, setCreateDeviceLoading] = useState(false)
const [newDeviceName, setNewDeviceName] = useState("")
@ -1713,15 +1714,18 @@ export function AdminDevicesOverview({
}, [])
const handleSelectAllUsbDevices = useCallback((checked: boolean) => {
const windowsDevices = filteredDevices.filter(
const allWindowsDevices = filteredDevices.filter(
(m) => (m.devicePlatform ?? "").toLowerCase() === "windows"
)
const windowsDevices = usbCompanyFilter === "all"
? allWindowsDevices
: allWindowsDevices.filter((m) => (m.companySlug ?? "") === usbCompanyFilter)
if (checked) {
setUsbPolicySelection(windowsDevices.map((m) => m.id))
} else {
setUsbPolicySelection([])
}
}, [filteredDevices])
}, [filteredDevices, usbCompanyFilter])
const handleApplyBulkUsbPolicy = useCallback(async () => {
if (usbPolicySelection.length === 0) {
@ -1853,7 +1857,7 @@ export function AdminDevicesOverview({
<CardHeader className="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
<div>
<CardTitle>Dispositivos registrados</CardTitle>
<CardDescription>Sincronizadas via agente local instalado nas máquinas. Atualiza em tempo quase real.</CardDescription>
<CardDescription>Sincronizadas via agente local instalado nas máquinas.</CardDescription>
</div>
<div className="flex flex-wrap items-center gap-2">
<Button size="sm" onClick={handleOpenCreateDevice}>
@ -2199,20 +2203,55 @@ export function AdminDevicesOverview({
</DialogDescription>
</DialogHeader>
{(() => {
const windowsDevices = filteredDevices.filter(
const allWindowsDevices = filteredDevices.filter(
(m) => (m.devicePlatform ?? "").toLowerCase() === "windows"
)
if (windowsDevices.length === 0) {
const windowsDevices = usbCompanyFilter === "all"
? allWindowsDevices
: allWindowsDevices.filter((m) => (m.companySlug ?? "") === usbCompanyFilter)
const usbCompanyOptions = Array.from(
new Map(
allWindowsDevices
.filter((m) => m.companySlug)
.map((m) => [m.companySlug, m.companyName ?? m.companySlug])
)
).sort((a, b) => (a[1] ?? "").localeCompare(b[1] ?? "", "pt-BR"))
if (allWindowsDevices.length === 0) {
return (
<div className="rounded-md border border-dashed border-slate-200 px-4 py-8 text-center text-sm text-muted-foreground">
Nenhum dispositivo Windows disponível com os filtros atuais.
</div>
)
}
const allSelected = usbPolicySelection.length === windowsDevices.length
const allSelected = windowsDevices.length > 0 && usbPolicySelection.length === windowsDevices.length
const someSelected = usbPolicySelection.length > 0 && usbPolicySelection.length < windowsDevices.length
return (
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm font-medium text-slate-700">Filtrar por empresa</label>
<Select
value={usbCompanyFilter}
onValueChange={(value) => {
setUsbCompanyFilter(value)
setUsbPolicySelection([])
}}
disabled={isApplyingUsbPolicy}
>
<SelectTrigger>
<SelectValue placeholder="Selecione uma empresa" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">
<span>Todas as empresas</span>
</SelectItem>
{usbCompanyOptions.map(([slug, name]) => (
<SelectItem key={slug} value={slug ?? ""}>
<span>{name}</span>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex items-center justify-between rounded-md border border-slate-200 bg-slate-50 px-3 py-2 text-xs text-slate-600">
<span>
{usbPolicySelection.length} de {windowsDevices.length} dispositivos Windows selecionados
@ -2220,9 +2259,14 @@ export function AdminDevicesOverview({
<Checkbox
checked={allSelected ? true : someSelected ? "indeterminate" : false}
onCheckedChange={(value) => handleSelectAllUsbDevices(value === true || value === "indeterminate")}
disabled={isApplyingUsbPolicy}
disabled={isApplyingUsbPolicy || windowsDevices.length === 0}
/>
</div>
{windowsDevices.length === 0 ? (
<div className="rounded-md border border-dashed border-slate-200 px-4 py-6 text-center text-sm text-muted-foreground">
Nenhum dispositivo Windows nesta empresa.
</div>
) : (
<div className="max-h-48 space-y-1 overflow-y-auto rounded-md border border-slate-200 p-2">
{windowsDevices.map((device) => {
const checked = usbPolicySelection.includes(device.id)
@ -2249,6 +2293,7 @@ export function AdminDevicesOverview({
)
})}
</div>
)}
<div className="space-y-2">
<label className="text-sm font-medium text-slate-700">Política USB</label>
<Select