feat: expose the remaining fields the edit forms were withholding
The backend accepted every field of these records already; the forms simply did not offer them, so from the app they were still frozen. - Inventar: only the condition could be changed. A mistyped inventory number, a wrong purchase or maintenance date, a renamed item - all stuck for the life of the record. New edit dialog covering every field; the inline condition select stays as a shortcut. - Produkt: the status was shown as a badge but could not be set anywhere, so a product could never be discontinued or brought back. - Aufgabe: the status could only ever move to DONE through the "Erledigt" button. IN_PROGRESS and CANCELLED were unreachable, and an accidentally completed task could not be reopened. - Aufgabenvorlage: priority, assignee and the active flag were missing. Deactivating was one-way, since only a deactivate endpoint was wired up - the active checkbox now reactivates through PATCH. - Königinnen-Bewertung: notes and the evaluated colony were missing. Verified by extracting the field set each form submits and comparing it with the matching update DTO: every form now covers its DTO. The one gap left is re-linking a document to a different record, which still has no UI. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
1 parent
5a5bd4a138
commit
786d929df9
6 files changed
+421
-4
No files matched your search
@@ -0,0 +1,211 @@
|
||||
"use client";
|
||||
|
||||
import { FormEvent, useState } from "react";
|
||||
import { Pencil } from "lucide-react";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
import { updateEquipment } from "@/lib/equipment/api";
|
||||
import { Equipment, EquipmentCondition } from "@/lib/equipment/types";
|
||||
import {
|
||||
EQUIPMENT_CONDITIONS,
|
||||
EQUIPMENT_CONDITION_LABELS,
|
||||
} from "@/lib/equipment/labels";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
|
||||
/**
|
||||
* Corrects every field of an inventory item. Only the condition used to be
|
||||
* changeable, so a mistyped inventory number or a wrong purchase date stayed
|
||||
* wrong for the life of the record.
|
||||
*/
|
||||
function EquipmentEditForm({
|
||||
equipment,
|
||||
onSaved,
|
||||
}: {
|
||||
equipment: Equipment;
|
||||
onSaved: () => void;
|
||||
}) {
|
||||
const queryClient = useQueryClient();
|
||||
const [inventoryNumber, setInventoryNumber] = useState(
|
||||
equipment.inventoryNumber,
|
||||
);
|
||||
const [name, setName] = useState(equipment.name);
|
||||
const [manufacturer, setManufacturer] = useState(equipment.manufacturer ?? "");
|
||||
const [location, setLocation] = useState(equipment.location ?? "");
|
||||
const [purchaseDate, setPurchaseDate] = useState(
|
||||
equipment.purchaseDate?.slice(0, 10) ?? "",
|
||||
);
|
||||
const [maintenanceDate, setMaintenanceDate] = useState(
|
||||
equipment.maintenanceDate?.slice(0, 10) ?? "",
|
||||
);
|
||||
const [condition, setCondition] = useState<EquipmentCondition>(
|
||||
equipment.condition,
|
||||
);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: () =>
|
||||
updateEquipment(equipment.id, {
|
||||
inventoryNumber,
|
||||
name,
|
||||
manufacturer: manufacturer || undefined,
|
||||
location: location || undefined,
|
||||
purchaseDate: purchaseDate
|
||||
? new Date(purchaseDate).toISOString()
|
||||
: undefined,
|
||||
maintenanceDate: maintenanceDate
|
||||
? new Date(maintenanceDate).toISOString()
|
||||
: undefined,
|
||||
condition,
|
||||
}),
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: ["equipment", equipment.id],
|
||||
});
|
||||
void queryClient.invalidateQueries({ queryKey: ["equipment"] });
|
||||
onSaved();
|
||||
},
|
||||
onError: (mutationError: Error) => {
|
||||
const message =
|
||||
mutationError.message || "Gegenstand konnte nicht gespeichert werden";
|
||||
setError(message);
|
||||
toast.error(message);
|
||||
},
|
||||
});
|
||||
|
||||
function handleSubmit(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
setError(null);
|
||||
mutation.mutate();
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="flex flex-col gap-4" onSubmit={handleSubmit}>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="equipment-edit-inventory-number">Inventarnummer</Label>
|
||||
<Input
|
||||
id="equipment-edit-inventory-number"
|
||||
required
|
||||
value={inventoryNumber}
|
||||
onChange={(event) => setInventoryNumber(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="equipment-edit-name">Bezeichnung</Label>
|
||||
<Input
|
||||
id="equipment-edit-name"
|
||||
required
|
||||
value={name}
|
||||
onChange={(event) => setName(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="equipment-edit-manufacturer">Hersteller</Label>
|
||||
<Input
|
||||
id="equipment-edit-manufacturer"
|
||||
value={manufacturer}
|
||||
onChange={(event) => setManufacturer(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="equipment-edit-location">Standort</Label>
|
||||
<Input
|
||||
id="equipment-edit-location"
|
||||
value={location}
|
||||
onChange={(event) => setLocation(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<div className="flex flex-1 flex-col gap-2">
|
||||
<Label htmlFor="equipment-edit-purchase-date">Kaufdatum</Label>
|
||||
<Input
|
||||
id="equipment-edit-purchase-date"
|
||||
type="date"
|
||||
value={purchaseDate}
|
||||
onChange={(event) => setPurchaseDate(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col gap-2">
|
||||
<Label htmlFor="equipment-edit-maintenance-date">Wartungsdatum</Label>
|
||||
<Input
|
||||
id="equipment-edit-maintenance-date"
|
||||
type="date"
|
||||
value={maintenanceDate}
|
||||
onChange={(event) => setMaintenanceDate(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="equipment-edit-condition">Zustand</Label>
|
||||
<Select
|
||||
value={condition}
|
||||
onValueChange={(value) =>
|
||||
setCondition((value ?? condition) as EquipmentCondition)
|
||||
}
|
||||
>
|
||||
<SelectTrigger id="equipment-edit-condition" className="w-full">
|
||||
<SelectValue>{EQUIPMENT_CONDITION_LABELS[condition]}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{EQUIPMENT_CONDITIONS.map((option) => (
|
||||
<SelectItem key={option} value={option}>
|
||||
{EQUIPMENT_CONDITION_LABELS[option]}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{error ? (
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<Button type="submit" disabled={mutation.isPending}>
|
||||
{mutation.isPending ? "Speichert…" : "Speichern"}
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
export function EquipmentEditDialog({ equipment }: { equipment: Equipment }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger
|
||||
render={
|
||||
<Button variant="outline" size="sm">
|
||||
<Pencil className="size-3.5" />
|
||||
Bearbeiten
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Gegenstand bearbeiten</DialogTitle>
|
||||
</DialogHeader>
|
||||
{open ? (
|
||||
<EquipmentEditForm
|
||||
equipment={equipment}
|
||||
onSaved={() => setOpen(false)}
|
||||
/>
|
||||
) : null}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { Wrench } from "lucide-react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
import { fetchEquipment, updateEquipment } from "@/lib/equipment/api";
|
||||
import { EquipmentEditDialog } from "./equipment-edit-dialog";
|
||||
import { EquipmentCondition } from "@/lib/equipment/types";
|
||||
import { EQUIPMENT_CONDITIONS, EQUIPMENT_CONDITION_LABELS } from "@/lib/equipment/labels";
|
||||
import { statusTone } from "@/lib/status-tone";
|
||||
@@ -60,9 +61,12 @@ export default function EquipmentDetailPage() {
|
||||
</span>
|
||||
<CardTitle>{equipment.name}</CardTitle>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant={statusTone(equipment.condition)}>
|
||||
{EQUIPMENT_CONDITION_LABELS[equipment.condition]}
|
||||
</Badge>
|
||||
<EquipmentEditDialog equipment={equipment} />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-3 text-sm">
|
||||
<p>
|
||||
|
||||
@@ -5,11 +5,19 @@ import { Pencil } from "lucide-react";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
import { updateProduct } from "@/lib/products/api";
|
||||
import { Product } from "@/lib/products/types";
|
||||
import { Product, ProductStatus } from "@/lib/products/types";
|
||||
import { PRODUCT_STATUS_LABELS } from "@/lib/products/labels";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -28,6 +36,7 @@ export function EditProductDialog({ product }: { product: Product }) {
|
||||
const [packagingSize, setPackagingSize] = useState(product.packagingSize?.toString() ?? "");
|
||||
const [weight, setWeight] = useState(product.weight?.toString() ?? "");
|
||||
const [salesUnit, setSalesUnit] = useState(product.salesUnit ?? "");
|
||||
const [status, setStatus] = useState<ProductStatus>(product.status);
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: () =>
|
||||
@@ -38,6 +47,7 @@ export function EditProductDialog({ product }: { product: Product }) {
|
||||
packagingSize: packagingSize ? Number(packagingSize) : undefined,
|
||||
weight: weight ? Number(weight) : undefined,
|
||||
salesUnit: salesUnit || undefined,
|
||||
status,
|
||||
}),
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["products"] });
|
||||
@@ -111,6 +121,28 @@ export function EditProductDialog({ product }: { product: Product }) {
|
||||
onChange={(event) => setWeight(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="product-edit-status">Status</Label>
|
||||
<Select
|
||||
value={status}
|
||||
onValueChange={(value) =>
|
||||
setStatus((value ?? status) as ProductStatus)
|
||||
}
|
||||
>
|
||||
<SelectTrigger id="product-edit-status" className="w-full">
|
||||
<SelectValue>{PRODUCT_STATUS_LABELS[status]}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{(Object.keys(PRODUCT_STATUS_LABELS) as ProductStatus[]).map(
|
||||
(option) => (
|
||||
<SelectItem key={option} value={option}>
|
||||
{PRODUCT_STATUS_LABELS[option]}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="product-edit-sales-unit">Verkaufseinheit</Label>
|
||||
<Input
|
||||
|
||||
@@ -12,6 +12,15 @@ import { CreateQueenEvaluationInput, QueenEvaluation } from "@/lib/queens/types"
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { fetchColonies } from "@/lib/colonies/api";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
@@ -32,6 +41,10 @@ const CRITERIA: { key: keyof Omit<CreateQueenEvaluationInput, "date" | "colonyId
|
||||
{ key: "honeyYield", label: "Honigertrag" },
|
||||
];
|
||||
|
||||
/** Sentinel for "no colony": the rating may stand on its own, and an empty
|
||||
* select value would render the placeholder rather than a choosable entry. */
|
||||
const NO_COLONY = "__none__";
|
||||
|
||||
function todayIsoDate(): string {
|
||||
return new Date().toISOString().slice(0, 10);
|
||||
}
|
||||
@@ -58,8 +71,15 @@ function EditEvaluationDialog({
|
||||
]),
|
||||
),
|
||||
);
|
||||
const [colonyId, setColonyId] = useState(evaluation.colonyId ?? NO_COLONY);
|
||||
const [notes, setNotes] = useState(evaluation.notes ?? "");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const { data: colonies } = useQuery({
|
||||
queryKey: ["colonies"],
|
||||
queryFn: () => fetchColonies(),
|
||||
});
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: () =>
|
||||
updateQueenEvaluation(evaluation.id, {
|
||||
@@ -70,6 +90,8 @@ function EditEvaluationDialog({
|
||||
swarmingTendency: Number(scores.swarmingTendency),
|
||||
broodPattern: Number(scores.broodPattern),
|
||||
honeyYield: Number(scores.honeyYield),
|
||||
colonyId: colonyId === NO_COLONY ? undefined : colonyId,
|
||||
notes: notes || undefined,
|
||||
}),
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({
|
||||
@@ -136,6 +158,38 @@ function EditEvaluationDialog({
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor={`evaluation-edit-colony-${evaluation.id}`}>Volk</Label>
|
||||
<Select
|
||||
value={colonyId}
|
||||
onValueChange={(value) => setColonyId(value ?? NO_COLONY)}
|
||||
>
|
||||
<SelectTrigger
|
||||
id={`evaluation-edit-colony-${evaluation.id}`}
|
||||
className="w-full"
|
||||
>
|
||||
<SelectValue placeholder="Kein Volk" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={NO_COLONY}>Kein Volk</SelectItem>
|
||||
{(colonies ?? []).map((colony) => (
|
||||
<SelectItem key={colony.id} value={colony.id}>
|
||||
{colony.code}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor={`evaluation-edit-notes-${evaluation.id}`}>
|
||||
Notizen
|
||||
</Label>
|
||||
<Textarea
|
||||
id={`evaluation-edit-notes-${evaluation.id}`}
|
||||
value={notes}
|
||||
onChange={(event) => setNotes(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{error ? (
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{error}
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
|
||||
import { FormEvent, useState } from "react";
|
||||
import { Pencil } from "lucide-react";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { updateTaskTemplate } from "@/lib/task-templates/api";
|
||||
import { TaskTemplate, TaskTemplateRecurrenceType } from "@/lib/task-templates/types";
|
||||
import { TaskPriority } from "@/lib/tasks/types";
|
||||
import { TASK_PRIORITIES, TASK_PRIORITY_LABELS } from "@/lib/tasks/labels";
|
||||
import { fetchUsers } from "@/lib/users/api";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
@@ -25,6 +28,10 @@ import {
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
|
||||
/** Sentinel for "nobody assigned" - an empty select value renders as the
|
||||
* placeholder instead of a choosable entry. */
|
||||
const NO_ASSIGNEE = "__none__";
|
||||
|
||||
const RECURRENCE_LABELS: Record<TaskTemplateRecurrenceType, string> = {
|
||||
YEARLY: "Jährlich",
|
||||
MONTHLY: "Monatlich",
|
||||
@@ -61,8 +68,23 @@ export function EditTaskTemplateDialog({ template }: { template: TaskTemplate })
|
||||
template.recurrenceDayOfMonth?.toString() ?? "1",
|
||||
);
|
||||
const [intervalDays, setIntervalDays] = useState(template.intervalDays?.toString() ?? "14");
|
||||
const [priority, setPriority] = useState<TaskPriority>(
|
||||
template.priority ?? "NORMAL",
|
||||
);
|
||||
const [assignedUserId, setAssignedUserId] = useState(
|
||||
template.assignedUserId ?? NO_ASSIGNEE,
|
||||
);
|
||||
const [active, setActive] = useState(template.active);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Assigning needs users:read, which not every role has - if the request
|
||||
// fails the field is simply omitted rather than breaking the dialog.
|
||||
const { data: users } = useQuery({
|
||||
queryKey: ["users"],
|
||||
queryFn: () => fetchUsers(),
|
||||
retry: false,
|
||||
});
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: () =>
|
||||
updateTaskTemplate(template.id, {
|
||||
@@ -75,6 +97,10 @@ export function EditTaskTemplateDialog({ template }: { template: TaskTemplate })
|
||||
? Number(recurrenceDayOfMonth)
|
||||
: undefined,
|
||||
intervalDays: recurrenceType === "INTERVAL_DAYS" ? Number(intervalDays) : undefined,
|
||||
priority,
|
||||
assignedUserId:
|
||||
assignedUserId === NO_ASSIGNEE ? undefined : assignedUserId,
|
||||
active,
|
||||
}),
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["task-templates"] });
|
||||
@@ -190,6 +216,60 @@ export function EditTaskTemplateDialog({ template }: { template: TaskTemplate })
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="template-edit-priority">Priorität</Label>
|
||||
<Select
|
||||
value={priority}
|
||||
onValueChange={(value) =>
|
||||
setPriority((value ?? priority) as TaskPriority)
|
||||
}
|
||||
>
|
||||
<SelectTrigger id="template-edit-priority" className="w-full">
|
||||
<SelectValue>{TASK_PRIORITY_LABELS[priority]}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{TASK_PRIORITIES.map((option) => (
|
||||
<SelectItem key={option} value={option}>
|
||||
{TASK_PRIORITY_LABELS[option]}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{users ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="template-edit-assignee">Zugewiesen an</Label>
|
||||
<Select
|
||||
value={assignedUserId}
|
||||
onValueChange={(value) => setAssignedUserId(value ?? NO_ASSIGNEE)}
|
||||
>
|
||||
<SelectTrigger id="template-edit-assignee" className="w-full">
|
||||
<SelectValue placeholder="Niemand" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={NO_ASSIGNEE}>Niemand</SelectItem>
|
||||
{users.map((user) => (
|
||||
<SelectItem key={user.id} value={user.id}>
|
||||
{user.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
) : null}
|
||||
<label
|
||||
className="flex h-9 items-center gap-3 text-sm"
|
||||
htmlFor="template-edit-active"
|
||||
>
|
||||
<input
|
||||
id="template-edit-active"
|
||||
type="checkbox"
|
||||
className="size-4.5"
|
||||
checked={active}
|
||||
onChange={(event) => setActive(event.target.checked)}
|
||||
/>
|
||||
Aktiv – eine deaktivierte Vorlage erzeugt keine Aufgaben mehr
|
||||
</label>
|
||||
{error ? (
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{error}
|
||||
|
||||
@@ -5,8 +5,18 @@ import { Pencil } from "lucide-react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
import { updateTask } from "@/lib/tasks/api";
|
||||
import { Task, TaskPriority, TaskRelatedType } from "@/lib/tasks/types";
|
||||
import { TASK_PRIORITIES, TASK_PRIORITY_LABELS } from "@/lib/tasks/labels";
|
||||
import {
|
||||
Task,
|
||||
TaskPriority,
|
||||
TaskRelatedType,
|
||||
TaskStatus,
|
||||
} from "@/lib/tasks/types";
|
||||
import {
|
||||
TASK_PRIORITIES,
|
||||
TASK_PRIORITY_LABELS,
|
||||
TASK_STATUSES,
|
||||
TASK_STATUS_LABELS,
|
||||
} from "@/lib/tasks/labels";
|
||||
import { fetchUsers } from "@/lib/users/api";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -38,6 +48,7 @@ export function TaskEditDialog({ task }: { task: Task }) {
|
||||
const [assignedUserId, setAssignedUserId] = useState(task.assignedUserId ?? "");
|
||||
const [relatedType, setRelatedType] = useState<TaskRelatedType>(task.relatedType);
|
||||
const [relatedId, setRelatedId] = useState(task.relatedId ?? "");
|
||||
const [status, setStatus] = useState<TaskStatus>(task.status);
|
||||
|
||||
// Assigning a task requires users:read, which not every role has - if the
|
||||
// request fails, the assignee field below is simply omitted rather than
|
||||
@@ -58,6 +69,7 @@ export function TaskEditDialog({ task }: { task: Task }) {
|
||||
assignedUserId: assignedUserId || undefined,
|
||||
relatedType,
|
||||
relatedId: relatedType === "GENERAL" ? undefined : relatedId || undefined,
|
||||
status,
|
||||
}),
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["tasks"] });
|
||||
@@ -138,6 +150,30 @@ export function TaskEditDialog({ task }: { task: Task }) {
|
||||
</Select>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="task-edit-status">Status</Label>
|
||||
<Select
|
||||
value={status}
|
||||
onValueChange={(value) =>
|
||||
setStatus((value ?? status) as TaskStatus)
|
||||
}
|
||||
>
|
||||
<SelectTrigger id="task-edit-status" className="w-full">
|
||||
<SelectValue>{TASK_STATUS_LABELS[status]}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{TASK_STATUSES.map((option) => (
|
||||
<SelectItem key={option} value={option}>
|
||||
{TASK_STATUS_LABELS[option]}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Hier lässt sich eine versehentlich erledigte oder abgebrochene
|
||||
Aufgabe wieder öffnen.
|
||||
</p>
|
||||
</div>
|
||||
<TaskRelatedField
|
||||
idPrefix="task-edit-related"
|
||||
relatedType={relatedType}
|
||||
|
||||
Reference in new issue
Block a user