1<template>
2 <SettingsLayoutBase
3 :back-label="teamData.name || __('New Team')"
4 :on-back="goBack"
5 :dirty="isDirty"
6 >
7 <template #header-actions>
8 <div class="flex items-center gap-4">
9 <div class="flex items-center gap-2 cursor-pointer">
10 <Switch v-model="teamData.enabled" />
11 <span class="text-sm-medium text-ink-gray-7">
12 {{ __("Enabled") }}
13 </span>
14 </div>
15 <Button
16 :label="__('Save')"
17 variant="solid"
18 @click="saveTeam()"
19 :disabled="!isDirty"
20 :loading="teamsList.insert.loading"
21 />
22 </div>
23 </template>
24 <template #content>
25 <div class="flex flex-col gap-4">
26 <div class="space-y-1.5">
27 <FormControl
28 v-model="teamData.name"
29 :label="__('Team Name')"
30 :placeholder="__('Product Experts')"
31 maxlength="50"
32 required
33 @change="validateData('name')"
34 />
35 <ErrorMessage :message="errors.name" />
36 </div>
37 <div class="flex flex-col gap-1.5">
38 <FormLabel :label="__('Members')" required />
39 <div class="flex">
40 <AgentSelector
41 v-model="teamData.agents"
42 @change="validateData('agents')"
43 />
44 </div>
45 <ErrorMessage :message="errors.agents" />
46 </div>
47 </div>
48 </template>
49 </SettingsLayoutBase>
50 <ConfirmDialog
51 v-model="showConfirmDialog.show"
52 :title="showConfirmDialog.title"
53 :message="showConfirmDialog.message"
54 :onConfirm="showConfirmDialog.onConfirm"
55 />
56</template>
57
58<script setup lang="ts">
59import { computed, inject, onMounted, ref } from "vue";
60import SettingsLayoutBase from "@/components/layouts/SettingsLayoutBase.vue";
61import {
62 Badge,
63 ErrorMessage,
64 FormControl,
65 FormLabel,
66 Switch,
67 toast,
68} from "frappe-ui";
69import { __ } from "@/translation";
70import AgentSelector from "./components/AgentSelector.vue";
71import { useAgentStore } from "@/stores/agent";
72import { TeamListResourceSymbol } from "@/types";
73import ConfirmDialog from "../../ConfirmDialog.vue";
74
75interface E {
76 (event: "update:step", step: string, team?: string): void;
77}
78
79const emit = defineEmits<E>();
80
81const teamsList = inject(TeamListResourceSymbol);
82const { agents } = useAgentStore();
83
84const isDirty = computed(() => {
85 return teamData.value.name || teamData.value.agents.length;
86});
87const showConfirmDialog = ref({
88 show: false,
89 title: "",
90 message: "",
91 onConfirm: () => {},
92});
93
94const teamData = ref({
95 name: "",
96 agents: [],
97 enabled: true,
98});
99
100const errors = ref({
101 name: "",
102 agents: "",
103});
104
105const goBack = () => {
106 const confirmDialogInfo = {
107 show: true,
108 title: __("Unsaved changes"),
109 message: __(
110 "Are you sure you want to go back? Unsaved changes will be lost."
111 ),
112 onConfirm: goBack,
113 };
114 if (isDirty.value && !showConfirmDialog.value.show) {
115 showConfirmDialog.value = confirmDialogInfo;
116 return;
117 }
118
119 // Workaround fix for settings modal not closing after going back
120 setTimeout(() => {
121 emit("update:step", "team-list");
122 }, 250);
123 showConfirmDialog.value.show = false;
124};
125
126const saveTeam = () => {
127 validateData();
128 if (Object.values(errors.value).some((error) => error)) {
129 toast.error(__("Please fill all required fields"));
130 return;
131 }
132 teamsList.insert.submit(
133 {
134 team_name: teamData.value.name,
135 users: teamData.value.agents.map((agent) => ({ user: agent })),
136 disabled: !teamData.value.enabled,
137 },
138 {
139 onSuccess: (data) => {
140 toast.success(__("Team created successfully."));
141 emit("update:step", "team-edit", data.name);
142 },
143 }
144 );
145};
146
147const validateData = (key?: string) => {
148 const validateField = (field: string) => {
149 if (key && field !== key) return;
150
151 switch (field) {
152 case "name":
153 teamData.value.name?.length == 0
154 ? (errors.value.name = "Name is required")
155 : (errors.value.name = "");
156 break;
157 case "agents":
158 teamData.value.agents.length == 0
159 ? (errors.value.agents = "At least one team member is required")
160 : (errors.value.agents = "");
161 break;
162 }
163 };
164
165 if (key) {
166 validateField(key);
167 } else {
168 (Object.keys(errors.value) as string[]).forEach(validateField);
169 }
170
171 return errors.value;
172};
173
174onMounted(() => {
175 if (agents.loading || agents.data?.length || agents.list.promise) {
176 return;
177 }
178 agents.fetch();
179});
180</script>
181