SettingsCrud.tsx 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. "use client";
  2. import { PlusIcon } from "@heroicons/react/24/outline";
  3. import { useEffect, useState } from "react";
  4. import toast from "react-hot-toast";
  5. import { useAppContext } from "../providers/AppContext";
  6. import JsonInput from "./JsonInput";
  7. import { useNotifications } from "./NotificationContext";
  8. import QueueSettingsEditor from "./QueueSettingsEditor";
  9. import SlideInForm from "./SlideInForm";
  10. import WatcherSettingsEditor from "./WatcherSettingsEditor";
  11. interface SettingsCrudProps {
  12. editKey?: string;
  13. editValue?: string;
  14. onEditClose?: () => void;
  15. }
  16. interface SettingsFormState {
  17. key: string;
  18. value: string;
  19. isOpen: boolean;
  20. }
  21. const initialSettingsState: SettingsFormState = {
  22. key: "",
  23. value: "",
  24. isOpen: false,
  25. };
  26. export default function SettingsCrud({
  27. editKey,
  28. editValue,
  29. onEditClose,
  30. }: SettingsCrudProps) {
  31. const { updateSetting } = useAppContext();
  32. const { addNotification } = useNotifications();
  33. const [formState, setFormState] =
  34. useState<SettingsFormState>(initialSettingsState);
  35. const [isSaving, setIsSaving] = useState(false);
  36. const isEditing = !!editKey;
  37. useEffect(() => {
  38. if (isEditing) {
  39. setFormState({
  40. key: editKey,
  41. value: editValue || "",
  42. isOpen: true,
  43. });
  44. }
  45. }, [editKey, editValue, isEditing]);
  46. const handleSave = async () => {
  47. try {
  48. setIsSaving(true);
  49. await updateSetting(formState.key, formState.value);
  50. setFormState(initialSettingsState);
  51. if (onEditClose) onEditClose();
  52. const message = isEditing
  53. ? "Setting updated successfully"
  54. : "Setting added successfully";
  55. toast.success(message);
  56. addNotification({
  57. type: "success",
  58. title: "Setting Saved",
  59. message: `${formState.key} has been ${isEditing ? "updated" : "added"} successfully.`,
  60. });
  61. } catch (error) {
  62. console.error("Failed to save setting:", error);
  63. toast.error("Failed to save setting");
  64. addNotification({
  65. type: "error",
  66. title: "Save Failed",
  67. message: "Failed to save the setting. Please try again.",
  68. });
  69. } finally {
  70. setIsSaving(false);
  71. }
  72. };
  73. const handleSubmit = (e: React.FormEvent) => {
  74. e.preventDefault();
  75. handleSave();
  76. };
  77. const getValueEditor = (currentKey?: string) => {
  78. const editorKey = currentKey || formState.key;
  79. switch (editorKey) {
  80. case "watcher":
  81. return (
  82. <WatcherSettingsEditor
  83. value={formState.value}
  84. onChange={(val) => setFormState({ ...formState, value: val })}
  85. />
  86. );
  87. case "queue":
  88. return (
  89. <QueueSettingsEditor
  90. value={formState.value}
  91. onChange={(val) => setFormState({ ...formState, value: val })}
  92. />
  93. );
  94. case "datasets":
  95. // Use JsonInput with JSON mode toggle for datasets configuration
  96. return (
  97. <JsonInput
  98. value={formState.value}
  99. onChange={(val) => setFormState({ ...formState, value: val })}
  100. className="w-full"
  101. placeholder="Datasets configuration (JSON)"
  102. />
  103. );
  104. default:
  105. return (
  106. <JsonInput
  107. value={formState.value}
  108. onChange={(val) => setFormState({ ...formState, value: val })}
  109. className="w-full"
  110. placeholder="Value (JSON)"
  111. />
  112. );
  113. }
  114. };
  115. const handleClose = () => {
  116. setFormState(initialSettingsState);
  117. if (onEditClose) onEditClose();
  118. };
  119. if (isEditing) {
  120. return (
  121. <SlideInForm
  122. isOpen={formState.isOpen}
  123. onClose={handleClose}
  124. title="Edit Setting"
  125. actions={
  126. <div className="flex justify-end space-x-3">
  127. <button
  128. type="button"
  129. onClick={handleClose}
  130. className="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
  131. >
  132. Cancel
  133. </button>
  134. <button
  135. type="button"
  136. onClick={handleSubmit}
  137. disabled={isSaving}
  138. className="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
  139. >
  140. Update Setting
  141. </button>
  142. </div>
  143. }
  144. >
  145. <form onSubmit={handleSubmit} className="space-y-4">
  146. <div>
  147. <label className="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">
  148. Key
  149. </label>
  150. <input
  151. className="block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"
  152. placeholder="Key"
  153. value={formState.key}
  154. onChange={(e) =>
  155. setFormState({ ...formState, key: e.target.value })
  156. }
  157. required
  158. disabled
  159. />
  160. </div>
  161. <div>
  162. <label className="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">
  163. Value
  164. </label>
  165. {getValueEditor()}
  166. </div>
  167. </form>
  168. </SlideInForm>
  169. );
  170. }
  171. return (
  172. <>
  173. <div className="relative group">
  174. <button
  175. onClick={() => setFormState({ ...formState, isOpen: true })}
  176. className="p-2 rounded-md text-gray-600 dark:text-gray-400 hover:bg-indigo-100 dark:hover:bg-indigo-900/30 hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors"
  177. title="Add Setting"
  178. >
  179. <PlusIcon className="h-5 w-5" />
  180. </button>
  181. <div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none">
  182. Add Setting
  183. </div>
  184. </div>
  185. <SlideInForm
  186. isOpen={formState.isOpen}
  187. onClose={handleClose}
  188. title={isEditing ? `Edit Setting: ${editKey}` : "Add New Setting"}
  189. actions={
  190. <div className="flex justify-end space-x-3">
  191. <button
  192. type="button"
  193. onClick={handleClose}
  194. className="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
  195. >
  196. Cancel
  197. </button>
  198. <button
  199. type="submit"
  200. onClick={handleSubmit}
  201. disabled={isSaving}
  202. className="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
  203. >
  204. {isEditing ? "Update Setting" : "Add Setting"}
  205. </button>
  206. </div>
  207. }
  208. >
  209. <form onSubmit={handleSubmit} className="space-y-4">
  210. <div>
  211. <label className="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">
  212. Key
  213. </label>
  214. <input
  215. className="block w-full rounded-md border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"
  216. placeholder="Key"
  217. value={formState.key}
  218. onChange={(e) =>
  219. setFormState({ ...formState, key: e.target.value })
  220. }
  221. required
  222. />
  223. </div>
  224. <div>
  225. <label className="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">
  226. Value
  227. </label>
  228. {getValueEditor(formState.key)}
  229. </div>
  230. </form>
  231. </SlideInForm>
  232. </>
  233. );
  234. }