feat: add document sharing functionality across various modules
- Introduced ShareDocumentButton component for sharing documents. - Added ShareDocumentDialog for email and WhatsApp sharing options. - Integrated document sharing in estimates, invoices, inspections, job cards, bills, and purchase orders. - Implemented useDocumentShare hook for handling share logic. - Created DocumentShareClient for API interactions related to document sharing. - Updated layouts and actions to include sharing options for relevant entities.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { ClipboardList } from "lucide-react"
|
||||
import { ClipboardList, Loader2 } from "lucide-react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { ApiError } from "@garage/api"
|
||||
import { Button } from "@/shared/components/ui/button"
|
||||
@@ -30,34 +30,37 @@ export function CreateJobCardFromEstimateButton() {
|
||||
if (!confirmed) return
|
||||
|
||||
setIsConverting(true)
|
||||
const promise = api.estimates.convertToJobCard(estimateId, {})
|
||||
toast.promise(promise, {
|
||||
loading: "Generating job card...",
|
||||
success: "Estimate converted to job card successfully",
|
||||
error: (error: unknown) =>
|
||||
error instanceof ApiError && error.status === 409
|
||||
? "A job card already exists for this estimate."
|
||||
: "Failed to convert estimate to job card",
|
||||
})
|
||||
try {
|
||||
const response = await api.estimates.convertToJobCard(estimateId, {})
|
||||
const response = await promise
|
||||
const jobCardId = response?.data?.id
|
||||
|
||||
toast.success("Estimate converted to job card successfully")
|
||||
|
||||
if (jobCardId) {
|
||||
router.push(`/sales/job-cards/${jobCardId}`)
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError && error.status === 409) {
|
||||
const jobCardId = (error.payload?.data as { job_card_id?: number } | undefined)?.job_card_id
|
||||
toast.info("A job card already exists for this estimate.")
|
||||
if (jobCardId) {
|
||||
router.push(`/sales/job-cards/${jobCardId}`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
toast.error("Failed to convert estimate to job card")
|
||||
} finally {
|
||||
setIsConverting(false)
|
||||
}
|
||||
setIsConverting(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Button variant="outline" size="sm" onClick={handleConvert} disabled={isConverting}>
|
||||
<ClipboardList className="me-2 size-4" />
|
||||
{isConverting ? <Loader2 className="me-2 size-4 animate-spin" /> : <ClipboardList className="me-2 size-4" />}
|
||||
{isConverting ? "Generating..." : "Generate Job Card"}
|
||||
</Button>
|
||||
)
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/shared/components/ui/select"
|
||||
import { Ellipsis, Pencil, Trash2, ShieldCheck, Check, X, Printer } from "lucide-react"
|
||||
import { Ellipsis, Pencil, Trash2, ShieldCheck, Check, X, Printer, Share2 } from "lucide-react"
|
||||
import { useState } from "react"
|
||||
import { useMutation, useQuery } from "@tanstack/react-query"
|
||||
import { toast } from "sonner"
|
||||
@@ -34,6 +34,7 @@ import { DatePickerField, TimePickerField } from "@/shared/components/form"
|
||||
import { cn } from "@/shared/lib/utils"
|
||||
import { EmployeeCombobox, type EmployeeOption } from "../employees/employee-combobox"
|
||||
import { useDocumentPrint } from "@/shared/hooks/use-document-print"
|
||||
import { ShareDocumentDialog } from "@/shared/components/share-document-dialog"
|
||||
|
||||
type EstimateActionsProps = {
|
||||
estimateId: string
|
||||
@@ -135,6 +136,7 @@ export function EstimateActions({ estimateId }: EstimateActionsProps) {
|
||||
const { print, isPrinting } = useDocumentPrint()
|
||||
const [editOpen, setEditOpen] = useState(false)
|
||||
const [authOpen, setAuthOpen] = useState(false)
|
||||
const [shareOpen, setShareOpen] = useState(false)
|
||||
const [itemStatuses, setItemStatuses] = useState<Record<string, string>>({})
|
||||
const [authMethod, setAuthMethod] = useState("in_person")
|
||||
const [employee, setEmployee] = useState<EmployeeOption | null>(null)
|
||||
@@ -224,6 +226,10 @@ export function EstimateActions({ estimateId }: EstimateActionsProps) {
|
||||
<Printer className="size-4" />
|
||||
{isPrinting ? "Printing..." : "Print"}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setShareOpen(true)}>
|
||||
<Share2 className="size-4" />
|
||||
Share
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={openAuthDialog}>
|
||||
<ShieldCheck className="size-4" />
|
||||
Store Authorisation
|
||||
@@ -235,6 +241,8 @@ export function EstimateActions({ estimateId }: EstimateActionsProps) {
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<ShareDocumentDialog type="estimate" id={estimateId} open={shareOpen} onOpenChange={setShareOpen} />
|
||||
|
||||
{/* Edit Dialog */}
|
||||
<Dialog open={editOpen} onOpenChange={setEditOpen}>
|
||||
<DialogContent className="min-w-xl">
|
||||
|
||||
Reference in New Issue
Block a user