NoteForm.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. "use client";
  2. import { useState, useEffect } from "react";
  3. import { useRouter } from "next/navigation";
  4. import { Button } from "@/components/ui/button";
  5. import { Input } from "@/components/ui/input";
  6. import { Label } from "@/components/ui/label";
  7. import {
  8. Dialog,
  9. DialogContent,
  10. DialogHeader,
  11. DialogTitle,
  12. } from "@/components/ui/dialog";
  13. import { useGlassModal } from "@/components/glass-modal";
  14. import { toast } from "sonner";
  15. import { createNote, updateNote } from "../Actions/noteActions";
  16. import { RichTextEditor } from "@/components/ui/rich-text-editor";
  17. import { Loader2 } from "lucide-react";
  18. import { useTranslations } from "next-intl";
  19. interface NoteFormProps {
  20. vehicleId: string;
  21. open: boolean;
  22. onOpenChange: (open: boolean) => void;
  23. note?: {
  24. id: string;
  25. title: string;
  26. content: string;
  27. isPinned: boolean;
  28. };
  29. }
  30. export function NoteForm({ vehicleId, open, onOpenChange, note }: NoteFormProps) {
  31. const router = useRouter();
  32. const modal = useGlassModal();
  33. const t = useTranslations("vehicles.notes");
  34. const tc = useTranslations("common.buttons");
  35. const [loading, setLoading] = useState(false);
  36. const [title, setTitle] = useState("");
  37. const [content, setContent] = useState("");
  38. const isEditing = !!note;
  39. useEffect(() => {
  40. if (open) {
  41. setTitle(note?.title ?? "");
  42. setContent(note?.content ?? "");
  43. }
  44. }, [open, note]);
  45. const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
  46. e.preventDefault();
  47. if (!title.trim() || !content.trim() || content === "<p></p>") {
  48. modal.open("error", "Error", t("titleRequired"));
  49. return;
  50. }
  51. setLoading(true);
  52. const result = isEditing
  53. ? await updateNote({ id: note.id, title, content })
  54. : await createNote({ vehicleId, title, content });
  55. if (result.success) {
  56. toast.success(isEditing ? t("noteUpdated") : t("noteCreated"));
  57. onOpenChange(false);
  58. router.refresh();
  59. } else {
  60. modal.open("error", "Error", result.error || t("saveError"));
  61. }
  62. setLoading(false);
  63. };
  64. return (
  65. <Dialog open={open} onOpenChange={onOpenChange}>
  66. <DialogContent className="sm:max-w-3xl">
  67. <DialogHeader>
  68. <DialogTitle>{isEditing ? t("editTitle") : t("addTitle")}</DialogTitle>
  69. </DialogHeader>
  70. <form onSubmit={handleSubmit} className="space-y-4">
  71. <div className="space-y-2">
  72. <Label htmlFor="title">{t("titleLabel")}</Label>
  73. <Input
  74. id="title"
  75. placeholder={t("titlePlaceholder")}
  76. value={title}
  77. onChange={(e) => setTitle(e.target.value)}
  78. required
  79. />
  80. </div>
  81. <div className="space-y-2">
  82. <Label>{t("contentLabel")}</Label>
  83. <RichTextEditor
  84. content={content}
  85. onChange={setContent}
  86. placeholder={t("contentPlaceholder")}
  87. />
  88. </div>
  89. <div className="flex justify-end gap-3 pt-2">
  90. <Button
  91. type="button"
  92. variant="outline"
  93. onClick={() => onOpenChange(false)}
  94. >
  95. {tc("cancel")}
  96. </Button>
  97. <Button type="submit" disabled={loading}>
  98. {loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
  99. {isEditing ? tc("saveChanges") : t("addTitle")}
  100. </Button>
  101. </div>
  102. </form>
  103. </DialogContent>
  104. </Dialog>
  105. );
  106. }