- 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.
122 lines
3.5 KiB
TypeScript
122 lines
3.5 KiB
TypeScript
"use client"
|
|
|
|
import { useEffect, useState } from "react"
|
|
import { create } from "zustand"
|
|
|
|
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 { Label } from "@/shared/components/ui/label"
|
|
|
|
// ── Types ──
|
|
|
|
export type PromptOptions = {
|
|
title?: string
|
|
description?: string
|
|
label?: string
|
|
placeholder?: string
|
|
defaultValue?: string
|
|
confirmLabel?: string
|
|
cancelLabel?: string
|
|
required?: boolean
|
|
}
|
|
|
|
type PromptStore = {
|
|
open: boolean
|
|
options: PromptOptions
|
|
resolve: ((value: string | null) => void) | null
|
|
_show: (options: PromptOptions) => Promise<string | null>
|
|
_close: (value: string | null) => void
|
|
}
|
|
|
|
// ── Store ──
|
|
|
|
const usePromptStore = create<PromptStore>((set, get) => ({
|
|
open: false,
|
|
options: {},
|
|
resolve: null,
|
|
_show: (options) =>
|
|
new Promise<string | null>((resolve) => {
|
|
set({ open: true, options, resolve })
|
|
}),
|
|
_close: (value) => {
|
|
const { resolve } = get()
|
|
resolve?.(value)
|
|
set({ open: false, resolve: null })
|
|
},
|
|
}))
|
|
|
|
// ── Imperative API ──
|
|
|
|
export function prompt(options: PromptOptions = {}): Promise<string | null> {
|
|
return usePromptStore.getState()._show(options)
|
|
}
|
|
|
|
// ── Dialog component (mount once in root layout) ──
|
|
|
|
export function PromptDialog() {
|
|
const { open, options, _close } = usePromptStore()
|
|
const [value, setValue] = useState("")
|
|
|
|
// Reset value whenever the dialog opens
|
|
useEffect(() => {
|
|
if (open) setValue(options.defaultValue ?? "")
|
|
}, [open, options.defaultValue])
|
|
|
|
const submit = () => {
|
|
if (options.required !== false && value.trim() === "") return
|
|
_close(value.trim())
|
|
}
|
|
|
|
return (
|
|
<Dialog
|
|
open={open}
|
|
onOpenChange={(v) => {
|
|
if (!v) _close(null)
|
|
}}
|
|
>
|
|
<DialogContent className="sm:max-w-md">
|
|
<DialogHeader>
|
|
<DialogTitle>{options.title ?? "Enter a value"}</DialogTitle>
|
|
{options.description && (
|
|
<DialogDescription>{options.description}</DialogDescription>
|
|
)}
|
|
</DialogHeader>
|
|
|
|
<div className="grid gap-2">
|
|
{options.label && <Label htmlFor="prompt-input">{options.label}</Label>}
|
|
<Input
|
|
id="prompt-input"
|
|
autoFocus
|
|
value={value}
|
|
placeholder={options.placeholder}
|
|
onChange={(e) => setValue(e.target.value)}
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter") {
|
|
e.preventDefault()
|
|
submit()
|
|
}
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => _close(null)}>
|
|
{options.cancelLabel ?? "Cancel"}
|
|
</Button>
|
|
<Button onClick={submit} disabled={options.required !== false && value.trim() === ""}>
|
|
{options.confirmLabel ?? "OK"}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|