feat: add progress tracking dialog with content editor for ticket updates
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useCallback, useEffect, useRef, useState } from "react"
|
import { useCallback, useEffect, useRef, useState } from "react"
|
||||||
import { MessageSquareTextIcon, PencilIcon, RefreshCcwIcon, SendIcon, UserRoundIcon } from "lucide-react"
|
import { MessageSquareTextIcon, PencilIcon, PlusIcon, RefreshCcwIcon, SendIcon, UserRoundIcon } from "lucide-react"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
|
|
||||||
import { type CustomerFormSavePayload } from "@/components/customer-form"
|
import { type CustomerFormSavePayload } from "@/components/customer-form"
|
||||||
@@ -12,6 +12,13 @@ import { ProjectDialog } from "@/components/project-dialog"
|
|||||||
import { isRichTextEmpty, SafeRichHTML } from "@/components/safe-rich-html"
|
import { isRichTextEmpty, SafeRichHTML } from "@/components/safe-rich-html"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog"
|
||||||
import { Separator } from "@/components/ui/separator"
|
import { Separator } from "@/components/ui/separator"
|
||||||
import { saveCustomerProfile } from "@/lib/api/customer"
|
import { saveCustomerProfile } from "@/lib/api/customer"
|
||||||
import {
|
import {
|
||||||
@@ -74,6 +81,7 @@ export function TicketDetailDialog({
|
|||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [statusSaving, setStatusSaving] = useState<TicketStatus | null>(null)
|
const [statusSaving, setStatusSaving] = useState<TicketStatus | null>(null)
|
||||||
const [progressSaving, setProgressSaving] = useState(false)
|
const [progressSaving, setProgressSaving] = useState(false)
|
||||||
|
const [progressOpen, setProgressOpen] = useState(false)
|
||||||
const [progressContent, setProgressContent] = useState("")
|
const [progressContent, setProgressContent] = useState("")
|
||||||
const [assignOpen, setAssignOpen] = useState(false)
|
const [assignOpen, setAssignOpen] = useState(false)
|
||||||
const [editOpen, setEditOpen] = useState(false)
|
const [editOpen, setEditOpen] = useState(false)
|
||||||
@@ -126,6 +134,7 @@ export function TicketDetailDialog({
|
|||||||
setLoading(false)
|
setLoading(false)
|
||||||
setStatusSaving(null)
|
setStatusSaving(null)
|
||||||
setProgressSaving(false)
|
setProgressSaving(false)
|
||||||
|
setProgressOpen(false)
|
||||||
setEditSaving(false)
|
setEditSaving(false)
|
||||||
setCustomerEditSaving(false)
|
setCustomerEditSaving(false)
|
||||||
setAssignOpen(false)
|
setAssignOpen(false)
|
||||||
@@ -191,6 +200,7 @@ export function TicketDetailDialog({
|
|||||||
}
|
}
|
||||||
toast.success("处理进展已记录")
|
toast.success("处理进展已记录")
|
||||||
setProgressContent("")
|
setProgressContent("")
|
||||||
|
setProgressOpen(false)
|
||||||
await loadDetail(activeTicketId, activeDialogSeq)
|
await loadDetail(activeTicketId, activeDialogSeq)
|
||||||
if (!isCurrentOperation(activeTicketId, activeDialogSeq)) {
|
if (!isCurrentOperation(activeTicketId, activeDialogSeq)) {
|
||||||
return
|
return
|
||||||
@@ -433,29 +443,19 @@ export function TicketDetailDialog({
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside className="flex min-h-0 flex-col p-6">
|
<aside className="flex min-h-0 flex-col">
|
||||||
<div className="flex items-center gap-2 text-sm font-medium">
|
<div className="flex items-center justify-between gap-2 px-4 py-2">
|
||||||
<MessageSquareTextIcon className="size-4 text-muted-foreground" />
|
<div className="flex items-center gap-2 text-sm font-medium">
|
||||||
处理进展
|
<MessageSquareTextIcon className="size-4 text-muted-foreground" />
|
||||||
</div>
|
处理进展
|
||||||
<div className="mt-3 space-y-2">
|
|
||||||
<ContentEditor
|
|
||||||
value={{ mode: "html", raw: progressContent }}
|
|
||||||
onChange={(next) => setProgressContent(next.raw)}
|
|
||||||
placeholder="记录本次处理进展"
|
|
||||||
disabled={progressSaving}
|
|
||||||
allowedModes={["html"]}
|
|
||||||
height={220}
|
|
||||||
/>
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button type="button" size="sm" disabled={progressSaving} onClick={() => void handleCreateProgress()}>
|
|
||||||
<SendIcon className="size-3.5" />
|
|
||||||
{progressSaving ? "提交中..." : "添加进展"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<Button type="button" size="sm" onClick={() => setProgressOpen(true)}>
|
||||||
|
<PlusIcon className="size-3.5" />
|
||||||
|
添加进展
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Separator className="my-4" />
|
<Separator />
|
||||||
<div className="min-h-0 flex-1 overflow-y-auto pr-1">
|
<div className="min-h-0 flex-1 overflow-y-auto p-4">
|
||||||
{detail.progresses && detail.progresses.length > 0 ? (
|
{detail.progresses && detail.progresses.length > 0 ? (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{detail.progresses.map((progress, index) => (
|
{detail.progresses.map((progress, index) => (
|
||||||
@@ -519,6 +519,51 @@ export function TicketDetailDialog({
|
|||||||
ticketId={ticket?.id ?? null}
|
ticketId={ticket?.id ?? null}
|
||||||
onSuccess={handleCustomerLinked}
|
onSuccess={handleCustomerLinked}
|
||||||
/>
|
/>
|
||||||
|
<Dialog
|
||||||
|
open={progressOpen}
|
||||||
|
onOpenChange={(nextOpen) => {
|
||||||
|
if (progressSaving) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setProgressOpen(nextOpen)
|
||||||
|
if (!nextOpen) {
|
||||||
|
setProgressContent("")
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogContent className="max-w-2xl gap-0 p-0 sm:max-w-3xl">
|
||||||
|
<DialogHeader className="px-6 pt-6">
|
||||||
|
<DialogTitle>添加处理进展</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="px-6 py-4">
|
||||||
|
<ContentEditor
|
||||||
|
value={{ mode: "html", raw: progressContent }}
|
||||||
|
onChange={(next) => setProgressContent(next.raw)}
|
||||||
|
placeholder="记录本次处理进展"
|
||||||
|
disabled={progressSaving}
|
||||||
|
allowedModes={["html"]}
|
||||||
|
height={260}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<DialogFooter className="mx-0 mb-0 px-6 py-4">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
disabled={progressSaving}
|
||||||
|
onClick={() => {
|
||||||
|
setProgressOpen(false)
|
||||||
|
setProgressContent("")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button type="button" disabled={progressSaving} onClick={() => void handleCreateProgress()}>
|
||||||
|
<SendIcon className="size-3.5" />
|
||||||
|
{progressSaving ? "提交中..." : "提交"}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user