-
Notifications
You must be signed in to change notification settings - Fork 714
feat: enhance agent profile with availability status and signature #3002
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 5 commits
6d4192f
e52454d
910edf4
3171e12
54ce1dd
6b4bc87
e1d108a
29732e4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -163,6 +163,8 @@ | |
| </template> | ||
|
|
||
| <script setup lang="ts"> | ||
|
|
||
|
|
||
| import { | ||
| AttachmentItem, | ||
| MultiSelectInput, | ||
|
|
@@ -190,7 +192,8 @@ import { | |
| toast, | ||
| } from "frappe-ui"; | ||
| import { useOnboarding } from "frappe-ui/frappe"; | ||
| import { computed, nextTick, onBeforeUnmount, ref, watch } from "vue"; | ||
| import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue"; | ||
|
|
||
| import SavedReplyIcon from "./icons/SavedReplyIcon.vue"; | ||
|
|
||
| const editorRef = ref(null); | ||
|
|
@@ -242,8 +245,15 @@ const newEmail = useStorage<null | string>( | |
| null | ||
| ); | ||
| const { updateOnboardingStep } = useOnboarding("helpdesk"); | ||
| const { isManager } = useAuthStore(); | ||
|
|
||
| const auth = useAuthStore(); | ||
| const getSignature = createResource({ | ||
| url: "frappe.client.get_value", | ||
| params: { | ||
| doctype: "HD Agent", | ||
| filters: { user: auth.userId }, | ||
| fieldname: "signature", | ||
| }, | ||
| }); | ||
| // Initialize typing composable | ||
| const { onUserType, cleanup } = useTyping(props.ticketId); | ||
|
|
||
|
|
@@ -301,10 +311,6 @@ const sendMail = createResource({ | |
| onSuccess: () => { | ||
| resetState(); | ||
| emit("submit"); | ||
|
|
||
| if (isManager) { | ||
| updateOnboardingStep("reply_on_ticket"); | ||
| } | ||
| }, | ||
| debounce: 300, | ||
| }); | ||
|
|
@@ -345,7 +351,7 @@ async function removeAttachment(attachment) { | |
| await removeAttachmentFromServer(attachment.name); | ||
| } | ||
|
|
||
| function addToReply( | ||
| async function addToReply( | ||
| body: string, | ||
| toEmails: string[], | ||
| ccEmails: string[], | ||
|
|
@@ -354,6 +360,24 @@ function addToReply( | |
| toEmailsClone.value = toEmails; | ||
| ccEmailsClone.value = ccEmails; | ||
| bccEmailsClone.value = bccEmails; | ||
|
|
||
| let signature = ""; | ||
|
|
||
| try { | ||
| const res = await getSignature.fetch(); | ||
| signature = res?.signature || ""; | ||
| } catch (err) { | ||
|
||
|
|
||
| } | ||
|
|
||
| const content = signature | ||
| ? `${body}<p><br/></p><p>${signature}</p>` | ||
| : body; | ||
|
|
||
| editorRef.value.editor | ||
| .chain() | ||
| .clearContent() | ||
| .insertContent(content) | ||
| const repliedMessage = `<p class="reply-to-content"><p><blockquote>${body}</blockquote>`; | ||
| editorRef.value.editor | ||
| .chain() | ||
|
|
@@ -390,6 +414,35 @@ const editor = computed(() => { | |
| return editorRef.value.editor; | ||
| }); | ||
|
|
||
| watch( | ||
| () => props.editable, | ||
| async (isEditable) => { | ||
| if (isEditable) { | ||
| try { | ||
| const res = await getSignature.fetch(); | ||
| const signature = String(res.signature || "").trim(); | ||
|
|
||
|
|
||
| // Always set signature if box is empty or null | ||
| if (signature && (!newEmail.value || newEmail.value.trim() === "")) { | ||
| newEmail.value = `<p><br/></p><p>${signature.replace(/\n/g, "<br/>")}</p>`; | ||
|
|
||
| } else { | ||
| // Force clear localStorage and set signature | ||
| newEmail.value = null; | ||
| setTimeout(() => { | ||
| newEmail.value = `<p><br/></p><p>${signature.replace(/\n/g, "<br/>")}</p>`; | ||
|
|
||
| }, 100); | ||
| } | ||
| } catch (err) { | ||
|
|
||
| } | ||
| } | ||
| }, | ||
| { immediate: true } | ||
| ); | ||
|
|
||
| defineExpose({ | ||
| addToReply, | ||
| editor, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -108,6 +108,24 @@ | |
| maxlength="40" | ||
| v-model="profile.lastName" | ||
| /> | ||
|
|
||
|
|
||
| <FormControl | ||
| class="w-full" | ||
| type="select" | ||
| :label="__('Availability Status')" | ||
| :options="['Available', 'Away']" | ||
| v-model="profile.availabilityStatus" | ||
| /> | ||
| <FormControl | ||
| class="w-full md:col-span-2" | ||
| type="textarea" | ||
| :label="__('Signature')" | ||
| v-model="profile.signature" | ||
| /> | ||
|
||
|
|
||
|
|
||
|
|
||
| </div> | ||
| <div class="flex items-center justify-between mt-6"> | ||
| <div class="flex flex-col gap-1"> | ||
|
|
@@ -176,6 +194,8 @@ const profile = ref({ | |
| userImage: auth.userImage, | ||
| firstName: auth.userFirstName, | ||
| lastName: auth.userLastName, | ||
| availabilityStatus: "Available", | ||
| signature: "", | ||
| }); | ||
| const showChangePasswordModal = ref(false); | ||
| const language = ref(auth.language); | ||
|
|
@@ -214,6 +234,8 @@ const agentData = createResource({ | |
| firstName: fullName[0], | ||
| lastName: fullName[1] || "", | ||
| userImage: data.user_image, | ||
| availabilityStatus: data.availability_status, | ||
| signature: data.signature, | ||
|
||
| }; | ||
| }, | ||
| }); | ||
|
|
@@ -232,6 +254,8 @@ const setAgent = createResource({ | |
| fieldname: { | ||
| agent_name: `${profile.value.firstName} ${profile.value.lastName}`, | ||
| user_image: profile.value.userImage, | ||
| availability_status: profile.value.availabilityStatus, | ||
| signature: profile.value.signature, | ||
| }, | ||
| }; | ||
| }, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,9 @@ | |
| "user", | ||
| "agent_name", | ||
| "user_image", | ||
| "is_active" | ||
| "is_active", | ||
| "availability_status", | ||
| "signature" | ||
| ], | ||
| "fields": [ | ||
| { | ||
|
|
@@ -34,6 +36,21 @@ | |
| "in_list_view": 1, | ||
| "label": "Is Active" | ||
| }, | ||
| { | ||
| "default": "Available", | ||
| "fieldname": "availability_status", | ||
| "fieldtype": "Select", | ||
| "in_list_view": 1, | ||
| "label": "Availability Status", | ||
| "options": "Available\nAway", | ||
| "reqd": 1 | ||
| }, | ||
| { | ||
| "description": "automatically added in reply section", | ||
| "fieldname": "signature", | ||
| "fieldtype": "Small Text", | ||
| "label": "Signature" | ||
|
Comment on lines
12
to
+52
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure that these are also shown in the "Profile" tab shown in "Settings Modal" both signature and status field
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Fixed in commit 910edf4 — added both fields to Profile.vue" |
||
| }, | ||
| { | ||
| "fieldname": "user_image", | ||
| "fieldtype": "Attach Image", | ||
|
|
@@ -42,7 +59,7 @@ | |
| ], | ||
| "index_web_pages_for_search": 1, | ||
| "links": [], | ||
| "modified": "2026-02-02 11:15:47.402850", | ||
| "modified": "2026-02-09 22:08:22.055831", | ||
| "modified_by": "Administrator", | ||
| "module": "Helpdesk", | ||
| "name": "HD Agent", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,4 +6,4 @@ frappe.ui.form.on("HD Ticket", { | |
| if (frm.is_new()) return; | ||
| frm.call("mark_seen"); | ||
| }, | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import frappe | ||
| from frappe import _ | ||
|
|
||
|
|
||
| def validate_agent_availability(doc, method=None): | ||
| if not doc.allocated_to: | ||
| return | ||
|
|
||
| status = frappe.db.get_value( | ||
| "HD Agent", | ||
| {"user": doc.allocated_to}, | ||
| "availability_status", | ||
| ) | ||
|
|
||
| if status == "Away": | ||
| frappe.msgprint( | ||
| msg=_("This agent is marked as Away "), | ||
| title=_("Agent Unavailable"), | ||
| ) | ||
| frappe.log_error("Ticket assigned to Away agent", "Agent Unavailable") | ||
|
||
|
|
||
|
|
||
|
|
||
|
|
||
| # removed user_query_condition | ||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should not be Async