garage-erp/apps/dashboard/modules/inspections/inspection-share-dialog.tsx
humam kerdiah 4bfd8c84a9 feat: add template checkpoint edit dialog and vendor management components
- Implemented TemplateCheckpointEditDialog for creating and editing inspection checkpoints.
- Added VendorActions component for managing vendor actions including edit, activate/deactivate, and delete.
- Created VendorContext for managing vendor state across components.
- Developed VendorGeneralInfo component to display detailed vendor information.
- Introduced AedSymbol and Money components for consistent currency representation.
- Added PromptDialog for user input prompts throughout the application.
- Implemented RelationLink component for unified related-data display in CRUD tables.
- Created InspectionTemplatesClient for API interactions related to inspection templates.
2026-05-18 12:08:42 +04:00

140 lines
5.1 KiB
TypeScript

"use client"
import { useEffect, useState } from "react"
import { toast } from "sonner"
import { Check, Copy, ExternalLink, Link2, XCircle } from "lucide-react"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/shared/components/ui/dialog"
import { Button } from "@/shared/components/ui/button"
import { Input } from "@/shared/components/ui/input"
import { useAuthApi } from "@/shared/useApi"
import { confirm } from "@/shared/components/confirm-dialog"
export function InspectionShareDialog({
open,
onOpenChange,
inspectionId,
}: {
open: boolean
onOpenChange: (o: boolean) => void
inspectionId: number | string
}) {
const api = useAuthApi()
const [loading, setLoading] = useState(false)
const [shareUrl, setShareUrl] = useState<string | null>(null)
const [expiresAt, setExpiresAt] = useState<string | null>(null)
const [copied, setCopied] = useState(false)
const generate = async () => {
setLoading(true)
try {
const res = await api.inspections.share(inspectionId)
setShareUrl(res.data.share_url)
setExpiresAt(res.data.share_expires_at)
} catch (e: any) {
toast.error(e?.payload?.message ?? e?.message ?? "Failed to generate share link")
onOpenChange(false)
} finally {
setLoading(false)
}
}
useEffect(() => {
if (open && !shareUrl) generate()
if (!open) {
setShareUrl(null)
setExpiresAt(null)
setCopied(false)
}
}, [open])
const copy = async () => {
if (!shareUrl) return
try {
await navigator.clipboard.writeText(shareUrl)
setCopied(true)
toast.success("Link copied")
setTimeout(() => setCopied(false), 2000)
} catch {
toast.error("Copy failed — select the field and press Cmd/Ctrl+C")
}
}
const revoke = async () => {
const ok = await confirm({
title: "Revoke this share link?",
description: "The customer will no longer be able to view the report.",
confirmLabel: "Revoke",
variant: "destructive",
})
if (!ok) return
try {
await api.inspections.revokeShare(inspectionId)
toast.success("Share link revoked")
onOpenChange(false)
} catch (e: any) {
toast.error(e?.payload?.message ?? e?.message ?? "Failed to revoke")
}
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Link2 className="size-4" /> Share inspection with customer
</DialogTitle>
<DialogDescription>
Anyone with this link can view the inspection report. No login required.
</DialogDescription>
</DialogHeader>
{loading && (
<div className="py-6 text-center text-sm text-muted-foreground">Generating link</div>
)}
{shareUrl && (
<div className="space-y-3">
<div className="flex items-center gap-2">
<Input value={shareUrl} readOnly className="font-mono text-xs" />
<Button type="button" size="sm" onClick={copy}>
{copied ? <Check className="size-4" /> : <Copy className="size-4" />}
{copied ? "Copied" : "Copy"}
</Button>
</div>
{expiresAt && (
<p className="text-xs text-muted-foreground">
Expires on {new Date(expiresAt).toLocaleDateString()}.
</p>
)}
<div className="flex items-center justify-between text-xs">
<a
href={shareUrl}
target="_blank"
rel="noopener noreferrer"
className="text-primary inline-flex items-center gap-1 hover:underline"
>
<ExternalLink className="size-3.5" /> Open preview in new tab
</a>
</div>
</div>
)}
<DialogFooter className="flex justify-between gap-2 sm:justify-between">
<Button variant="ghost" size="sm" onClick={revoke} disabled={!shareUrl}>
<XCircle className="size-4 text-rose-600" /> Revoke link
</Button>
<Button variant="outline" onClick={() => onOpenChange(false)}>Done</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}