@greatapps/greatauth-ui 0.3.6 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +137 -1
- package/dist/index.js +1430 -5
- package/dist/index.js.map +1 -1
- package/package.json +11 -7
- package/src/components/entity-avatar.tsx +72 -0
- package/src/components/image-crop-upload.tsx +253 -0
- package/src/components/index.ts +4 -0
- package/src/components/users/data-table.tsx +185 -0
- package/src/components/users/user-form-dialog.tsx +169 -0
- package/src/components/users/user-profile-badge.tsx +31 -0
- package/src/components/users/users-page.tsx +273 -0
- package/src/hooks/use-users.ts +82 -0
- package/src/index.ts +20 -0
- package/src/lib/image-utils.ts +82 -0
- package/src/types/users.ts +46 -0
package/dist/index.js
CHANGED
|
@@ -7,15 +7,15 @@ var { useSession, signIn, signUp, signOut } = authClient;
|
|
|
7
7
|
import { NextResponse } from "next/server";
|
|
8
8
|
function createAuthMiddleware(config) {
|
|
9
9
|
const { publicPaths, preserveSearchParams = false } = config;
|
|
10
|
-
return function middleware(
|
|
11
|
-
const { pathname, search } =
|
|
10
|
+
return function middleware(request2) {
|
|
11
|
+
const { pathname, search } = request2.nextUrl;
|
|
12
12
|
if (publicPaths.some((path) => pathname.startsWith(path))) {
|
|
13
13
|
return NextResponse.next();
|
|
14
14
|
}
|
|
15
|
-
const sessionToken =
|
|
15
|
+
const sessionToken = request2.cookies.get("better-auth.session_token")?.value || request2.cookies.get("__Secure-better-auth.session_token")?.value;
|
|
16
16
|
if (!sessionToken) {
|
|
17
17
|
const callbackUrl = preserveSearchParams ? pathname + search : pathname;
|
|
18
|
-
const loginUrl = new URL("/login",
|
|
18
|
+
const loginUrl = new URL("/login", request2.url);
|
|
19
19
|
loginUrl.searchParams.set("callbackUrl", callbackUrl);
|
|
20
20
|
return NextResponse.redirect(loginUrl);
|
|
21
21
|
}
|
|
@@ -346,6 +346,16 @@ function SheetDescription({
|
|
|
346
346
|
|
|
347
347
|
// src/components/ui/skeleton.tsx
|
|
348
348
|
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
349
|
+
function Skeleton({ className, ...props }) {
|
|
350
|
+
return /* @__PURE__ */ jsx6(
|
|
351
|
+
"div",
|
|
352
|
+
{
|
|
353
|
+
"data-slot": "skeleton",
|
|
354
|
+
className: cn("bg-muted rounded-md animate-pulse", className),
|
|
355
|
+
...props
|
|
356
|
+
}
|
|
357
|
+
);
|
|
358
|
+
}
|
|
349
359
|
|
|
350
360
|
// src/components/ui/sidebar.tsx
|
|
351
361
|
import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -1312,16 +1322,1426 @@ function LoginForm({ config }) {
|
|
|
1312
1322
|
/* @__PURE__ */ jsx18("p", { className: "mt-6 text-center text-xs text-muted-foreground", children: config.footerText || "Acesso restrito a utilizadores autorizados" })
|
|
1313
1323
|
] }) });
|
|
1314
1324
|
}
|
|
1325
|
+
|
|
1326
|
+
// src/components/entity-avatar.tsx
|
|
1327
|
+
import { jsx as jsx19, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1328
|
+
var COLORS = [
|
|
1329
|
+
"bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300",
|
|
1330
|
+
"bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300",
|
|
1331
|
+
"bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300",
|
|
1332
|
+
"bg-emerald-100 text-emerald-700 dark:bg-emerald-900 dark:text-emerald-300",
|
|
1333
|
+
"bg-cyan-100 text-cyan-700 dark:bg-cyan-900 dark:text-cyan-300",
|
|
1334
|
+
"bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300",
|
|
1335
|
+
"bg-violet-100 text-violet-700 dark:bg-violet-900 dark:text-violet-300",
|
|
1336
|
+
"bg-pink-100 text-pink-700 dark:bg-pink-900 dark:text-pink-300"
|
|
1337
|
+
];
|
|
1338
|
+
var SIZE_MAP = {
|
|
1339
|
+
xs: "h-5 w-5 text-[9px]",
|
|
1340
|
+
sm: "h-6 w-6 text-[10px]",
|
|
1341
|
+
md: "h-8 w-8 text-xs",
|
|
1342
|
+
lg: "h-10 w-10 text-sm",
|
|
1343
|
+
xl: "h-16 w-16 text-lg"
|
|
1344
|
+
};
|
|
1345
|
+
function hashCode(str) {
|
|
1346
|
+
let hash = 0;
|
|
1347
|
+
for (let i = 0; i < str.length; i++) {
|
|
1348
|
+
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
1349
|
+
}
|
|
1350
|
+
return Math.abs(hash);
|
|
1351
|
+
}
|
|
1352
|
+
function getInitials(name) {
|
|
1353
|
+
const parts = name.trim().split(/\s+/);
|
|
1354
|
+
if (parts.length >= 2) {
|
|
1355
|
+
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
1356
|
+
}
|
|
1357
|
+
return (name[0] || "?").toUpperCase();
|
|
1358
|
+
}
|
|
1359
|
+
function EntityAvatar({
|
|
1360
|
+
photo,
|
|
1361
|
+
name,
|
|
1362
|
+
size = "md",
|
|
1363
|
+
className
|
|
1364
|
+
}) {
|
|
1365
|
+
const displayName = name || "?";
|
|
1366
|
+
const color = COLORS[hashCode(displayName) % COLORS.length];
|
|
1367
|
+
const initials = getInitials(displayName);
|
|
1368
|
+
return /* @__PURE__ */ jsxs11(Avatar, { className: cn(SIZE_MAP[size], className), children: [
|
|
1369
|
+
photo && /* @__PURE__ */ jsx19(
|
|
1370
|
+
AvatarImage,
|
|
1371
|
+
{
|
|
1372
|
+
src: photo,
|
|
1373
|
+
alt: displayName,
|
|
1374
|
+
className: "object-cover"
|
|
1375
|
+
}
|
|
1376
|
+
),
|
|
1377
|
+
/* @__PURE__ */ jsx19(AvatarFallback, { className: cn("font-medium", color), children: initials })
|
|
1378
|
+
] });
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
// src/components/image-crop-upload.tsx
|
|
1382
|
+
import { useCallback as useCallback3, useRef, useState as useState4 } from "react";
|
|
1383
|
+
import Cropper from "react-easy-crop";
|
|
1384
|
+
|
|
1385
|
+
// src/components/ui/dialog.tsx
|
|
1386
|
+
import { Dialog as DialogPrimitive } from "radix-ui";
|
|
1387
|
+
import { X as X2 } from "lucide-react";
|
|
1388
|
+
import { jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1389
|
+
function Dialog({
|
|
1390
|
+
...props
|
|
1391
|
+
}) {
|
|
1392
|
+
return /* @__PURE__ */ jsx20(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
|
|
1393
|
+
}
|
|
1394
|
+
function DialogPortal({
|
|
1395
|
+
...props
|
|
1396
|
+
}) {
|
|
1397
|
+
return /* @__PURE__ */ jsx20(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
|
|
1398
|
+
}
|
|
1399
|
+
function DialogOverlay({
|
|
1400
|
+
className,
|
|
1401
|
+
...props
|
|
1402
|
+
}) {
|
|
1403
|
+
return /* @__PURE__ */ jsx20(
|
|
1404
|
+
DialogPrimitive.Overlay,
|
|
1405
|
+
{
|
|
1406
|
+
"data-slot": "dialog-overlay",
|
|
1407
|
+
className: cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50", className),
|
|
1408
|
+
...props
|
|
1409
|
+
}
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
function DialogContent({
|
|
1413
|
+
className,
|
|
1414
|
+
children,
|
|
1415
|
+
showCloseButton = true,
|
|
1416
|
+
...props
|
|
1417
|
+
}) {
|
|
1418
|
+
return /* @__PURE__ */ jsxs12(DialogPortal, { children: [
|
|
1419
|
+
/* @__PURE__ */ jsx20(DialogOverlay, {}),
|
|
1420
|
+
/* @__PURE__ */ jsxs12(
|
|
1421
|
+
DialogPrimitive.Content,
|
|
1422
|
+
{
|
|
1423
|
+
"data-slot": "dialog-content",
|
|
1424
|
+
className: cn(
|
|
1425
|
+
"bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-6 rounded-xl p-6 text-sm ring-1 duration-100 sm:max-w-md fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none",
|
|
1426
|
+
className
|
|
1427
|
+
),
|
|
1428
|
+
...props,
|
|
1429
|
+
children: [
|
|
1430
|
+
children,
|
|
1431
|
+
showCloseButton && /* @__PURE__ */ jsx20(DialogPrimitive.Close, { "data-slot": "dialog-close", asChild: true, children: /* @__PURE__ */ jsxs12(Button, { variant: "ghost", className: "absolute top-4 right-4", size: "icon-sm", children: [
|
|
1432
|
+
/* @__PURE__ */ jsx20(X2, {}),
|
|
1433
|
+
/* @__PURE__ */ jsx20("span", { className: "sr-only", children: "Close" })
|
|
1434
|
+
] }) })
|
|
1435
|
+
]
|
|
1436
|
+
}
|
|
1437
|
+
)
|
|
1438
|
+
] });
|
|
1439
|
+
}
|
|
1440
|
+
function DialogHeader({ className, ...props }) {
|
|
1441
|
+
return /* @__PURE__ */ jsx20(
|
|
1442
|
+
"div",
|
|
1443
|
+
{
|
|
1444
|
+
"data-slot": "dialog-header",
|
|
1445
|
+
className: cn("gap-2 flex flex-col", className),
|
|
1446
|
+
...props
|
|
1447
|
+
}
|
|
1448
|
+
);
|
|
1449
|
+
}
|
|
1450
|
+
function DialogFooter({
|
|
1451
|
+
className,
|
|
1452
|
+
children,
|
|
1453
|
+
...props
|
|
1454
|
+
}) {
|
|
1455
|
+
return /* @__PURE__ */ jsx20(
|
|
1456
|
+
"div",
|
|
1457
|
+
{
|
|
1458
|
+
"data-slot": "dialog-footer",
|
|
1459
|
+
className: cn(
|
|
1460
|
+
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
1461
|
+
className
|
|
1462
|
+
),
|
|
1463
|
+
...props,
|
|
1464
|
+
children
|
|
1465
|
+
}
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1468
|
+
function DialogTitle({
|
|
1469
|
+
className,
|
|
1470
|
+
...props
|
|
1471
|
+
}) {
|
|
1472
|
+
return /* @__PURE__ */ jsx20(
|
|
1473
|
+
DialogPrimitive.Title,
|
|
1474
|
+
{
|
|
1475
|
+
"data-slot": "dialog-title",
|
|
1476
|
+
className: cn("leading-none font-medium", className),
|
|
1477
|
+
...props
|
|
1478
|
+
}
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
// src/components/ui/slider.tsx
|
|
1483
|
+
import * as React2 from "react";
|
|
1484
|
+
import { Slider as SliderPrimitive } from "radix-ui";
|
|
1485
|
+
import { jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1486
|
+
function Slider({
|
|
1487
|
+
className,
|
|
1488
|
+
defaultValue,
|
|
1489
|
+
value,
|
|
1490
|
+
min = 0,
|
|
1491
|
+
max = 100,
|
|
1492
|
+
...props
|
|
1493
|
+
}) {
|
|
1494
|
+
const _values = React2.useMemo(
|
|
1495
|
+
() => Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max],
|
|
1496
|
+
[value, defaultValue, min, max]
|
|
1497
|
+
);
|
|
1498
|
+
return /* @__PURE__ */ jsxs13(
|
|
1499
|
+
SliderPrimitive.Root,
|
|
1500
|
+
{
|
|
1501
|
+
"data-slot": "slider",
|
|
1502
|
+
defaultValue,
|
|
1503
|
+
value,
|
|
1504
|
+
min,
|
|
1505
|
+
max,
|
|
1506
|
+
className: cn(
|
|
1507
|
+
"data-vertical:min-h-40 relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-vertical:h-full data-vertical:w-auto data-vertical:flex-col",
|
|
1508
|
+
className
|
|
1509
|
+
),
|
|
1510
|
+
...props,
|
|
1511
|
+
children: [
|
|
1512
|
+
/* @__PURE__ */ jsx21(
|
|
1513
|
+
SliderPrimitive.Track,
|
|
1514
|
+
{
|
|
1515
|
+
"data-slot": "slider-track",
|
|
1516
|
+
className: "bg-muted rounded-full data-horizontal:h-1.5 data-vertical:w-1.5 relative grow overflow-hidden data-horizontal:w-full data-vertical:h-full",
|
|
1517
|
+
children: /* @__PURE__ */ jsx21(
|
|
1518
|
+
SliderPrimitive.Range,
|
|
1519
|
+
{
|
|
1520
|
+
"data-slot": "slider-range",
|
|
1521
|
+
className: "bg-primary absolute select-none data-horizontal:h-full data-vertical:w-full"
|
|
1522
|
+
}
|
|
1523
|
+
)
|
|
1524
|
+
}
|
|
1525
|
+
),
|
|
1526
|
+
Array.from({ length: _values.length }, (_, index) => /* @__PURE__ */ jsx21(
|
|
1527
|
+
SliderPrimitive.Thumb,
|
|
1528
|
+
{
|
|
1529
|
+
"data-slot": "slider-thumb",
|
|
1530
|
+
className: "border-primary ring-ring/50 size-4 rounded-full border bg-white shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden block shrink-0 select-none disabled:pointer-events-none disabled:opacity-50"
|
|
1531
|
+
},
|
|
1532
|
+
index
|
|
1533
|
+
))
|
|
1534
|
+
]
|
|
1535
|
+
}
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// src/components/image-crop-upload.tsx
|
|
1540
|
+
import { toast } from "sonner";
|
|
1541
|
+
import { Camera, Trash2 } from "lucide-react";
|
|
1542
|
+
|
|
1543
|
+
// src/lib/image-utils.ts
|
|
1544
|
+
async function getCroppedImage(imageSrc, cropArea, outputSize = 400, quality = 0.85) {
|
|
1545
|
+
const image = await loadImage(imageSrc);
|
|
1546
|
+
const canvas = document.createElement("canvas");
|
|
1547
|
+
canvas.width = outputSize;
|
|
1548
|
+
canvas.height = outputSize;
|
|
1549
|
+
const ctx = canvas.getContext("2d");
|
|
1550
|
+
if (!ctx) throw new Error("Canvas context not available");
|
|
1551
|
+
ctx.drawImage(
|
|
1552
|
+
image,
|
|
1553
|
+
cropArea.x,
|
|
1554
|
+
cropArea.y,
|
|
1555
|
+
cropArea.width,
|
|
1556
|
+
cropArea.height,
|
|
1557
|
+
0,
|
|
1558
|
+
0,
|
|
1559
|
+
outputSize,
|
|
1560
|
+
outputSize
|
|
1561
|
+
);
|
|
1562
|
+
const blob = await new Promise((resolve, reject) => {
|
|
1563
|
+
canvas.toBlob(
|
|
1564
|
+
(b) => b ? resolve(b) : reject(new Error("Failed to create blob")),
|
|
1565
|
+
"image/webp",
|
|
1566
|
+
quality
|
|
1567
|
+
);
|
|
1568
|
+
});
|
|
1569
|
+
return blobToBase64(blob);
|
|
1570
|
+
}
|
|
1571
|
+
function loadImage(src) {
|
|
1572
|
+
return new Promise((resolve, reject) => {
|
|
1573
|
+
const img = new Image();
|
|
1574
|
+
img.crossOrigin = "anonymous";
|
|
1575
|
+
img.onload = () => resolve(img);
|
|
1576
|
+
img.onerror = reject;
|
|
1577
|
+
img.src = src;
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
function blobToBase64(blob) {
|
|
1581
|
+
return new Promise((resolve, reject) => {
|
|
1582
|
+
const reader = new FileReader();
|
|
1583
|
+
reader.onload = () => {
|
|
1584
|
+
const result = reader.result;
|
|
1585
|
+
resolve(result.split(",")[1]);
|
|
1586
|
+
};
|
|
1587
|
+
reader.onerror = reject;
|
|
1588
|
+
reader.readAsDataURL(blob);
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1591
|
+
var ACCEPTED_TYPES = ["image/png", "image/jpeg", "image/jpg", "image/webp"];
|
|
1592
|
+
var MAX_SIZE_BYTES = 5 * 1024 * 1024;
|
|
1593
|
+
function validateImageFile(file) {
|
|
1594
|
+
if (!ACCEPTED_TYPES.includes(file.type)) {
|
|
1595
|
+
return "Formato n\xE3o suportado. Use PNG, JPG ou WEBP";
|
|
1596
|
+
}
|
|
1597
|
+
if (file.size > MAX_SIZE_BYTES) {
|
|
1598
|
+
return "Imagem muito grande. M\xE1ximo: 5MB";
|
|
1599
|
+
}
|
|
1600
|
+
return null;
|
|
1601
|
+
}
|
|
1602
|
+
function fileToDataUrl(file) {
|
|
1603
|
+
return new Promise((resolve, reject) => {
|
|
1604
|
+
const reader = new FileReader();
|
|
1605
|
+
reader.onload = () => resolve(reader.result);
|
|
1606
|
+
reader.onerror = reject;
|
|
1607
|
+
reader.readAsDataURL(file);
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
// src/components/image-crop-upload.tsx
|
|
1612
|
+
import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1613
|
+
var STORAGE_URL_MAP = {
|
|
1614
|
+
users: "https://gauth-r2-storage.greatlabs.workers.dev",
|
|
1615
|
+
agents: "https://gagents-r2-storage.greatlabs.workers.dev",
|
|
1616
|
+
contacts: "https://gchat-r2-storage.greatlabs.workers.dev",
|
|
1617
|
+
professionals: "https://gclinic-r2-storage.greatlabs.workers.dev",
|
|
1618
|
+
patients: "https://gclinic-r2-storage.greatlabs.workers.dev",
|
|
1619
|
+
clinics: "https://gclinic-r2-storage.greatlabs.workers.dev"
|
|
1620
|
+
};
|
|
1621
|
+
var BUCKET_MAP = {
|
|
1622
|
+
users: "gauth-r1-storage",
|
|
1623
|
+
agents: "gagents-r1-storage",
|
|
1624
|
+
contacts: "gchat-r1-storage",
|
|
1625
|
+
professionals: "gclinic-r1-storage",
|
|
1626
|
+
patients: "gclinic-r1-storage",
|
|
1627
|
+
clinics: "gclinic-r1-storage"
|
|
1628
|
+
};
|
|
1629
|
+
var DEFAULT_STORAGE_URL = "https://gauth-r2-storage.greatlabs.workers.dev";
|
|
1630
|
+
var DEFAULT_BUCKET = "gauth-r1-storage";
|
|
1631
|
+
function ImageCropUpload({
|
|
1632
|
+
value,
|
|
1633
|
+
onChange,
|
|
1634
|
+
onRemove,
|
|
1635
|
+
entityType,
|
|
1636
|
+
entityId,
|
|
1637
|
+
idAccount,
|
|
1638
|
+
name,
|
|
1639
|
+
disabled = false
|
|
1640
|
+
}) {
|
|
1641
|
+
const [imageSrc, setImageSrc] = useState4(null);
|
|
1642
|
+
const [crop, setCrop] = useState4({ x: 0, y: 0 });
|
|
1643
|
+
const [zoom, setZoom] = useState4(1);
|
|
1644
|
+
const [croppedArea, setCroppedArea] = useState4(null);
|
|
1645
|
+
const [isOpen, setIsOpen] = useState4(false);
|
|
1646
|
+
const [isUploading, setIsUploading] = useState4(false);
|
|
1647
|
+
const inputRef = useRef(null);
|
|
1648
|
+
const onCropComplete = useCallback3((_, croppedAreaPixels) => {
|
|
1649
|
+
setCroppedArea(croppedAreaPixels);
|
|
1650
|
+
}, []);
|
|
1651
|
+
const handleFileSelect = useCallback3(
|
|
1652
|
+
async (e) => {
|
|
1653
|
+
const file = e.target.files?.[0];
|
|
1654
|
+
if (!file) return;
|
|
1655
|
+
const error = validateImageFile(file);
|
|
1656
|
+
if (error) {
|
|
1657
|
+
toast.error(error);
|
|
1658
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
1659
|
+
return;
|
|
1660
|
+
}
|
|
1661
|
+
const dataUrl = await fileToDataUrl(file);
|
|
1662
|
+
setImageSrc(dataUrl);
|
|
1663
|
+
setCrop({ x: 0, y: 0 });
|
|
1664
|
+
setZoom(1);
|
|
1665
|
+
setIsOpen(true);
|
|
1666
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
1667
|
+
},
|
|
1668
|
+
[]
|
|
1669
|
+
);
|
|
1670
|
+
const handleConfirmCrop = useCallback3(async () => {
|
|
1671
|
+
if (!imageSrc || !croppedArea) return;
|
|
1672
|
+
setIsUploading(true);
|
|
1673
|
+
try {
|
|
1674
|
+
const base64 = await getCroppedImage(imageSrc, croppedArea);
|
|
1675
|
+
const path = `${entityType}/${idAccount}/${entityId || "temp"}.webp`;
|
|
1676
|
+
const storageUrl = STORAGE_URL_MAP[entityType] || DEFAULT_STORAGE_URL;
|
|
1677
|
+
const bucket = BUCKET_MAP[entityType] || DEFAULT_BUCKET;
|
|
1678
|
+
const res = await fetch(storageUrl, {
|
|
1679
|
+
method: "POST",
|
|
1680
|
+
headers: { "Content-Type": "application/json" },
|
|
1681
|
+
body: JSON.stringify({ file: base64, bucket, path })
|
|
1682
|
+
});
|
|
1683
|
+
const data = await res.json();
|
|
1684
|
+
if (data.status !== 1) {
|
|
1685
|
+
throw new Error(data.message || "Erro ao enviar imagem");
|
|
1686
|
+
}
|
|
1687
|
+
const publicUrl = `${storageUrl}/${path}?t=${Date.now()}`;
|
|
1688
|
+
onChange(publicUrl);
|
|
1689
|
+
toast.success("Imagem atualizada");
|
|
1690
|
+
} catch (err) {
|
|
1691
|
+
toast.error(
|
|
1692
|
+
err instanceof Error ? err.message : "Erro ao enviar imagem"
|
|
1693
|
+
);
|
|
1694
|
+
} finally {
|
|
1695
|
+
setIsUploading(false);
|
|
1696
|
+
setIsOpen(false);
|
|
1697
|
+
setImageSrc(null);
|
|
1698
|
+
}
|
|
1699
|
+
}, [imageSrc, croppedArea, entityType, entityId, idAccount, onChange]);
|
|
1700
|
+
const handleRemove = useCallback3(async () => {
|
|
1701
|
+
if (!value) return;
|
|
1702
|
+
try {
|
|
1703
|
+
const path = `${entityType}/${idAccount}/${entityId || "temp"}.webp`;
|
|
1704
|
+
const storageUrl = STORAGE_URL_MAP[entityType] || DEFAULT_STORAGE_URL;
|
|
1705
|
+
const bucket = BUCKET_MAP[entityType] || DEFAULT_BUCKET;
|
|
1706
|
+
await fetch(storageUrl, {
|
|
1707
|
+
method: "DELETE",
|
|
1708
|
+
headers: { "Content-Type": "application/json" },
|
|
1709
|
+
body: JSON.stringify({ bucket, path })
|
|
1710
|
+
});
|
|
1711
|
+
onRemove?.();
|
|
1712
|
+
toast.success("Imagem removida");
|
|
1713
|
+
} catch {
|
|
1714
|
+
toast.error("Erro ao remover imagem");
|
|
1715
|
+
}
|
|
1716
|
+
}, [value, entityType, entityId, idAccount, onRemove]);
|
|
1717
|
+
return /* @__PURE__ */ jsxs14("div", { className: "flex flex-col items-center gap-2", children: [
|
|
1718
|
+
/* @__PURE__ */ jsxs14("div", { className: "relative group", children: [
|
|
1719
|
+
/* @__PURE__ */ jsx22(EntityAvatar, { photo: value, name: name ?? null, size: "xl" }),
|
|
1720
|
+
!disabled && /* @__PURE__ */ jsx22(
|
|
1721
|
+
"button",
|
|
1722
|
+
{
|
|
1723
|
+
type: "button",
|
|
1724
|
+
onClick: () => inputRef.current?.click(),
|
|
1725
|
+
className: "absolute inset-0 flex items-center justify-center rounded-full bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer",
|
|
1726
|
+
children: /* @__PURE__ */ jsx22(Camera, { className: "h-5 w-5 text-white" })
|
|
1727
|
+
}
|
|
1728
|
+
)
|
|
1729
|
+
] }),
|
|
1730
|
+
/* @__PURE__ */ jsx22(
|
|
1731
|
+
"input",
|
|
1732
|
+
{
|
|
1733
|
+
ref: inputRef,
|
|
1734
|
+
type: "file",
|
|
1735
|
+
accept: "image/png,image/jpeg,image/webp",
|
|
1736
|
+
onChange: handleFileSelect,
|
|
1737
|
+
className: "hidden",
|
|
1738
|
+
disabled
|
|
1739
|
+
}
|
|
1740
|
+
),
|
|
1741
|
+
!disabled && value && /* @__PURE__ */ jsxs14(
|
|
1742
|
+
Button,
|
|
1743
|
+
{
|
|
1744
|
+
type: "button",
|
|
1745
|
+
variant: "ghost",
|
|
1746
|
+
size: "sm",
|
|
1747
|
+
onClick: handleRemove,
|
|
1748
|
+
className: "text-destructive hover:text-destructive",
|
|
1749
|
+
children: [
|
|
1750
|
+
/* @__PURE__ */ jsx22(Trash2, { className: "h-4 w-4 mr-1" }),
|
|
1751
|
+
"Remover"
|
|
1752
|
+
]
|
|
1753
|
+
}
|
|
1754
|
+
),
|
|
1755
|
+
/* @__PURE__ */ jsx22(Dialog, { open: isOpen, onOpenChange: setIsOpen, children: /* @__PURE__ */ jsxs14(DialogContent, { className: "sm:max-w-md", children: [
|
|
1756
|
+
/* @__PURE__ */ jsx22(DialogHeader, { children: /* @__PURE__ */ jsx22(DialogTitle, { children: "Recortar imagem" }) }),
|
|
1757
|
+
/* @__PURE__ */ jsx22("div", { className: "relative w-full h-72 bg-muted rounded-md overflow-hidden", children: imageSrc && /* @__PURE__ */ jsx22(
|
|
1758
|
+
Cropper,
|
|
1759
|
+
{
|
|
1760
|
+
image: imageSrc,
|
|
1761
|
+
crop,
|
|
1762
|
+
zoom,
|
|
1763
|
+
aspect: 1,
|
|
1764
|
+
onCropChange: setCrop,
|
|
1765
|
+
onZoomChange: setZoom,
|
|
1766
|
+
onCropComplete,
|
|
1767
|
+
cropShape: "round",
|
|
1768
|
+
showGrid: false
|
|
1769
|
+
}
|
|
1770
|
+
) }),
|
|
1771
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-3 px-1", children: [
|
|
1772
|
+
/* @__PURE__ */ jsx22("span", { className: "text-sm text-muted-foreground whitespace-nowrap", children: "Zoom" }),
|
|
1773
|
+
/* @__PURE__ */ jsx22(
|
|
1774
|
+
Slider,
|
|
1775
|
+
{
|
|
1776
|
+
value: [zoom],
|
|
1777
|
+
onValueChange: (v) => setZoom(v[0]),
|
|
1778
|
+
min: 1,
|
|
1779
|
+
max: 3,
|
|
1780
|
+
step: 0.1,
|
|
1781
|
+
className: "flex-1"
|
|
1782
|
+
}
|
|
1783
|
+
)
|
|
1784
|
+
] }),
|
|
1785
|
+
/* @__PURE__ */ jsxs14(DialogFooter, { children: [
|
|
1786
|
+
/* @__PURE__ */ jsx22(
|
|
1787
|
+
Button,
|
|
1788
|
+
{
|
|
1789
|
+
variant: "outline",
|
|
1790
|
+
onClick: () => {
|
|
1791
|
+
setIsOpen(false);
|
|
1792
|
+
setImageSrc(null);
|
|
1793
|
+
},
|
|
1794
|
+
disabled: isUploading,
|
|
1795
|
+
children: "Cancelar"
|
|
1796
|
+
}
|
|
1797
|
+
),
|
|
1798
|
+
/* @__PURE__ */ jsx22(
|
|
1799
|
+
Button,
|
|
1800
|
+
{
|
|
1801
|
+
onClick: handleConfirmCrop,
|
|
1802
|
+
disabled: isUploading || !croppedArea,
|
|
1803
|
+
children: isUploading ? "Enviando..." : "Confirmar"
|
|
1804
|
+
}
|
|
1805
|
+
)
|
|
1806
|
+
] })
|
|
1807
|
+
] }) })
|
|
1808
|
+
] });
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
// src/hooks/use-users.ts
|
|
1812
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
1813
|
+
function buildUrl(config, path) {
|
|
1814
|
+
const lang = config.language || "pt-br";
|
|
1815
|
+
const idWl = config.idWl || "1";
|
|
1816
|
+
return `${config.gauthApiUrl}/v1/${lang}/${idWl}/accounts/${config.accountId}/${path}`;
|
|
1817
|
+
}
|
|
1818
|
+
async function request(url, token, options) {
|
|
1819
|
+
const res = await fetch(url, {
|
|
1820
|
+
headers: {
|
|
1821
|
+
"Content-Type": "application/json",
|
|
1822
|
+
Authorization: `Bearer ${token}`
|
|
1823
|
+
},
|
|
1824
|
+
...options
|
|
1825
|
+
});
|
|
1826
|
+
if (!res.ok) {
|
|
1827
|
+
let message = `HTTP ${res.status}`;
|
|
1828
|
+
try {
|
|
1829
|
+
const b = await res.json();
|
|
1830
|
+
message = b.message || message;
|
|
1831
|
+
} catch {
|
|
1832
|
+
}
|
|
1833
|
+
throw new Error(message);
|
|
1834
|
+
}
|
|
1835
|
+
const json = await res.json();
|
|
1836
|
+
if (json.status === 0) throw new Error(json.message || "Opera\xE7\xE3o falhou");
|
|
1837
|
+
return json;
|
|
1838
|
+
}
|
|
1839
|
+
function useUsers(config, params) {
|
|
1840
|
+
return useQuery({
|
|
1841
|
+
queryKey: ["greatauth", "users", config.accountId, params],
|
|
1842
|
+
queryFn: () => {
|
|
1843
|
+
const qs = params ? "?" + new URLSearchParams(params).toString() : "";
|
|
1844
|
+
return request(buildUrl(config, `users${qs}`), config.token);
|
|
1845
|
+
},
|
|
1846
|
+
enabled: !!config.token && !!config.accountId,
|
|
1847
|
+
select: (res) => ({ data: res.data || [], total: res.total || 0 })
|
|
1848
|
+
});
|
|
1849
|
+
}
|
|
1850
|
+
function useCreateUser(config) {
|
|
1851
|
+
const qc = useQueryClient();
|
|
1852
|
+
return useMutation({
|
|
1853
|
+
mutationFn: (data) => request(buildUrl(config, "users"), config.token, {
|
|
1854
|
+
method: "POST",
|
|
1855
|
+
body: JSON.stringify(data)
|
|
1856
|
+
}),
|
|
1857
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ["greatauth", "users"] })
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
function useUpdateUser(config) {
|
|
1861
|
+
const qc = useQueryClient();
|
|
1862
|
+
return useMutation({
|
|
1863
|
+
mutationFn: ({ id, body }) => request(buildUrl(config, `users/${id}`), config.token, {
|
|
1864
|
+
method: "PUT",
|
|
1865
|
+
body: JSON.stringify(body)
|
|
1866
|
+
}),
|
|
1867
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ["greatauth", "users"] })
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1870
|
+
function useDeleteUser(config) {
|
|
1871
|
+
const qc = useQueryClient();
|
|
1872
|
+
return useMutation({
|
|
1873
|
+
mutationFn: (id) => request(buildUrl(config, `users/${id}`), config.token, { method: "DELETE" }),
|
|
1874
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ["greatauth", "users"] })
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
function useResetPassword(config) {
|
|
1878
|
+
const qc = useQueryClient();
|
|
1879
|
+
return useMutation({
|
|
1880
|
+
mutationFn: (id) => request(buildUrl(config, `users/${id}/reset-password`), config.token, { method: "POST" }),
|
|
1881
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ["greatauth", "users"] })
|
|
1882
|
+
});
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
// src/components/users/users-page.tsx
|
|
1886
|
+
import { useMemo as useMemo3, useState as useState7 } from "react";
|
|
1887
|
+
|
|
1888
|
+
// src/components/users/user-form-dialog.tsx
|
|
1889
|
+
import { useEffect as useEffect3, useState as useState5 } from "react";
|
|
1890
|
+
|
|
1891
|
+
// src/components/ui/select.tsx
|
|
1892
|
+
import { Select as SelectPrimitive } from "radix-ui";
|
|
1893
|
+
import { ChevronsUpDown, Check as Check2, ChevronUp as ChevronUp2, ChevronDown } from "lucide-react";
|
|
1894
|
+
import { jsx as jsx23, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1895
|
+
function Select({
|
|
1896
|
+
...props
|
|
1897
|
+
}) {
|
|
1898
|
+
return /* @__PURE__ */ jsx23(SelectPrimitive.Root, { "data-slot": "select", ...props });
|
|
1899
|
+
}
|
|
1900
|
+
function SelectValue({
|
|
1901
|
+
...props
|
|
1902
|
+
}) {
|
|
1903
|
+
return /* @__PURE__ */ jsx23(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
|
|
1904
|
+
}
|
|
1905
|
+
function SelectTrigger({
|
|
1906
|
+
className,
|
|
1907
|
+
size = "default",
|
|
1908
|
+
children,
|
|
1909
|
+
...props
|
|
1910
|
+
}) {
|
|
1911
|
+
return /* @__PURE__ */ jsxs15(
|
|
1912
|
+
SelectPrimitive.Trigger,
|
|
1913
|
+
{
|
|
1914
|
+
"data-slot": "select-trigger",
|
|
1915
|
+
"data-size": size,
|
|
1916
|
+
className: cn(
|
|
1917
|
+
"border-input data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-md border bg-transparent py-2 pr-2 pl-2.5 text-sm shadow-xs transition-[color,box-shadow] focus-visible:ring-3 aria-invalid:ring-3 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*='size-'])]:size-4 flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
1918
|
+
className
|
|
1919
|
+
),
|
|
1920
|
+
...props,
|
|
1921
|
+
children: [
|
|
1922
|
+
children,
|
|
1923
|
+
/* @__PURE__ */ jsx23(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx23(ChevronsUpDown, { className: "text-muted-foreground size-4 pointer-events-none" }) })
|
|
1924
|
+
]
|
|
1925
|
+
}
|
|
1926
|
+
);
|
|
1927
|
+
}
|
|
1928
|
+
function SelectContent({
|
|
1929
|
+
className,
|
|
1930
|
+
children,
|
|
1931
|
+
position = "item-aligned",
|
|
1932
|
+
align = "center",
|
|
1933
|
+
...props
|
|
1934
|
+
}) {
|
|
1935
|
+
return /* @__PURE__ */ jsx23(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs15(
|
|
1936
|
+
SelectPrimitive.Content,
|
|
1937
|
+
{
|
|
1938
|
+
"data-slot": "select-content",
|
|
1939
|
+
"data-align-trigger": position === "item-aligned",
|
|
1940
|
+
className: cn("bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-36 rounded-md shadow-md ring-1 duration-100 relative z-50 max-h-(--radix-select-content-available-height) origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto data-[align-trigger=true]:animate-none", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className),
|
|
1941
|
+
position,
|
|
1942
|
+
align,
|
|
1943
|
+
...props,
|
|
1944
|
+
children: [
|
|
1945
|
+
/* @__PURE__ */ jsx23(SelectScrollUpButton, {}),
|
|
1946
|
+
/* @__PURE__ */ jsx23(
|
|
1947
|
+
SelectPrimitive.Viewport,
|
|
1948
|
+
{
|
|
1949
|
+
"data-position": position,
|
|
1950
|
+
className: cn(
|
|
1951
|
+
"data-[position=popper]:h-(--radix-select-trigger-height) data-[position=popper]:w-full data-[position=popper]:min-w-(--radix-select-trigger-width)",
|
|
1952
|
+
position === "popper" && ""
|
|
1953
|
+
),
|
|
1954
|
+
children
|
|
1955
|
+
}
|
|
1956
|
+
),
|
|
1957
|
+
/* @__PURE__ */ jsx23(SelectScrollDownButton, {})
|
|
1958
|
+
]
|
|
1959
|
+
}
|
|
1960
|
+
) });
|
|
1961
|
+
}
|
|
1962
|
+
function SelectItem({
|
|
1963
|
+
className,
|
|
1964
|
+
children,
|
|
1965
|
+
...props
|
|
1966
|
+
}) {
|
|
1967
|
+
return /* @__PURE__ */ jsxs15(
|
|
1968
|
+
SelectPrimitive.Item,
|
|
1969
|
+
{
|
|
1970
|
+
"data-slot": "select-item",
|
|
1971
|
+
className: cn(
|
|
1972
|
+
"focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
1973
|
+
className
|
|
1974
|
+
),
|
|
1975
|
+
...props,
|
|
1976
|
+
children: [
|
|
1977
|
+
/* @__PURE__ */ jsx23("span", { className: "pointer-events-none absolute right-2 flex size-4 items-center justify-center", children: /* @__PURE__ */ jsx23(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx23(Check2, { className: "pointer-events-none" }) }) }),
|
|
1978
|
+
/* @__PURE__ */ jsx23(SelectPrimitive.ItemText, { children })
|
|
1979
|
+
]
|
|
1980
|
+
}
|
|
1981
|
+
);
|
|
1982
|
+
}
|
|
1983
|
+
function SelectScrollUpButton({
|
|
1984
|
+
className,
|
|
1985
|
+
...props
|
|
1986
|
+
}) {
|
|
1987
|
+
return /* @__PURE__ */ jsx23(
|
|
1988
|
+
SelectPrimitive.ScrollUpButton,
|
|
1989
|
+
{
|
|
1990
|
+
"data-slot": "select-scroll-up-button",
|
|
1991
|
+
className: cn("bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4", className),
|
|
1992
|
+
...props,
|
|
1993
|
+
children: /* @__PURE__ */ jsx23(ChevronUp2, {})
|
|
1994
|
+
}
|
|
1995
|
+
);
|
|
1996
|
+
}
|
|
1997
|
+
function SelectScrollDownButton({
|
|
1998
|
+
className,
|
|
1999
|
+
...props
|
|
2000
|
+
}) {
|
|
2001
|
+
return /* @__PURE__ */ jsx23(
|
|
2002
|
+
SelectPrimitive.ScrollDownButton,
|
|
2003
|
+
{
|
|
2004
|
+
"data-slot": "select-scroll-down-button",
|
|
2005
|
+
className: cn("bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4", className),
|
|
2006
|
+
...props,
|
|
2007
|
+
children: /* @__PURE__ */ jsx23(ChevronDown, {})
|
|
2008
|
+
}
|
|
2009
|
+
);
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
// src/components/users/user-form-dialog.tsx
|
|
2013
|
+
import { Loader2 as Loader22 } from "lucide-react";
|
|
2014
|
+
import { toast as toast2 } from "sonner";
|
|
2015
|
+
import { jsx as jsx24, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2016
|
+
var DEFAULT_PROFILES = [
|
|
2017
|
+
{ value: "owner", label: "Propriet\xE1rio" },
|
|
2018
|
+
{ value: "admin", label: "Administrador" },
|
|
2019
|
+
{ value: "collaborator", label: "Colaborador" },
|
|
2020
|
+
{ value: "attendant", label: "Atendente" },
|
|
2021
|
+
{ value: "viewer", label: "Visualizador" }
|
|
2022
|
+
];
|
|
2023
|
+
function UserFormDialog({
|
|
2024
|
+
open,
|
|
2025
|
+
onOpenChange,
|
|
2026
|
+
user,
|
|
2027
|
+
config,
|
|
2028
|
+
profileOptions,
|
|
2029
|
+
renderPhones
|
|
2030
|
+
}) {
|
|
2031
|
+
const isEditing = !!user;
|
|
2032
|
+
const createUser = useCreateUser(config);
|
|
2033
|
+
const updateUser = useUpdateUser(config);
|
|
2034
|
+
const profiles = profileOptions || DEFAULT_PROFILES;
|
|
2035
|
+
const [name, setName] = useState5("");
|
|
2036
|
+
const [lastName, setLastName] = useState5("");
|
|
2037
|
+
const [email, setEmail] = useState5("");
|
|
2038
|
+
const [profile, setProfile] = useState5("collaborator");
|
|
2039
|
+
const [password, setPassword] = useState5("");
|
|
2040
|
+
const [photo, setPhoto] = useState5("");
|
|
2041
|
+
useEffect3(() => {
|
|
2042
|
+
if (user) {
|
|
2043
|
+
setName(user.name || "");
|
|
2044
|
+
setLastName(user.last_name || "");
|
|
2045
|
+
setEmail(user.email || "");
|
|
2046
|
+
setProfile(user.profile || "collaborator");
|
|
2047
|
+
setPhoto(user.photo || "");
|
|
2048
|
+
} else {
|
|
2049
|
+
setName("");
|
|
2050
|
+
setLastName("");
|
|
2051
|
+
setEmail("");
|
|
2052
|
+
setProfile("collaborator");
|
|
2053
|
+
setPhoto("");
|
|
2054
|
+
}
|
|
2055
|
+
setPassword("");
|
|
2056
|
+
}, [user, open]);
|
|
2057
|
+
const isPending = createUser.isPending || updateUser.isPending;
|
|
2058
|
+
async function handleSubmit(e) {
|
|
2059
|
+
e.preventDefault();
|
|
2060
|
+
if (!name.trim() || !email.trim()) return;
|
|
2061
|
+
try {
|
|
2062
|
+
if (isEditing) {
|
|
2063
|
+
await updateUser.mutateAsync({
|
|
2064
|
+
id: user.id,
|
|
2065
|
+
body: {
|
|
2066
|
+
name: name.trim(),
|
|
2067
|
+
last_name: lastName.trim() || void 0,
|
|
2068
|
+
email: email.trim(),
|
|
2069
|
+
profile,
|
|
2070
|
+
photo: photo || null
|
|
2071
|
+
}
|
|
2072
|
+
});
|
|
2073
|
+
toast2.success("Usu\xE1rio atualizado");
|
|
2074
|
+
} else {
|
|
2075
|
+
await createUser.mutateAsync({
|
|
2076
|
+
name: name.trim(),
|
|
2077
|
+
last_name: lastName.trim() || void 0,
|
|
2078
|
+
email: email.trim(),
|
|
2079
|
+
profile,
|
|
2080
|
+
password: password || void 0,
|
|
2081
|
+
photo: photo || null
|
|
2082
|
+
});
|
|
2083
|
+
toast2.success("Usu\xE1rio criado");
|
|
2084
|
+
}
|
|
2085
|
+
onOpenChange(false);
|
|
2086
|
+
} catch {
|
|
2087
|
+
toast2.error(isEditing ? "Erro ao atualizar usu\xE1rio" : "Erro ao criar usu\xE1rio");
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
return /* @__PURE__ */ jsx24(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs16(DialogContent, { children: [
|
|
2091
|
+
/* @__PURE__ */ jsx24(DialogHeader, { children: /* @__PURE__ */ jsx24(DialogTitle, { children: isEditing ? "Editar Usu\xE1rio" : "Novo Usu\xE1rio" }) }),
|
|
2092
|
+
/* @__PURE__ */ jsxs16("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
|
|
2093
|
+
/* @__PURE__ */ jsx24("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx24(
|
|
2094
|
+
ImageCropUpload,
|
|
2095
|
+
{
|
|
2096
|
+
value: photo || null,
|
|
2097
|
+
onChange: setPhoto,
|
|
2098
|
+
onRemove: () => setPhoto(""),
|
|
2099
|
+
entityType: "users",
|
|
2100
|
+
entityId: user?.id,
|
|
2101
|
+
idAccount: Number(config.accountId) || 0,
|
|
2102
|
+
name: `${name} ${lastName}`.trim() || null,
|
|
2103
|
+
disabled: isPending
|
|
2104
|
+
}
|
|
2105
|
+
) }),
|
|
2106
|
+
/* @__PURE__ */ jsxs16("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2107
|
+
/* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
|
|
2108
|
+
/* @__PURE__ */ jsx24(Label, { htmlFor: "user-name", children: "Nome *" }),
|
|
2109
|
+
/* @__PURE__ */ jsx24(Input, { id: "user-name", value: name, onChange: (e) => setName(e.target.value), placeholder: "Nome", required: true, disabled: isPending })
|
|
2110
|
+
] }),
|
|
2111
|
+
/* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
|
|
2112
|
+
/* @__PURE__ */ jsx24(Label, { htmlFor: "user-lastname", children: "Sobrenome" }),
|
|
2113
|
+
/* @__PURE__ */ jsx24(Input, { id: "user-lastname", value: lastName, onChange: (e) => setLastName(e.target.value), placeholder: "Sobrenome", disabled: isPending })
|
|
2114
|
+
] })
|
|
2115
|
+
] }),
|
|
2116
|
+
/* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
|
|
2117
|
+
/* @__PURE__ */ jsx24(Label, { htmlFor: "user-email", children: "E-mail *" }),
|
|
2118
|
+
/* @__PURE__ */ jsx24(Input, { id: "user-email", type: "email", value: email, onChange: (e) => setEmail(e.target.value), placeholder: "email@exemplo.com", required: true, disabled: isPending })
|
|
2119
|
+
] }),
|
|
2120
|
+
!isEditing && /* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
|
|
2121
|
+
/* @__PURE__ */ jsx24(Label, { htmlFor: "user-password", children: "Senha" }),
|
|
2122
|
+
/* @__PURE__ */ jsx24(Input, { id: "user-password", type: "password", value: password, onChange: (e) => setPassword(e.target.value), placeholder: "Senha inicial (opcional)", disabled: isPending })
|
|
2123
|
+
] }),
|
|
2124
|
+
/* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
|
|
2125
|
+
/* @__PURE__ */ jsx24(Label, { children: "Perfil de acesso" }),
|
|
2126
|
+
/* @__PURE__ */ jsxs16(Select, { value: profile, onValueChange: setProfile, disabled: isPending, children: [
|
|
2127
|
+
/* @__PURE__ */ jsx24(SelectTrigger, { className: "w-full", children: /* @__PURE__ */ jsx24(SelectValue, {}) }),
|
|
2128
|
+
/* @__PURE__ */ jsx24(SelectContent, { children: profiles.map((opt) => /* @__PURE__ */ jsx24(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })
|
|
2129
|
+
] })
|
|
2130
|
+
] }),
|
|
2131
|
+
isEditing && renderPhones && /* @__PURE__ */ jsx24("div", { className: "border-t pt-4", children: renderPhones(user.id) }),
|
|
2132
|
+
/* @__PURE__ */ jsxs16(DialogFooter, { children: [
|
|
2133
|
+
/* @__PURE__ */ jsx24(Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), disabled: isPending, children: "Cancelar" }),
|
|
2134
|
+
/* @__PURE__ */ jsxs16(Button, { type: "submit", disabled: isPending || !name.trim() || !email.trim(), children: [
|
|
2135
|
+
isPending && /* @__PURE__ */ jsx24(Loader22, { className: "mr-2 h-4 w-4 animate-spin" }),
|
|
2136
|
+
isEditing ? "Salvar" : "Criar"
|
|
2137
|
+
] })
|
|
2138
|
+
] })
|
|
2139
|
+
] })
|
|
2140
|
+
] }) });
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
// src/components/users/user-profile-badge.tsx
|
|
2144
|
+
import { jsx as jsx25 } from "react/jsx-runtime";
|
|
2145
|
+
var profileConfig = {
|
|
2146
|
+
owner: { label: "Propriet\xE1rio", className: "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200" },
|
|
2147
|
+
proprietario: { label: "Propriet\xE1rio", className: "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200" },
|
|
2148
|
+
admin: { label: "Administrador", className: "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200" },
|
|
2149
|
+
administrador: { label: "Administrador", className: "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200" },
|
|
2150
|
+
manager: { label: "Gerente", className: "bg-teal-100 text-teal-800 dark:bg-teal-900 dark:text-teal-200" },
|
|
2151
|
+
gerente: { label: "Gerente", className: "bg-teal-100 text-teal-800 dark:bg-teal-900 dark:text-teal-200" },
|
|
2152
|
+
supervisor: { label: "Supervisor", className: "bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200" },
|
|
2153
|
+
collaborator: { label: "Colaborador", className: "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200" },
|
|
2154
|
+
colaborador: { label: "Colaborador", className: "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200" },
|
|
2155
|
+
attendant: { label: "Atendente", className: "bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200" },
|
|
2156
|
+
viewer: { label: "Visualizador", className: "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200" }
|
|
2157
|
+
};
|
|
2158
|
+
function UserProfileBadge({ profile }) {
|
|
2159
|
+
const config = profileConfig[profile] || { label: profile, className: "" };
|
|
2160
|
+
return /* @__PURE__ */ jsx25(Badge, { variant: "secondary", className: config.className, children: config.label });
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
// src/components/users/data-table.tsx
|
|
2164
|
+
import {
|
|
2165
|
+
flexRender,
|
|
2166
|
+
getCoreRowModel,
|
|
2167
|
+
getSortedRowModel,
|
|
2168
|
+
useReactTable
|
|
2169
|
+
} from "@tanstack/react-table";
|
|
2170
|
+
import { useState as useState6 } from "react";
|
|
2171
|
+
|
|
2172
|
+
// src/components/ui/table.tsx
|
|
2173
|
+
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
2174
|
+
function Table({ className, ...props }) {
|
|
2175
|
+
return /* @__PURE__ */ jsx26("div", { "data-slot": "table-container", className: "relative w-full overflow-x-auto", children: /* @__PURE__ */ jsx26(
|
|
2176
|
+
"table",
|
|
2177
|
+
{
|
|
2178
|
+
"data-slot": "table",
|
|
2179
|
+
className: cn("w-full caption-bottom text-sm", className),
|
|
2180
|
+
...props
|
|
2181
|
+
}
|
|
2182
|
+
) });
|
|
2183
|
+
}
|
|
2184
|
+
function TableHeader({ className, ...props }) {
|
|
2185
|
+
return /* @__PURE__ */ jsx26(
|
|
2186
|
+
"thead",
|
|
2187
|
+
{
|
|
2188
|
+
"data-slot": "table-header",
|
|
2189
|
+
className: cn("[&_tr]:border-b", className),
|
|
2190
|
+
...props
|
|
2191
|
+
}
|
|
2192
|
+
);
|
|
2193
|
+
}
|
|
2194
|
+
function TableBody({ className, ...props }) {
|
|
2195
|
+
return /* @__PURE__ */ jsx26(
|
|
2196
|
+
"tbody",
|
|
2197
|
+
{
|
|
2198
|
+
"data-slot": "table-body",
|
|
2199
|
+
className: cn("[&_tr:last-child]:border-0", className),
|
|
2200
|
+
...props
|
|
2201
|
+
}
|
|
2202
|
+
);
|
|
2203
|
+
}
|
|
2204
|
+
function TableRow({ className, ...props }) {
|
|
2205
|
+
return /* @__PURE__ */ jsx26(
|
|
2206
|
+
"tr",
|
|
2207
|
+
{
|
|
2208
|
+
"data-slot": "table-row",
|
|
2209
|
+
className: cn("hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", className),
|
|
2210
|
+
...props
|
|
2211
|
+
}
|
|
2212
|
+
);
|
|
2213
|
+
}
|
|
2214
|
+
function TableHead({ className, ...props }) {
|
|
2215
|
+
return /* @__PURE__ */ jsx26(
|
|
2216
|
+
"th",
|
|
2217
|
+
{
|
|
2218
|
+
"data-slot": "table-head",
|
|
2219
|
+
className: cn("text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0", className),
|
|
2220
|
+
...props
|
|
2221
|
+
}
|
|
2222
|
+
);
|
|
2223
|
+
}
|
|
2224
|
+
function TableCell({ className, ...props }) {
|
|
2225
|
+
return /* @__PURE__ */ jsx26(
|
|
2226
|
+
"td",
|
|
2227
|
+
{
|
|
2228
|
+
"data-slot": "table-cell",
|
|
2229
|
+
className: cn("p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0", className),
|
|
2230
|
+
...props
|
|
2231
|
+
}
|
|
2232
|
+
);
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
// src/components/users/data-table.tsx
|
|
2236
|
+
import { ChevronLeft, ChevronRight as ChevronRight4, ArrowUp, ArrowDown, ArrowUpDown } from "lucide-react";
|
|
2237
|
+
import { jsx as jsx27, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2238
|
+
function DataTable({
|
|
2239
|
+
columns,
|
|
2240
|
+
data,
|
|
2241
|
+
isLoading,
|
|
2242
|
+
emptyMessage = "Nenhum registro encontrado",
|
|
2243
|
+
total,
|
|
2244
|
+
page = 1,
|
|
2245
|
+
onPageChange,
|
|
2246
|
+
pageSize = 15,
|
|
2247
|
+
onRowClick,
|
|
2248
|
+
selectedRowId,
|
|
2249
|
+
getRowId,
|
|
2250
|
+
compact
|
|
2251
|
+
}) {
|
|
2252
|
+
const [sorting, setSorting] = useState6([]);
|
|
2253
|
+
const table = useReactTable({
|
|
2254
|
+
data,
|
|
2255
|
+
columns,
|
|
2256
|
+
getCoreRowModel: getCoreRowModel(),
|
|
2257
|
+
getSortedRowModel: getSortedRowModel(),
|
|
2258
|
+
onSortingChange: setSorting,
|
|
2259
|
+
state: { sorting }
|
|
2260
|
+
});
|
|
2261
|
+
const totalPages = total ? Math.ceil(total / pageSize) : 1;
|
|
2262
|
+
const showPagination = onPageChange && total && total > pageSize;
|
|
2263
|
+
return /* @__PURE__ */ jsxs17("div", { className: "space-y-4", children: [
|
|
2264
|
+
/* @__PURE__ */ jsx27("div", { className: "rounded-md border", children: /* @__PURE__ */ jsxs17(Table, { children: [
|
|
2265
|
+
/* @__PURE__ */ jsx27(TableHeader, { children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx27(TableRow, { children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx27(
|
|
2266
|
+
TableHead,
|
|
2267
|
+
{
|
|
2268
|
+
className: cn(compact && "py-1.5 text-xs"),
|
|
2269
|
+
style: { width: header.getSize() !== 150 ? header.getSize() : void 0 },
|
|
2270
|
+
children: header.isPlaceholder ? null : header.column.getCanSort() ? /* @__PURE__ */ jsxs17(
|
|
2271
|
+
"button",
|
|
2272
|
+
{
|
|
2273
|
+
type: "button",
|
|
2274
|
+
className: "flex items-center gap-1 hover:text-foreground -ml-1 px-1 py-0.5 rounded cursor-pointer select-none",
|
|
2275
|
+
onClick: header.column.getToggleSortingHandler(),
|
|
2276
|
+
children: [
|
|
2277
|
+
flexRender(header.column.columnDef.header, header.getContext()),
|
|
2278
|
+
{
|
|
2279
|
+
asc: /* @__PURE__ */ jsx27(ArrowUp, { className: "h-3.5 w-3.5" }),
|
|
2280
|
+
desc: /* @__PURE__ */ jsx27(ArrowDown, { className: "h-3.5 w-3.5" })
|
|
2281
|
+
}[header.column.getIsSorted()] ?? /* @__PURE__ */ jsx27(ArrowUpDown, { className: "h-3.5 w-3.5 text-muted-foreground/50" })
|
|
2282
|
+
]
|
|
2283
|
+
}
|
|
2284
|
+
) : flexRender(header.column.columnDef.header, header.getContext())
|
|
2285
|
+
},
|
|
2286
|
+
header.id
|
|
2287
|
+
)) }, headerGroup.id)) }),
|
|
2288
|
+
/* @__PURE__ */ jsx27(TableBody, { children: isLoading ? Array.from({ length: compact ? 3 : 5 }).map((_, i) => /* @__PURE__ */ jsx27(TableRow, { children: columns.map((_2, j) => /* @__PURE__ */ jsx27(TableCell, { className: cn(compact && "py-1.5"), children: /* @__PURE__ */ jsx27(Skeleton, { className: "h-4 w-full max-w-[120px]" }) }, j)) }, i)) : table.getRowModel().rows.length === 0 ? /* @__PURE__ */ jsx27(TableRow, { children: /* @__PURE__ */ jsx27(
|
|
2289
|
+
TableCell,
|
|
2290
|
+
{
|
|
2291
|
+
colSpan: columns.length,
|
|
2292
|
+
className: cn(
|
|
2293
|
+
"text-center text-muted-foreground",
|
|
2294
|
+
compact ? "py-4" : "py-8"
|
|
2295
|
+
),
|
|
2296
|
+
children: emptyMessage
|
|
2297
|
+
}
|
|
2298
|
+
) }) : table.getRowModel().rows.map((row) => {
|
|
2299
|
+
const rowId = getRowId ? getRowId(row.original) : void 0;
|
|
2300
|
+
const isSelected = selectedRowId != null && rowId != null && rowId === selectedRowId;
|
|
2301
|
+
return /* @__PURE__ */ jsx27(
|
|
2302
|
+
TableRow,
|
|
2303
|
+
{
|
|
2304
|
+
className: cn(
|
|
2305
|
+
onRowClick && "cursor-pointer",
|
|
2306
|
+
isSelected && "bg-accent"
|
|
2307
|
+
),
|
|
2308
|
+
onClick: () => onRowClick?.(row.original),
|
|
2309
|
+
children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx27(TableCell, { className: cn(compact && "py-1.5"), children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))
|
|
2310
|
+
},
|
|
2311
|
+
row.id
|
|
2312
|
+
);
|
|
2313
|
+
}) })
|
|
2314
|
+
] }) }),
|
|
2315
|
+
showPagination && /* @__PURE__ */ jsxs17("div", { className: "flex items-center justify-between px-2", children: [
|
|
2316
|
+
/* @__PURE__ */ jsxs17("p", { className: "text-sm text-muted-foreground", children: [
|
|
2317
|
+
total,
|
|
2318
|
+
" registro",
|
|
2319
|
+
total !== 1 ? "s" : ""
|
|
2320
|
+
] }),
|
|
2321
|
+
/* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", children: [
|
|
2322
|
+
/* @__PURE__ */ jsxs17(
|
|
2323
|
+
Button,
|
|
2324
|
+
{
|
|
2325
|
+
variant: "outline",
|
|
2326
|
+
size: "sm",
|
|
2327
|
+
onClick: () => onPageChange(page - 1),
|
|
2328
|
+
disabled: page <= 1,
|
|
2329
|
+
children: [
|
|
2330
|
+
/* @__PURE__ */ jsx27(ChevronLeft, { className: "h-4 w-4" }),
|
|
2331
|
+
"Anterior"
|
|
2332
|
+
]
|
|
2333
|
+
}
|
|
2334
|
+
),
|
|
2335
|
+
/* @__PURE__ */ jsxs17("span", { className: "text-sm text-muted-foreground", children: [
|
|
2336
|
+
page,
|
|
2337
|
+
" de ",
|
|
2338
|
+
totalPages
|
|
2339
|
+
] }),
|
|
2340
|
+
/* @__PURE__ */ jsxs17(
|
|
2341
|
+
Button,
|
|
2342
|
+
{
|
|
2343
|
+
variant: "outline",
|
|
2344
|
+
size: "sm",
|
|
2345
|
+
onClick: () => onPageChange(page + 1),
|
|
2346
|
+
disabled: page >= totalPages,
|
|
2347
|
+
children: [
|
|
2348
|
+
"Pr\xF3ximo",
|
|
2349
|
+
/* @__PURE__ */ jsx27(ChevronRight4, { className: "h-4 w-4" })
|
|
2350
|
+
]
|
|
2351
|
+
}
|
|
2352
|
+
)
|
|
2353
|
+
] })
|
|
2354
|
+
] })
|
|
2355
|
+
] });
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
// src/components/ui/alert-dialog.tsx
|
|
2359
|
+
import { AlertDialog as AlertDialogPrimitive } from "radix-ui";
|
|
2360
|
+
import { jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2361
|
+
function AlertDialog({
|
|
2362
|
+
...props
|
|
2363
|
+
}) {
|
|
2364
|
+
return /* @__PURE__ */ jsx28(AlertDialogPrimitive.Root, { "data-slot": "alert-dialog", ...props });
|
|
2365
|
+
}
|
|
2366
|
+
function AlertDialogPortal({
|
|
2367
|
+
...props
|
|
2368
|
+
}) {
|
|
2369
|
+
return /* @__PURE__ */ jsx28(AlertDialogPrimitive.Portal, { "data-slot": "alert-dialog-portal", ...props });
|
|
2370
|
+
}
|
|
2371
|
+
function AlertDialogOverlay({
|
|
2372
|
+
className,
|
|
2373
|
+
...props
|
|
2374
|
+
}) {
|
|
2375
|
+
return /* @__PURE__ */ jsx28(
|
|
2376
|
+
AlertDialogPrimitive.Overlay,
|
|
2377
|
+
{
|
|
2378
|
+
"data-slot": "alert-dialog-overlay",
|
|
2379
|
+
className: cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 z-50", className),
|
|
2380
|
+
...props
|
|
2381
|
+
}
|
|
2382
|
+
);
|
|
2383
|
+
}
|
|
2384
|
+
function AlertDialogContent({
|
|
2385
|
+
className,
|
|
2386
|
+
size = "default",
|
|
2387
|
+
...props
|
|
2388
|
+
}) {
|
|
2389
|
+
return /* @__PURE__ */ jsxs18(AlertDialogPortal, { children: [
|
|
2390
|
+
/* @__PURE__ */ jsx28(AlertDialogOverlay, {}),
|
|
2391
|
+
/* @__PURE__ */ jsx28(
|
|
2392
|
+
AlertDialogPrimitive.Content,
|
|
2393
|
+
{
|
|
2394
|
+
"data-slot": "alert-dialog-content",
|
|
2395
|
+
"data-size": size,
|
|
2396
|
+
className: cn(
|
|
2397
|
+
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 gap-6 rounded-xl p-6 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none",
|
|
2398
|
+
className
|
|
2399
|
+
),
|
|
2400
|
+
...props
|
|
2401
|
+
}
|
|
2402
|
+
)
|
|
2403
|
+
] });
|
|
2404
|
+
}
|
|
2405
|
+
function AlertDialogHeader({
|
|
2406
|
+
className,
|
|
2407
|
+
...props
|
|
2408
|
+
}) {
|
|
2409
|
+
return /* @__PURE__ */ jsx28(
|
|
2410
|
+
"div",
|
|
2411
|
+
{
|
|
2412
|
+
"data-slot": "alert-dialog-header",
|
|
2413
|
+
className: cn("grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-6 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]", className),
|
|
2414
|
+
...props
|
|
2415
|
+
}
|
|
2416
|
+
);
|
|
2417
|
+
}
|
|
2418
|
+
function AlertDialogFooter({
|
|
2419
|
+
className,
|
|
2420
|
+
...props
|
|
2421
|
+
}) {
|
|
2422
|
+
return /* @__PURE__ */ jsx28(
|
|
2423
|
+
"div",
|
|
2424
|
+
{
|
|
2425
|
+
"data-slot": "alert-dialog-footer",
|
|
2426
|
+
className: cn(
|
|
2427
|
+
"flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
|
|
2428
|
+
className
|
|
2429
|
+
),
|
|
2430
|
+
...props
|
|
2431
|
+
}
|
|
2432
|
+
);
|
|
2433
|
+
}
|
|
2434
|
+
function AlertDialogTitle({
|
|
2435
|
+
className,
|
|
2436
|
+
...props
|
|
2437
|
+
}) {
|
|
2438
|
+
return /* @__PURE__ */ jsx28(
|
|
2439
|
+
AlertDialogPrimitive.Title,
|
|
2440
|
+
{
|
|
2441
|
+
"data-slot": "alert-dialog-title",
|
|
2442
|
+
className: cn("text-lg font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2", className),
|
|
2443
|
+
...props
|
|
2444
|
+
}
|
|
2445
|
+
);
|
|
2446
|
+
}
|
|
2447
|
+
function AlertDialogDescription({
|
|
2448
|
+
className,
|
|
2449
|
+
...props
|
|
2450
|
+
}) {
|
|
2451
|
+
return /* @__PURE__ */ jsx28(
|
|
2452
|
+
AlertDialogPrimitive.Description,
|
|
2453
|
+
{
|
|
2454
|
+
"data-slot": "alert-dialog-description",
|
|
2455
|
+
className: cn("text-muted-foreground *:[a]:hover:text-foreground text-sm text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3", className),
|
|
2456
|
+
...props
|
|
2457
|
+
}
|
|
2458
|
+
);
|
|
2459
|
+
}
|
|
2460
|
+
function AlertDialogAction({
|
|
2461
|
+
className,
|
|
2462
|
+
variant = "default",
|
|
2463
|
+
size = "default",
|
|
2464
|
+
...props
|
|
2465
|
+
}) {
|
|
2466
|
+
return /* @__PURE__ */ jsx28(Button, { variant, size, asChild: true, children: /* @__PURE__ */ jsx28(
|
|
2467
|
+
AlertDialogPrimitive.Action,
|
|
2468
|
+
{
|
|
2469
|
+
"data-slot": "alert-dialog-action",
|
|
2470
|
+
className: cn(className),
|
|
2471
|
+
...props
|
|
2472
|
+
}
|
|
2473
|
+
) });
|
|
2474
|
+
}
|
|
2475
|
+
function AlertDialogCancel({
|
|
2476
|
+
className,
|
|
2477
|
+
variant = "outline",
|
|
2478
|
+
size = "default",
|
|
2479
|
+
...props
|
|
2480
|
+
}) {
|
|
2481
|
+
return /* @__PURE__ */ jsx28(Button, { variant, size, asChild: true, children: /* @__PURE__ */ jsx28(
|
|
2482
|
+
AlertDialogPrimitive.Cancel,
|
|
2483
|
+
{
|
|
2484
|
+
"data-slot": "alert-dialog-cancel",
|
|
2485
|
+
className: cn(className),
|
|
2486
|
+
...props
|
|
2487
|
+
}
|
|
2488
|
+
) });
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
// src/components/users/users-page.tsx
|
|
2492
|
+
import { Pencil, Trash2 as Trash22, Search, Forward, Plus } from "lucide-react";
|
|
2493
|
+
import { format } from "date-fns";
|
|
2494
|
+
import { ptBR } from "date-fns/locale";
|
|
2495
|
+
import { toast as toast3 } from "sonner";
|
|
2496
|
+
import { jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
2497
|
+
var PAGE_SIZE = 15;
|
|
2498
|
+
var PROFILE_FILTER_OPTIONS = [
|
|
2499
|
+
{ value: "all", label: "Todos os perfis" },
|
|
2500
|
+
{ value: "owner", label: "Propriet\xE1rio" },
|
|
2501
|
+
{ value: "admin", label: "Administrador" },
|
|
2502
|
+
{ value: "collaborator", label: "Colaborador" },
|
|
2503
|
+
{ value: "attendant", label: "Atendente" },
|
|
2504
|
+
{ value: "viewer", label: "Visualizador" }
|
|
2505
|
+
];
|
|
2506
|
+
function useColumns(onEdit, onDelete, onResetPassword) {
|
|
2507
|
+
return [
|
|
2508
|
+
{
|
|
2509
|
+
accessorKey: "name",
|
|
2510
|
+
header: "Nome",
|
|
2511
|
+
cell: ({ row }) => /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-2", children: [
|
|
2512
|
+
/* @__PURE__ */ jsx29(
|
|
2513
|
+
EntityAvatar,
|
|
2514
|
+
{
|
|
2515
|
+
photo: row.original.photo,
|
|
2516
|
+
name: `${row.original.name} ${row.original.last_name}`.trim(),
|
|
2517
|
+
size: "sm"
|
|
2518
|
+
}
|
|
2519
|
+
),
|
|
2520
|
+
/* @__PURE__ */ jsxs19("span", { className: "font-medium", children: [
|
|
2521
|
+
row.original.name,
|
|
2522
|
+
" ",
|
|
2523
|
+
row.original.last_name
|
|
2524
|
+
] })
|
|
2525
|
+
] }),
|
|
2526
|
+
sortingFn: (rowA, rowB) => {
|
|
2527
|
+
const a = `${rowA.original.name} ${rowA.original.last_name}`.toLowerCase();
|
|
2528
|
+
const b = `${rowB.original.name} ${rowB.original.last_name}`.toLowerCase();
|
|
2529
|
+
return a.localeCompare(b);
|
|
2530
|
+
}
|
|
2531
|
+
},
|
|
2532
|
+
{
|
|
2533
|
+
accessorKey: "email",
|
|
2534
|
+
header: "E-mail"
|
|
2535
|
+
},
|
|
2536
|
+
{
|
|
2537
|
+
accessorKey: "profile",
|
|
2538
|
+
header: "Perfil",
|
|
2539
|
+
cell: ({ row }) => /* @__PURE__ */ jsx29(UserProfileBadge, { profile: row.original.profile }),
|
|
2540
|
+
sortingFn: (rowA, rowB) => rowA.original.profile.localeCompare(rowB.original.profile)
|
|
2541
|
+
},
|
|
2542
|
+
{
|
|
2543
|
+
accessorKey: "last_login",
|
|
2544
|
+
header: "\xDAltimo acesso",
|
|
2545
|
+
cell: ({ row }) => /* @__PURE__ */ jsx29("span", { className: "text-muted-foreground text-sm", children: row.original.last_login ? format(new Date(row.original.last_login), "dd/MM/yyyy HH:mm", { locale: ptBR }) : "\u2014" })
|
|
2546
|
+
},
|
|
2547
|
+
{
|
|
2548
|
+
accessorKey: "datetime_add",
|
|
2549
|
+
header: "Criado em",
|
|
2550
|
+
cell: ({ row }) => /* @__PURE__ */ jsx29("span", { className: "text-muted-foreground text-sm", children: format(new Date(row.original.datetime_add), "dd/MM/yyyy", { locale: ptBR }) })
|
|
2551
|
+
},
|
|
2552
|
+
{
|
|
2553
|
+
id: "actions",
|
|
2554
|
+
size: 120,
|
|
2555
|
+
enableSorting: false,
|
|
2556
|
+
cell: ({ row }) => /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-1", children: [
|
|
2557
|
+
/* @__PURE__ */ jsxs19(Tooltip, { children: [
|
|
2558
|
+
/* @__PURE__ */ jsx29(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx29(Button, { variant: "ghost", size: "icon", className: "h-8 w-8", onClick: () => onResetPassword(row.original), children: /* @__PURE__ */ jsx29(Forward, { className: "h-4 w-4" }) }) }),
|
|
2559
|
+
/* @__PURE__ */ jsx29(TooltipContent, { children: "Resetar senha" })
|
|
2560
|
+
] }),
|
|
2561
|
+
/* @__PURE__ */ jsxs19(Tooltip, { children: [
|
|
2562
|
+
/* @__PURE__ */ jsx29(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx29(Button, { variant: "ghost", size: "icon", className: "h-8 w-8", onClick: () => onEdit(row.original), children: /* @__PURE__ */ jsx29(Pencil, { className: "h-4 w-4" }) }) }),
|
|
2563
|
+
/* @__PURE__ */ jsx29(TooltipContent, { children: "Editar" })
|
|
2564
|
+
] }),
|
|
2565
|
+
/* @__PURE__ */ jsxs19(Tooltip, { children: [
|
|
2566
|
+
/* @__PURE__ */ jsx29(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx29(Button, { variant: "ghost", size: "icon", className: "h-8 w-8 text-destructive hover:text-destructive", onClick: () => onDelete(row.original.id), children: /* @__PURE__ */ jsx29(Trash22, { className: "h-4 w-4" }) }) }),
|
|
2567
|
+
/* @__PURE__ */ jsx29(TooltipContent, { children: "Excluir" })
|
|
2568
|
+
] })
|
|
2569
|
+
] })
|
|
2570
|
+
}
|
|
2571
|
+
];
|
|
2572
|
+
}
|
|
2573
|
+
function UsersPage({ config, profileOptions, renderPhones }) {
|
|
2574
|
+
const [search, setSearch] = useState7("");
|
|
2575
|
+
const [profileFilter, setProfileFilter] = useState7("all");
|
|
2576
|
+
const [page, setPage] = useState7(1);
|
|
2577
|
+
const queryParams = useMemo3(() => {
|
|
2578
|
+
const params = {
|
|
2579
|
+
limit: String(PAGE_SIZE),
|
|
2580
|
+
page: String(page)
|
|
2581
|
+
};
|
|
2582
|
+
if (profileFilter !== "all") params.profile = profileFilter;
|
|
2583
|
+
return params;
|
|
2584
|
+
}, [profileFilter, page]);
|
|
2585
|
+
const { data, isLoading } = useUsers(config, queryParams);
|
|
2586
|
+
const deleteUser = useDeleteUser(config);
|
|
2587
|
+
const resetPassword = useResetPassword(config);
|
|
2588
|
+
const [editUser, setEditUser] = useState7(null);
|
|
2589
|
+
const [formOpen, setFormOpen] = useState7(false);
|
|
2590
|
+
const [deleteId, setDeleteId] = useState7(null);
|
|
2591
|
+
const [resetUser, setResetUser] = useState7(null);
|
|
2592
|
+
const users = data?.data || [];
|
|
2593
|
+
const total = data?.total || 0;
|
|
2594
|
+
const filtered = useMemo3(() => {
|
|
2595
|
+
if (!search) return users;
|
|
2596
|
+
const term = search.toLowerCase();
|
|
2597
|
+
return users.filter(
|
|
2598
|
+
(u) => `${u.name} ${u.last_name}`.toLowerCase().includes(term) || u.email.toLowerCase().includes(term)
|
|
2599
|
+
);
|
|
2600
|
+
}, [users, search]);
|
|
2601
|
+
const columns = useColumns(
|
|
2602
|
+
(u) => {
|
|
2603
|
+
setEditUser(u);
|
|
2604
|
+
setFormOpen(true);
|
|
2605
|
+
},
|
|
2606
|
+
(id) => setDeleteId(id),
|
|
2607
|
+
(u) => setResetUser(u)
|
|
2608
|
+
);
|
|
2609
|
+
function handleDelete() {
|
|
2610
|
+
if (!deleteId) return;
|
|
2611
|
+
deleteUser.mutate(deleteId, {
|
|
2612
|
+
onSuccess: () => {
|
|
2613
|
+
toast3.success("Usu\xE1rio exclu\xEDdo");
|
|
2614
|
+
setDeleteId(null);
|
|
2615
|
+
},
|
|
2616
|
+
onError: () => toast3.error("Erro ao excluir usu\xE1rio")
|
|
2617
|
+
});
|
|
2618
|
+
}
|
|
2619
|
+
function handleResetPassword() {
|
|
2620
|
+
if (!resetUser) return;
|
|
2621
|
+
resetPassword.mutate(resetUser.id, {
|
|
2622
|
+
onSuccess: () => {
|
|
2623
|
+
toast3.success("Senha resetada e email enviado");
|
|
2624
|
+
setResetUser(null);
|
|
2625
|
+
},
|
|
2626
|
+
onError: () => {
|
|
2627
|
+
toast3.error("Erro ao resetar senha");
|
|
2628
|
+
setResetUser(null);
|
|
2629
|
+
}
|
|
2630
|
+
});
|
|
2631
|
+
}
|
|
2632
|
+
return /* @__PURE__ */ jsxs19("div", { className: "flex flex-col gap-4 p-4", children: [
|
|
2633
|
+
/* @__PURE__ */ jsxs19("div", { className: "flex items-center justify-between", children: [
|
|
2634
|
+
/* @__PURE__ */ jsxs19("div", { children: [
|
|
2635
|
+
/* @__PURE__ */ jsx29("h1", { className: "text-xl font-semibold", children: "Usu\xE1rios" }),
|
|
2636
|
+
/* @__PURE__ */ jsx29("p", { className: "text-sm text-muted-foreground", children: "Gest\xE3o de usu\xE1rios da conta" })
|
|
2637
|
+
] }),
|
|
2638
|
+
/* @__PURE__ */ jsxs19(Button, { onClick: () => {
|
|
2639
|
+
setEditUser(null);
|
|
2640
|
+
setFormOpen(true);
|
|
2641
|
+
}, size: "sm", children: [
|
|
2642
|
+
/* @__PURE__ */ jsx29(Plus, { className: "mr-2 h-4 w-4" }),
|
|
2643
|
+
"Novo Usu\xE1rio"
|
|
2644
|
+
] })
|
|
2645
|
+
] }),
|
|
2646
|
+
/* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-3", children: [
|
|
2647
|
+
/* @__PURE__ */ jsxs19("div", { className: "relative flex-1 max-w-md", children: [
|
|
2648
|
+
/* @__PURE__ */ jsx29(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
|
|
2649
|
+
/* @__PURE__ */ jsx29(
|
|
2650
|
+
Input,
|
|
2651
|
+
{
|
|
2652
|
+
placeholder: "Buscar por nome ou e-mail...",
|
|
2653
|
+
value: search,
|
|
2654
|
+
onChange: (e) => {
|
|
2655
|
+
setSearch(e.target.value);
|
|
2656
|
+
setPage(1);
|
|
2657
|
+
},
|
|
2658
|
+
className: "pl-9"
|
|
2659
|
+
}
|
|
2660
|
+
)
|
|
2661
|
+
] }),
|
|
2662
|
+
/* @__PURE__ */ jsxs19(Select, { value: profileFilter, onValueChange: (v) => {
|
|
2663
|
+
setProfileFilter(v);
|
|
2664
|
+
setPage(1);
|
|
2665
|
+
}, children: [
|
|
2666
|
+
/* @__PURE__ */ jsx29(SelectTrigger, { className: "w-[180px]", children: /* @__PURE__ */ jsx29(SelectValue, {}) }),
|
|
2667
|
+
/* @__PURE__ */ jsx29(SelectContent, { children: PROFILE_FILTER_OPTIONS.map((opt) => /* @__PURE__ */ jsx29(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })
|
|
2668
|
+
] })
|
|
2669
|
+
] }),
|
|
2670
|
+
/* @__PURE__ */ jsx29(
|
|
2671
|
+
DataTable,
|
|
2672
|
+
{
|
|
2673
|
+
columns,
|
|
2674
|
+
data: search ? filtered : users,
|
|
2675
|
+
isLoading,
|
|
2676
|
+
emptyMessage: "Nenhum usu\xE1rio encontrado",
|
|
2677
|
+
total: search ? filtered.length : total,
|
|
2678
|
+
page,
|
|
2679
|
+
onPageChange: setPage,
|
|
2680
|
+
pageSize: PAGE_SIZE
|
|
2681
|
+
}
|
|
2682
|
+
),
|
|
2683
|
+
/* @__PURE__ */ jsx29(
|
|
2684
|
+
UserFormDialog,
|
|
2685
|
+
{
|
|
2686
|
+
open: formOpen,
|
|
2687
|
+
onOpenChange: (open) => {
|
|
2688
|
+
setFormOpen(open);
|
|
2689
|
+
if (!open) setEditUser(null);
|
|
2690
|
+
},
|
|
2691
|
+
user: editUser ?? void 0,
|
|
2692
|
+
config,
|
|
2693
|
+
profileOptions,
|
|
2694
|
+
renderPhones
|
|
2695
|
+
}
|
|
2696
|
+
),
|
|
2697
|
+
/* @__PURE__ */ jsx29(AlertDialog, { open: !!deleteId, onOpenChange: (open) => !open && setDeleteId(null), children: /* @__PURE__ */ jsxs19(AlertDialogContent, { children: [
|
|
2698
|
+
/* @__PURE__ */ jsxs19(AlertDialogHeader, { children: [
|
|
2699
|
+
/* @__PURE__ */ jsx29(AlertDialogTitle, { children: "Excluir usu\xE1rio?" }),
|
|
2700
|
+
/* @__PURE__ */ jsx29(AlertDialogDescription, { children: "Esta a\xE7\xE3o n\xE3o pode ser desfeita. O usu\xE1rio ser\xE1 removido permanentemente." })
|
|
2701
|
+
] }),
|
|
2702
|
+
/* @__PURE__ */ jsxs19(AlertDialogFooter, { children: [
|
|
2703
|
+
/* @__PURE__ */ jsx29(AlertDialogCancel, { children: "Cancelar" }),
|
|
2704
|
+
/* @__PURE__ */ jsx29(AlertDialogAction, { onClick: handleDelete, className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: "Excluir" })
|
|
2705
|
+
] })
|
|
2706
|
+
] }) }),
|
|
2707
|
+
/* @__PURE__ */ jsx29(AlertDialog, { open: !!resetUser, onOpenChange: (open) => !open && setResetUser(null), children: /* @__PURE__ */ jsxs19(AlertDialogContent, { children: [
|
|
2708
|
+
/* @__PURE__ */ jsxs19(AlertDialogHeader, { children: [
|
|
2709
|
+
/* @__PURE__ */ jsx29(AlertDialogTitle, { children: "Resetar senha?" }),
|
|
2710
|
+
/* @__PURE__ */ jsxs19(AlertDialogDescription, { children: [
|
|
2711
|
+
"A senha de ",
|
|
2712
|
+
/* @__PURE__ */ jsxs19("strong", { children: [
|
|
2713
|
+
resetUser?.name,
|
|
2714
|
+
" ",
|
|
2715
|
+
resetUser?.last_name
|
|
2716
|
+
] }),
|
|
2717
|
+
" (",
|
|
2718
|
+
resetUser?.email,
|
|
2719
|
+
") ser\xE1 removida e um email com link para definir nova senha ser\xE1 enviado."
|
|
2720
|
+
] })
|
|
2721
|
+
] }),
|
|
2722
|
+
/* @__PURE__ */ jsxs19(AlertDialogFooter, { children: [
|
|
2723
|
+
/* @__PURE__ */ jsx29(AlertDialogCancel, { children: "Cancelar" }),
|
|
2724
|
+
/* @__PURE__ */ jsx29(AlertDialogAction, { onClick: handleResetPassword, children: "Resetar e enviar" })
|
|
2725
|
+
] })
|
|
2726
|
+
] }) })
|
|
2727
|
+
] });
|
|
2728
|
+
}
|
|
1315
2729
|
export {
|
|
1316
2730
|
AppHeader,
|
|
1317
2731
|
AppShell,
|
|
1318
2732
|
AppSidebar,
|
|
2733
|
+
DataTable,
|
|
2734
|
+
EntityAvatar,
|
|
2735
|
+
ImageCropUpload,
|
|
1319
2736
|
LoginForm,
|
|
1320
2737
|
SidebarInset,
|
|
1321
2738
|
SidebarProvider,
|
|
1322
2739
|
SidebarTrigger,
|
|
1323
2740
|
ThemeToggle,
|
|
1324
2741
|
TooltipProvider,
|
|
2742
|
+
UserFormDialog,
|
|
2743
|
+
UserProfileBadge,
|
|
2744
|
+
UsersPage,
|
|
1325
2745
|
authClient,
|
|
1326
2746
|
authMiddlewareConfig,
|
|
1327
2747
|
cn,
|
|
@@ -1330,7 +2750,12 @@ export {
|
|
|
1330
2750
|
signIn,
|
|
1331
2751
|
signOut,
|
|
1332
2752
|
signUp,
|
|
2753
|
+
useCreateUser,
|
|
2754
|
+
useDeleteUser,
|
|
2755
|
+
useResetPassword,
|
|
1333
2756
|
useSession,
|
|
1334
|
-
useSidebar
|
|
2757
|
+
useSidebar,
|
|
2758
|
+
useUpdateUser,
|
|
2759
|
+
useUsers
|
|
1335
2760
|
};
|
|
1336
2761
|
//# sourceMappingURL=index.js.map
|