feat: queues have default team, tickets inherit it
- team_id on queues table (optional, can be overridden per-ticket) - Ticket creation auto-sets team_id from the queue's default - Queue admin form has team selector (scrip flow node 03) - Queue API (POST/PATCH) accepts team_id No enforcement — just a helpful default. Teams and queues are loosely coupled, not hierarchically locked. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -201,23 +201,27 @@ export default function AdminPage() {
|
||||
function QueuesTab() {
|
||||
const [queues, setQueues] = useState<Queue[]>([]);
|
||||
const [lifecycles, setLifecycles] = useState<Lifecycle[]>([]);
|
||||
const [teams, setTeams] = useState<Team[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
const [name, setName] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [lifecycleId, setLifecycleId] = useState("");
|
||||
const [teamId, setTeamId] = useState("");
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [saveError, setSaveError] = useState<string | null>(null);
|
||||
|
||||
const fetchQueues = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const [queueRes, lifecycleRes] = await Promise.all([getQueues(), getLifecycles()]);
|
||||
const [queueRes, lifecycleRes, teamsRes] = await Promise.all([getQueues(), getLifecycles(), getTeams()]);
|
||||
if (queueRes.error) setError(queueRes.error);
|
||||
else setQueues(queueRes.data ?? []);
|
||||
if (lifecycleRes.error) setError((prev) => prev || lifecycleRes.error);
|
||||
else setLifecycles(lifecycleRes.data ?? []);
|
||||
if (teamsRes.error) setError((prev) => prev || teamsRes.error);
|
||||
else setTeams(teamsRes.data ?? []);
|
||||
setLoading(false);
|
||||
}, []);
|
||||
|
||||
@@ -230,6 +234,7 @@ function QueuesTab() {
|
||||
setName("");
|
||||
setDescription("");
|
||||
setLifecycleId("");
|
||||
setTeamId("");
|
||||
setSaveError(null);
|
||||
};
|
||||
|
||||
@@ -238,6 +243,7 @@ function QueuesTab() {
|
||||
setName(queue.name);
|
||||
setDescription(queue.description ?? "");
|
||||
setLifecycleId(queue.lifecycle_id ?? "");
|
||||
setTeamId(queue.team_id ?? "");
|
||||
setSaveError(null);
|
||||
};
|
||||
|
||||
@@ -249,6 +255,7 @@ function QueuesTab() {
|
||||
name: name.trim(),
|
||||
description: description.trim() || null,
|
||||
lifecycle_id: lifecycleId || null,
|
||||
team_id: teamId || null,
|
||||
};
|
||||
const { data, error } = editingId
|
||||
? await updateQueue(editingId, payload)
|
||||
@@ -356,6 +363,19 @@ function QueuesTab() {
|
||||
</Select>
|
||||
</div>
|
||||
</ScripFlowNode>
|
||||
<ScripFlowNode label="03" title="Default team" description="New tickets in this queue inherit this team. Can be changed per-ticket.">
|
||||
<div className="grid gap-1.5">
|
||||
<Select value={teamId || "_none"} onValueChange={(value) => setTeamId(value === "_none" || !value ? "" : value)}>
|
||||
<SelectTrigger id="q-team"><SelectValue placeholder="No default team" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="_none">No default team</SelectItem>
|
||||
{teams.map((team) => (
|
||||
<SelectItem key={team.id} value={team.id}>{team.name}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</ScripFlowNode>
|
||||
{saveError && <div className="text-sm text-destructive">{saveError}</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user