EditUserForm.jsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. "use client";
  2. import React from "react";
  3. import { Loader2 } from "lucide-react";
  4. import BranchNumberInput from "@/components/admin/users/BranchNumberInput";
  5. import { EDIT_ROLE_OPTIONS } from "@/components/admin/users/edit-user/editUserUtils";
  6. import { Button } from "@/components/ui/button";
  7. import { Input } from "@/components/ui/input";
  8. import { Label } from "@/components/ui/label";
  9. import { Checkbox } from "@/components/ui/checkbox";
  10. import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert";
  11. import { DialogFooter } from "@/components/ui/dialog";
  12. import {
  13. DropdownMenu,
  14. DropdownMenuContent,
  15. DropdownMenuLabel,
  16. DropdownMenuRadioGroup,
  17. DropdownMenuRadioItem,
  18. DropdownMenuSeparator,
  19. DropdownMenuTrigger,
  20. } from "@/components/ui/dropdown-menu";
  21. function RoleSelect({ value, onChange, disabled }) {
  22. const label =
  23. EDIT_ROLE_OPTIONS.find((x) => x.value === value)?.label || "Rolle wählen";
  24. return (
  25. <div className="grid gap-2">
  26. <Label>Rolle</Label>
  27. <DropdownMenu>
  28. <DropdownMenuTrigger asChild>
  29. <Button type="button" variant="outline" disabled={disabled}>
  30. {label}
  31. </Button>
  32. </DropdownMenuTrigger>
  33. <DropdownMenuContent align="start" className="min-w-56">
  34. <DropdownMenuLabel>Rolle auswählen</DropdownMenuLabel>
  35. <DropdownMenuSeparator />
  36. <DropdownMenuRadioGroup value={value} onValueChange={onChange}>
  37. {EDIT_ROLE_OPTIONS.map((opt) => (
  38. <DropdownMenuRadioItem key={opt.value} value={opt.value}>
  39. {opt.label}
  40. </DropdownMenuRadioItem>
  41. ))}
  42. </DropdownMenuRadioGroup>
  43. </DropdownMenuContent>
  44. </DropdownMenu>
  45. </div>
  46. );
  47. }
  48. export default function EditUserForm({
  49. user,
  50. form,
  51. setPatch,
  52. error,
  53. branchesStatus,
  54. branchExistence,
  55. disabled,
  56. isSubmitting,
  57. canSubmit,
  58. onCancel,
  59. onSubmit,
  60. }) {
  61. const role = String(form?.role || "branch");
  62. const userId = String(user?.id || "");
  63. const branchMessageId = "eu-branch-message";
  64. const showUnknownBranch =
  65. role === "branch" && Boolean(branchExistence?.hasUnknownBranch);
  66. const showFailOpenNote = role === "branch" && Boolean(branchExistence?.listError);
  67. const showBranchLoading =
  68. role === "branch" && !showUnknownBranch && branchesStatus === "loading";
  69. return (
  70. <form onSubmit={onSubmit} className="space-y-4">
  71. {error ? (
  72. <Alert variant="destructive">
  73. <AlertTitle>{error.title}</AlertTitle>
  74. {error.description ? (
  75. <AlertDescription>{error.description}</AlertDescription>
  76. ) : null}
  77. {Array.isArray(error.hints) && error.hints.length > 0 ? (
  78. <AlertDescription>
  79. <ul className="mt-2 list-disc pl-5">
  80. {error.hints.map((line) => (
  81. <li key={line}>{line}</li>
  82. ))}
  83. </ul>
  84. </AlertDescription>
  85. ) : null}
  86. </Alert>
  87. ) : null}
  88. <div className="grid gap-2">
  89. <Label>User ID</Label>
  90. <Input value={userId || "—"} disabled />
  91. </div>
  92. <div className="grid gap-3 md:grid-cols-2">
  93. <div className="grid gap-2">
  94. <Label htmlFor="eu-username">Benutzername</Label>
  95. <Input
  96. id="eu-username"
  97. value={form?.username ?? ""}
  98. onChange={(e) => setPatch({ username: e.target.value })}
  99. disabled={disabled}
  100. autoCapitalize="none"
  101. autoCorrect="off"
  102. spellCheck={false}
  103. placeholder="z. B. branchuser"
  104. />
  105. </div>
  106. <div className="grid gap-2">
  107. <Label htmlFor="eu-email">E-Mail</Label>
  108. <Input
  109. id="eu-email"
  110. value={form?.email ?? ""}
  111. onChange={(e) => setPatch({ email: e.target.value })}
  112. disabled={disabled}
  113. placeholder="name@firma.de"
  114. />
  115. </div>
  116. </div>
  117. <div className="grid gap-3 md:grid-cols-2">
  118. <RoleSelect
  119. value={role}
  120. onChange={(v) => setPatch({ role: v })}
  121. disabled={disabled}
  122. />
  123. {role === "branch" ? (
  124. <div className="grid gap-2">
  125. <BranchNumberInput
  126. id="eu-branch"
  127. branchId={form?.branchId ?? ""}
  128. onBranchIdChange={(branchId) => setPatch({ branchId })}
  129. disabled={disabled}
  130. invalid={showUnknownBranch}
  131. describedBy={showUnknownBranch ? branchMessageId : undefined}
  132. />
  133. {showUnknownBranch ? (
  134. <p id={branchMessageId} className="text-xs text-destructive">
  135. Diese Niederlassung ist nicht in der aktuellen Liste vorhanden.
  136. </p>
  137. ) : null}
  138. {showFailOpenNote ? (
  139. <p className="text-xs text-muted-foreground">
  140. Hinweis: Die Niederlassungsliste konnte nicht geladen werden.
  141. Die Existenz kann aktuell nicht geprüft werden.
  142. </p>
  143. ) : null}
  144. {showBranchLoading ? (
  145. <p className="text-xs text-muted-foreground">
  146. Niederlassungsliste wird geladen…
  147. </p>
  148. ) : null}
  149. </div>
  150. ) : (
  151. <div className="grid gap-2">
  152. <Label>Niederlassung</Label>
  153. <Input value="—" disabled />
  154. </div>
  155. )}
  156. </div>
  157. <div className="flex items-start gap-3 rounded-lg border p-3">
  158. <Checkbox
  159. checked={Boolean(form?.mustChangePassword)}
  160. disabled={disabled}
  161. onCheckedChange={(v) => {
  162. setPatch({ mustChangePassword: Boolean(v) });
  163. }}
  164. />
  165. <div className="grid gap-1">
  166. <p className="text-sm font-medium">Passwortwechsel erforderlich</p>
  167. <p className="text-xs text-muted-foreground">
  168. Wenn aktiviert, muss der Benutzer beim nächsten Login sein Passwort
  169. ändern.
  170. </p>
  171. </div>
  172. </div>
  173. <DialogFooter>
  174. <Button
  175. type="button"
  176. variant="outline"
  177. disabled={disabled}
  178. onClick={onCancel}
  179. >
  180. Abbrechen
  181. </Button>
  182. <Button type="submit" disabled={disabled || !canSubmit}>
  183. {isSubmitting ? (
  184. <>
  185. <Loader2 className="h-4 w-4 animate-spin" />
  186. Speichern…
  187. </>
  188. ) : (
  189. "Speichern"
  190. )}
  191. </Button>
  192. </DialogFooter>
  193. </form>
  194. );
  195. }