diff --git a/beams/beams/workspace/ticket/ticket.json b/beams/beams/workspace/ticket/ticket.json new file mode 100644 index 000000000..fe622a61c --- /dev/null +++ b/beams/beams/workspace/ticket/ticket.json @@ -0,0 +1,41 @@ +{ + "charts": [], + "content": "[{\"id\":\"HGm0aWWSd-\",\"type\":\"header\",\"data\":{\"text\":\"Ticket\",\"col\":12}},{\"id\":\"7xhmgaoB-z\",\"type\":\"custom_block\",\"data\":{\"custom_block_name\":\"Ticket Summary\",\"col\":12}}]", + "creation": "2025-11-22 17:54:26.682582", + "custom_blocks": [ + { + "custom_block_name": "Ticket Summary", + "label": "Ticket Summary" + } + ], + "docstatus": 0, + "doctype": "Workspace", + "for_user": "", + "hide_custom": 0, + "icon": "support", + "idx": 0, + "indicator_color": "green", + "is_hidden": 0, + "label": "Ticket", + "links": [], + "modified": "2025-11-22 17:56:17.456197", + "modified_by": "Administrator", + "module": "BEAMS", + "name": "Ticket", + "number_cards": [], + "owner": "Administrator", + "parent_page": "", + "public": 1, + "quick_lists": [], + "roles": [ + { + "role": "Agent" + }, + { + "role": "Agent Manager" + } + ], + "sequence_id": 5.0, + "shortcuts": [], + "title": "Ticket" +} \ No newline at end of file diff --git a/beams/fixtures/custom_html_block.json b/beams/fixtures/custom_html_block.json index c51af5e6b..3e1bcaa41 100644 --- a/beams/fixtures/custom_html_block.json +++ b/beams/fixtures/custom_html_block.json @@ -74,5 +74,29 @@ ], "script": "(function () {\n if (typeof root_element === \"undefined\" || !root_element) {\n console.error(\"The 'root_element' variable is not available.\");\n return;\n }\n\n // ---------------------------\n // Manual Tab Switching\n // ---------------------------\n // This code uses data-toggle, which is for Bootstrap 4.\n // However, it's manually handling the logic, so it will still work even with Bootstrap 5.\n const tabButtons = root_element.querySelectorAll(\n 'button[data-bs-toggle=\"tab\"]'\n );\n tabButtons.forEach((tabBtn) => {\n tabBtn.addEventListener(\"click\", function (e) {\n e.preventDefault();\n\n const parentCard = this.closest(\".card\");\n parentCard\n .querySelectorAll(\".nav-link\")\n .forEach((btn) => btn.classList.remove(\"active\"));\n parentCard\n .querySelectorAll(\".tab-pane\")\n .forEach((pane) => pane.classList.remove(\"show\", \"active\"));\n\n this.classList.add(\"active\");\n const tabTarget = this.getAttribute(\"data-bs-target\");\n if (tabTarget) {\n const pane = parentCard.querySelector(tabTarget);\n if (pane) {\n pane.classList.add(\"show\", \"active\");\n }\n }\n });\n });\n\n // ---------------------------\n // Important Links - Holiday List Modal\n // ---------------------------\n root_element.addEventListener(\"click\", function (e) {\n let link = e.target.closest(\"a.open-holiday-list\");\n if (link) {\n e.preventDefault();\n\n // Only append modal if it isn't there already\n if ($(\"#holidayListModal\").length === 0) {\n $(\"body\").append(`\n
\n
\n
\n
\n

Holiday List 2025

\n \n
\n
\n
Loading...
\n
\n
\n
\n
\n `);\n }\n\n $(\"#holidayListModal\").modal(\"show\");\n $(\"#holiday-list-content\").html(\"Loading...\");\n\n // Get Holiday List assigned to Employee\n frappe.call({\n method: \"frappe.client.get_value\",\n args: {\n doctype: \"Employee\",\n filters: { user_id: frappe.session.user },\n fieldname: \"holiday_list\",\n },\n callback: function (res) {\n if (res && res.message && res.message.holiday_list) {\n let holiday_list_name = res.message.holiday_list;\n frappe.call({\n method: \"beams.api.workspace.get_holidays_by_list_name\",\n args: { holiday_list_name: holiday_list_name },\n callback: function (r) {\n if (r.message && r.message.length > 0) {\n let html = `\n \n \n \n \n \n \n \n \n \n \n `;\n r.message.forEach(function (holiday, index) {\n html += `\n \n \n \n \n \n \n `;\n });\n html += \"
SL. No.Holiday ForHoliday DateNature of Holiday
${index + 1}${\n holiday.description || \"\"\n }${frappe.format(\n holiday.holiday_date,\n { fieldtype: \"Date\" }\n )}${\n holiday.weekly_off\n ? \"Statutory Holiday\"\n : \"Festive\"\n }
\";\n $(\"#holiday-list-content\").html(html);\n } else {\n $(\"#holiday-list-content\").html(\n `

No holidays found in Holiday List: ${holiday_list_name}.

`\n );\n }\n },\n });\n } else {\n $(\"#holiday-list-content\").html(\n \"

No Holiday List is assigned to your Employee record.

\"\n );\n }\n },\n });\n }\n });\n\n // ---------------------------\n // Leave Tab - Load Data\n // ---------------------------\n root_element\n .querySelector(\"#leave-tab\")\n .addEventListener(\"click\", function () {\n const leaveContainer = root_element.querySelector(\"#leave\");\n leaveContainer.innerHTML = '

Loading leaves...

';\n\n frappe.call({\n method: \"beams.api.workspace.get_absent_days\",\n args: {}, // No user_id to get all employees on leave today\n callback: function (r) {\n if (r.message && r.message.length > 0) {\n let html = \"\";\n\n // Fetch employee details for each leave record\n let employeePromises = r.message.map((row) => {\n return new Promise((resolve) => {\n frappe.call({\n method: \"frappe.client.get_value\",\n args: {\n doctype: \"Employee\",\n filters: { name: row.employee },\n fieldname: [\n \"employee_name\",\n \"image\",\n \"department\",\n \"designation\",\n ],\n },\n callback: function (empRes) {\n resolve({\n ...row,\n employee_name:\n empRes.message?.employee_name || row.employee,\n image:\n empRes.message?.image ||\n \"/assets/frappe/images/default-avatar.png\",\n department: empRes.message?.department || \"\",\n designation: empRes.message?.designation || \"\",\n });\n },\n });\n });\n });\n\n Promise.all(employeePromises).then((enrichedLeaves) => {\n enrichedLeaves.forEach((row) => {\n const statusBadge =\n row.workflow_state || row.status || \"Pending\";\n const statusClass =\n statusBadge === \"Approved\"\n ? \"common_success\"\n : statusBadge === \"Rejected\"\n ? \"text-danger\"\n : \"common_applied\";\n\n html += `\n
\n
\n \"Avatar\"\n
\n
${row.employee_name}
\n
${row.leave_type}
\n
${frappe.format(\n row.from_date,\n { fieldtype: \"Date\" }\n )} to ${frappe.format(row.to_date, {\n fieldtype: \"Date\",\n })}
\n
\n
\n
\n ${statusBadge}\n
\n
`;\n });\n leaveContainer.innerHTML = html;\n });\n } else {\n leaveContainer.innerHTML =\n '

No one is on leave today 🎉

';\n }\n },\n });\n });\n\n // ---------------------------\n // Birthday Tab\n // ---------------------------\n root_element\n .querySelector(\"#birthday-tab\")\n .addEventListener(\"click\", function () {\n const birthdayContainer = root_element.querySelector(\"#birthday\");\n birthdayContainer.innerHTML =\n '

Loading birthdays...

';\n\n frappe.call({\n method: \"beams.api.workspace.get_today_birthdays\",\n callback: function (r) {\n if (r.message) {\n let html = \"\";\n r.message.forEach((row) => {\n const img =\n row.image || \"/assets/frappe/images/default-avatar.png\";\n html += `\n
\n
\n \"Avatar\"\n
\n
${row.employee_name}
\n
(${\n row.department || \"\"\n }${\n row.designation ? \", \" + row.designation : \"\"\n })
\n
\n
\n
\n \"Birthday\"\n
\n
`;\n });\n if (!html) {\n html = `
No birthdays today 🎉
`;\n }\n birthdayContainer.innerHTML = html;\n }\n },\n });\n });\n\n // ---------------------------\n // Anniversary Tab\n // ---------------------------\n root_element\n .querySelector(\"#anniversary-tab\")\n .addEventListener(\"click\", function () {\n const anniversaryContainer = root_element.querySelector(\"#anniversary\");\n anniversaryContainer.innerHTML =\n '

Loading anniversaries...

';\n\n frappe.call({\n method: \"beams.api.workspace.get_today_anniversaries\",\n callback: function (r) {\n if (r.message) {\n let html = \"\";\n r.message.forEach((row) => {\n const img =\n row.image || \"/assets/frappe/images/default-avatar.png\";\n html += `\n
\n
\n \"Avatar\"\n
\n
${row.employee_name}
\n
(${\n row.department || \"\"\n }${\n row.designation ? \", \" + row.designation : \"\"\n })
\n
\n
\n
\n \"Anniversary\"\n
\n
`;\n });\n if (!html) {\n html = `
No anniversaries today 🎉
`;\n }\n anniversaryContainer.innerHTML = html;\n }\n },\n });\n });\n root_element.querySelector(\"#leave-tab\").click();\n})();\n\n\n\n\n// Show Available Leaves\nlet btn = root_element.querySelector(\".view-leave\");\n\nif (btn) {\n btn.addEventListener(\"click\", function () {\n\n // 1. Get Employee from current user\n frappe.call({\n method: \"frappe.client.get_list\",\n args: {\n doctype: \"Employee\",\n filters: { user_id: frappe.session.user },\n fields: [\"name\"],\n limit_page_length: 1\n },\n callback: function (emp_res) {\n let employee =\n emp_res.message?.length ? emp_res.message[0].name : null;\n\n if (!employee) {\n frappe.msgprint(\"No Employee record linked to your User.\");\n return;\n }\n\n // 2. Fetch Leave Details using Employee ID\n frappe.call({\n method: \"hrms.hr.doctype.leave_application.leave_application.get_leave_details\",\n args: {\n employee: employee,\n date: frappe.datetime.get_today()\n },\n callback: function (r) {\n let html = \"\";\n let data = r.message?.leave_allocation || {};\n\n if (Object.keys(data).length > 0) {\n html = `\n \n \n \n \n \n \n \n \n \n \n \n \n `;\n\n Object.entries(data).forEach(([key, value]) => {\n let color =\n value.remaining_leaves > 0\n ? \"green\"\n : \"red\";\n\n html += `\n \n \n \n \n \n \n \n \n `;\n });\n\n html += `
Leave TypeTotal Allocated LeavesExpired LeavesUsed LeavesLeaves Pending ApprovalAvailable Leaves
${key}${value.total_leaves}${value.expired_leaves}${value.leaves_taken}${value.leaves_pending_approval}${value.remaining_leaves}
`;\n } else {\n html = `

No leaves have been allocated.

`;\n }\n\n // 3. Show Dialog\n let d = new frappe.ui.Dialog({\n title: \"Available Leaves\",\n size: \"extra-large\",\n primary_action_label: \"Close\",\n primary_action: () => d.hide()\n });\n\n d.$body.html(html);\n d.show();\n }\n });\n }\n });\n });\n}\n", "style": "body {\r\n font-size: 14px;\r\n font-family: \"Inter\", sans-serif;\r\n font-optical-sizing: auto; \r\n font-style: normal;\r\n}\r\n.welcome_msg {\r\n border: 1px solid #E2E8F0;\r\n}\r\n.welcome_title {\r\n color: #0F172B;\r\n font-size: 20px;\r\n font-weight: 500;\r\n}\r\n.welcome_msg p {\r\n color: #45556C;\r\n font-size: 14px;\r\n margin: 0;\r\n padding: 0;\r\n}\r\n.welcome_widget {\r\n margin: 0 0 0 -30px;\r\n}\r\n.mood_widget p {\r\n color: #0F172B;\r\n font-size: 12px;\r\n}\r\nh5 { \r\n margin: 0;\r\n padding: 0;\r\n font-size: 18px;\r\n font-weight: bold;\r\n color: #0F172B;\r\n}\r\n.tabs_widget li {\r\n width: 33.3333%;\r\n}\r\n.common_tabs_widget {\r\n background: #F1F5F9;\r\n padding: 5px;\r\n border-radius: 4px;\r\n}\r\n.common-sm-font {\r\n font-size: 12px;\r\n}\r\n.btn-sm {\r\n font-size: 12px;\r\n}\r\n.text-muted {\r\n color: #8b93ab !important;\r\n}\r\n.time-label {\r\n color: #0F172B;\r\n font-size: 20px;\r\n}\r\n.card {\r\n border: 1px solid #eee;\r\n border-radius: 12px;\r\n}\r\n.small-box {\r\n background: #fff;\r\n border-radius: 12px;\r\n padding: 15px;\r\n text-align: center;\r\n}\r\n.small-box h6 {\r\n font-size: 0.75rem;\r\n color: #6c757d;\r\n text-transform: uppercase;\r\n}\r\n.small-box h2 {\r\n font-size: 1.75rem;\r\n font-weight: bold;\r\n color: #000;\r\n}\r\n.info-icon {\r\n float: right;\r\n font-size: 1rem;\r\n color: #ccc;\r\n}\r\n.label {\r\n font-weight: 500;\r\n font-size: 0.7rem;\r\n color: #3e708d;\r\n}\r\n.work-hours {\r\n font-size: 1.5rem;\r\n font-weight: 700;\r\n color: #1d1d1d;\r\n}\r\n.break-info {\r\n font-weight: 600;\r\n color: #343a40;\r\n}\r\n.btn-punch {\r\n border: 1px solid red;\r\n color: red;\r\n font-weight: bold;\r\n border-radius: 4px;\r\n padding: 7px 10px !important;\r\n margin-top: -10px;\r\n}\r\n.btn-punch:hover img {\r\n filter: brightness(0) invert(1);\r\n}\r\n.availability-section {\r\n max-width: 600px;\r\n margin: auto;\r\n background: #f9fbfd;\r\n border-radius: 16px;\r\n padding: 20px;\r\n}\r\n.section-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n margin-bottom: 20px;\r\n}\r\n.section-title {\r\n font-weight: 700;\r\n color: #1d2c4d;\r\n font-size: 16px;\r\n}\r\n.card-container {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 5px;\r\n}\r\n.availability-card {\r\n flex: 1 1 calc(50% - 15px);\r\n background: #F8FAFC;\r\n border-radius: 4px; \r\n padding: 8px;\r\n}\r\n.card-title {\r\n font-size: 13px;\r\n font-weight: 600;\r\n color: #314158;\r\n margin-bottom: 0;\r\n}\r\n.card-value {\r\n font-size: 24px;\r\n font-weight: 700;\r\n color: #0F172B;\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n}\r\n.card-icon {\r\n font-size: 18px;\r\n color: #cde5f7;\r\n}\r\n.common-bg-light {\r\n background: #f2f4f7;\r\n border: 1px solid #eff1f4;\r\n border-radius: 10px;\r\n}\r\n.section-label {\r\n font-size: 14px;\r\n color: #7B849F;\r\n}\r\n.common_blue {\r\n background-color: #eaf2ff;\r\n font-size: 12px;\r\n color: #3aa1d6;\r\n}\r\n.common_blue:hover {\r\n background-color: #c1d5f6;\r\n color: #3aa1d6;\r\n}\r\n.common_select {\r\n background-color: transparent;\r\n font-size: 14px;\r\n color: #0F172B;\r\n border: 1px solid transparent;\r\n padding: 3px !important;\r\n border-radius: 4px;\r\n transition: all 0.5s ease-in-out 0s;\r\n margin-top: -10px;\r\n}\r\n.common_select:hover {\r\n background-color: #e4e4f4;\r\n}\r\n.common_select:focus {\r\n outline: none;\r\n box-shadow: none;\r\n}\r\n.text-primary {\r\n color: #3aa1d6 !important;\r\n}\r\n.common_tabs_widget .nav-pills .nav-link.active {\r\n background: #fff;\r\n color: #0F172B;\r\n font-weight: 500;\r\n}\r\n.common_tabs_widget .nav-pills .nav-link {\r\n background: #F1F5F9;\r\n color: #45556C;\r\n text-align: center;\r\n width: 100%;\r\n padding: 6px;\r\n font-size: 14px;\r\n}\r\n.list-group-item {\r\n border: none !important;\r\n padding: 0.40rem 0 !important;\r\n color: #0F172B;\r\n font-size: 14px;\r\n}\r\n.list-group-item:first-child {\r\n border-top-left-radius: 0;\r\n border-top-right-radius: 0;\r\n}\r\n.list-group-item img {\r\n width: 18px;\r\n margin: 0 5px 0 0;\r\n}\r\n.btn-outline-primary {\r\n border: 1px solid #3aa1d6;\r\n color: #3aa1d6;\r\n}\r\n.btn-outline-primary:hover {\r\n background: #3aa1d6;\r\n color: #fff;\r\n}\r\n.fw-bold {\r\n font-weight: bold;\r\n}\r\n.g-0>[class*=\"col\"],\r\n.gx-0>[class*=\"col\"] {\r\n padding-left: 0 !important;\r\n padding-right: 0 !important;\r\n}\r\n.g-0,\r\n.gx-0 {\r\n margin-left: 0 !important;\r\n margin-right: 0 !important;\r\n}\r\n.g-0>[class*=\"col\"],\r\n.gy-0>[class*=\"col\"] {\r\n padding-top: 0 !important;\r\n padding-bottom: 0 !important;\r\n}\r\n.g-0,\r\n.gy-0 {\r\n margin-top: 0 !important;\r\n margin-bottom: 0 !important;\r\n}\r\n.g-1>[class*=\"col\"] {\r\n padding: 4px !important;\r\n}\r\n.g-1 {\r\n margin: -4px !important;\r\n}\r\n.gx-1>[class*=\"col\"] {\r\n padding-left: 4px !important;\r\n padding-right: 4px !important;\r\n}\r\n.gx-1 {\r\n margin-left: 4px !important;\r\n margin-right: 4px !important;\r\n}\r\n.gy-1>[class*=\"col\"] {\r\n padding-top: 4px !important;\r\n padding-bottom: 4px !important;\r\n}\r\n.gy-1 {\r\n margin-top: 4px !important;\r\n margin-bottom: 4px !important;\r\n}\r\n/* Repeat for g-2 to g-5 */\r\n.g-2>[class*=\"col\"] {\r\n padding: 8px !important;\r\n}\r\n.g-2 {\r\n margin: -16px !important;\r\n}\r\n.rounded-3 {\r\n border-radius: 6px;\r\n}\r\n.common_badge_color {\r\n background: #2b7fff;\r\n color: #fff;\r\n}\r\ninput:checked+.slider {\r\n background-color: #01c16a !important;\r\n}\r\n.btn-secondary .icon {\r\n filter: brightness(0) invert(1) !important;\r\n}\r\n.border-none {\r\n border: none !important;\r\n background: transparent;\r\n}\r\n.carousel-control-prev {\r\n left: 44% !important;\r\n}\r\n.carousel-control-next {\r\n right: 44% !important;\r\n}\r\n.carousel-control-prev,\r\n.carousel-control-next {\r\n top: auto !important;\r\n}\r\n.btn_contoler {\r\n width: 30px;\r\n height: 30px;\r\n background: rgba(0, 0, 0, 0.2);\r\n border-radius: 50%;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n.notice_height {\r\n height: 130px;\r\n}\r\n.list-group {\r\n height: auto;\r\n max-height: 250px;\r\n overflow: auto;\r\n}\r\n.common_link_apply {\r\n color: #2B7FFF;\r\n font-weight: 500; \r\n transition: all 0.5s ease-in-out 0s;\r\n font-size: 14px;\r\n}\r\n.common_link_apply:hover {\r\n color: #2B7FFF;\r\n text-decoration: underline;\r\n}\r\n.common_success {\r\n color: #00C16A;\r\n font-weight: 500;\r\n}\r\n.common_applied {\r\n color: #62748E;\r\n font-weight: 500;\r\n}\r\n.common_font_size {\r\n font-size: 14px;\r\n color: #0F172B;\r\n}\r\n.separate_border {\r\n border-top: 1px solid #E2E8F0;\r\n}\r\n.counter_color {\r\n color: #0F172B;\r\n font-size: 20px;\r\n}\r\n.common_highlight {\r\n color: #2B7FFF;\r\n font-weight: 500; \r\n transition: all 0.5s ease-in-out 0s;\r\n font-size: 14px;\r\n}\r\n.common_widget p {\r\n color: #45556C;\r\n}\r\n.counter_big {\r\n font-size: 20px;\r\n color: #62748E;\r\n font-weight: 500; \r\n}\r\n.counter_big span {\r\n color: #0F172B;\r\n font-weight: 600;\r\n}\r\n.switch {\r\n position: relative;\r\n display: inline-block;\r\n width: 40px;\r\n height: 20px;\r\n}\r\n\r\n.switch input { \r\n opacity: 0;\r\n width: 0;\r\n height: 0;\r\n}\r\n\r\n.slider {\r\n position: absolute;\r\n cursor: pointer;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n bottom: 0;\r\n background-color: #E2E8F0;\r\n -webkit-transition: .4s;\r\n transition: .4s;\r\n}\r\n\r\n.slider:before {\r\n position: absolute;\r\n content: \"\";\r\n height: 16px;\r\n width: 16px;\r\n left: 2px;\r\n bottom: 2px;\r\n background-color: white;\r\n -webkit-transition: .4s;\r\n transition: .4s;\r\n}\r\n\r\ninput:checked + .slider {\r\n background-color: #00C16A;\r\n}\r\n\r\ninput:focus + .slider {\r\n box-shadow: 0 0 1px #00C16A;\r\n}\r\n\r\ninput:checked + .slider:before {\r\n -webkit-transform: translateX(20px);\r\n -ms-transform: translateX(20px);\r\n transform: translateX(20px);\r\n}\r\n\r\n/* Rounded sliders */\r\n.slider.round {\r\n border-radius: 34px;\r\n}\r\n\r\n.slider.round:before {\r\n border-radius: 50%;\r\n}\r\n.donut-center {\r\n position: absolute;\r\n top: 50%;\r\n left: 38%;\r\n transform: translate(-50%, -50%);\r\n text-align: center;\r\n pointer-events: none;\r\n}\r\n.donut-center .number {\r\n font-size: 3.5rem;\r\n font-weight: bold;\r\n color: #111;\r\n}\r\n.donut-center .label {\r\n font-size: 1.5rem;\r\n color: #555;\r\n}\r\n.break_widget .col-3 {\r\n text-align: right;\r\n}\r\n@media all and (max-width:768px) {\r\n \r\n.welcome_title {\r\n font-size: 1.5rem;\r\n}\r\n.time-label {\r\n font-size: 16px;\r\n}\r\n.time-utilization-card img {\r\n margin-top: 10px;\r\n}\r\n .welcome_widget {\r\n margin: 0;\r\n }\r\n .welcome_msg .col-sm-2,\r\n.welcome_msg .col-sm-7,\r\n.welcome_msg .col-sm-3 { \r\n max-width: 100%;\r\n text-align: center;\r\n}\r\n.common_widget .counter_big {\r\n margin: 20px 0;\r\n}\r\n.common_widget .col-sm-6 {\r\n max-width: 50%;\r\n}\r\n.common_widget p {\r\n margin: 20px 0 0 0!important;\r\n}\r\n.count_widget_ouput {\r\n margin-top: 20px;\r\n}\r\n.btn-punch {\r\n margin-top: 0;\r\n margin-bottom: 10px;\r\n}\r\n.break_widget .col-6 {\r\n max-width: 100%;\r\n flex: 0 0 100%;\r\n}\r\n.border-right {\r\n border-right: 0 !important;\r\n border-bottom: 1px solid #dee2e6;\r\n margin-bottom: 10px;\r\n}\r\n.border_right_right {\r\n border-right: 0 !important;\r\n border-bottom: 1px solid #dee2e6;\r\n margin: 0 0 10px 0;\r\n}\r\n.break_widget .mb-4 {\r\n margin-bottom: .5rem !important;\r\n}\r\n.break_widget .pl-2 {\r\n padding-left: 0!important;\r\n}\r\n.break_widget .pr-2 {\r\n padding-right: 0!important;\r\n}\r\n}\r\n.layout-main-section {\r\n padding: 0!important;\r\n border: none!important;\r\n}\r\n.layout-main-section-wrapper {\r\n overflow-y: inherit!important;\r\n}" + }, + { + "docstatus": 0, + "doctype": "Custom HTML Block", + "html": "

Ticket Summary

\n
\n
\n \n\n \n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
IDSubjectStatusPriorityAssignedCreated ByDescription
\n Loading...\n
\n\n
\n \n \n \n
\n
\n
\n", + "modified": "2025-11-22 17:52:37.257685", + "name": "Ticket Summary", + "private": 0, + "roles": [ + { + "parent": "Ticket Summary", + "parentfield": "roles", + "parenttype": "Custom HTML Block", + "role": "Agent" + }, + { + "parent": "Ticket Summary", + "parentfield": "roles", + "parenttype": "Custom HTML Block", + "role": "Agent Manager" + } + ], + "script": "frappe.after_ajax(() => {\n console.log(\"loaded\")\n let currentPage = 1;\n const pageSize = 10;\n initFilters();\n loadTickets();\n \n // Event listeners\n root_element.querySelector(\"#ticketTypeFilter\").addEventListener(\"change\", () => {\n console.log(\"Ticket type change\")\n currentPage = 1;\n loadTickets();\n });\n \n root_element.querySelector(\"#statusFilter\").addEventListener(\"change\", () => {\n console.log(\"Status change\")\n currentPage = 1;\n loadTickets();\n });\n \n root_element.querySelector(\"#nextPage\").addEventListener(\"click\", () => {\n console.log(\"Next Page\")\n currentPage++;\n loadTickets();\n });\n \n root_element.querySelector(\"#prevPage\").addEventListener(\"click\", () => {\n console.log(\"Prev Page\")\n if (currentPage > 1) currentPage--;\n loadTickets();\n });\n \n // Initialize filter dropdowns\n function initFilters() {\n const statuses = [\"Open\", \"Working\", \"Resolved\", \"Closed\"];\n const statusSelect = root_element.querySelector(\"#statusFilter\");\n statuses.forEach(s => statusSelect.innerHTML += ``);\n \n const ticket_types = [\"Unspecified\", \"Technical\", \"Non Technical\"];\n const ticketTypeSelect = root_element.querySelector(\"#ticketTypeFilter\");\n ticket_types.forEach(t => ticketTypeSelect.innerHTML += ``);\n }\n \n // Main data loader\n function loadTickets() {\n const ticketType = root_element.querySelector(\"#ticketTypeFilter\").value;\n const status = root_element.querySelector(\"#statusFilter\").value;\n \n frappe.call({\n method: \"frappe.client.get_list\",\n args: {\n doctype: \"HD Ticket\",\n fields: [\"name\", \"subject\", \"status\",\"priority\", \"assigned_agent\", \"employee_name\", \"description\"],\n limit_page_length: pageSize,\n limit_start: (currentPage - 1) * pageSize,\n filters: {\n ...(ticketType && { ticket_type: ticketType }),\n ...(status && { status })\n },\n order_by: \"modified desc\"\n },\n callback(res) {\n const tbody = root_element.querySelector(\"#ticketTableBody\");\n tbody.innerHTML = \"\";\n \n if (!res.message.length) {\n tbody.innerHTML = `No records found`;\n return;\n }\n \n res.message.forEach(t => {\n const row = `\n \n ${t.name}\n ${t.subject}\n ${t.status}\n ${t.priority}\n ${t.assigned_agent || \"Not Assigned\"}\n ${t.employee_name || \"-\"}\n ${(t.description || \"\").substring(0,70)}...\n \n `;\n \n tbody.innerHTML += row;\n });\n \n root_element.querySelector(\"#pageIndicator\").innerText = `Page ${currentPage}`;\n }\n });\n }\n});", + "style": "#ticket-table {\r\n width: 100%;\r\n border-collapse: collapse;\r\n font-size: 15px;\r\n background: #fff;\r\n border: 1px solid #cdd5e0;\r\n}\r\n\r\n#ticket-table thead th {\r\n background: #1d467d;\r\n color: #fff;\r\n font-weight: 700;\r\n padding: 3px 8px;\r\n font-size: 12px;\r\n border-right: 1px solid #cdd5e0;\r\n letter-spacing: 0.03em;\r\n text-transform: uppercase;\r\n}\r\n#ticket-table thead th:last-child {\r\n border-right: none;\r\n}\r\n\r\n#ticket-table td {\r\n padding: 3px 8px;\r\n border-bottom: 1px solid #cdd5e0;\r\n border-right: 1px solid #cdd5e0;\r\n font-size: 15px;\r\n color: #28354a; /* Not grey */\r\n background: #fff;\r\n}\r\n#ticket-table td:last-child {\r\n border-right: none;\r\n}\r\n\r\n#ticket-table tbody tr:nth-child(odd) td {\r\n background: #fff;\r\n}\r\n#ticket-table tbody tr:nth-child(even) td {\r\n background: #ffffff;\r\n}\r\n#ticket-table tbody tr:hover td {\r\n background: #D3D3D3 !important; /* Soft highlight on hover */\r\n color: black;\r\n cursor: pointer;\r\n}\r\n.ticket-status {\r\n display: inline-block;\r\n padding: 3px 13px;\r\n border-radius: 14px;\r\n font-size: 13px;\r\n font-weight: 600;\r\n}\r\n.status-Open { background: #e74c3c; color: #fff; }\r\n.status-Resolved { background: #27ae60; color: #fff; }\r\n.status-Closed { background: #7f8c8d; color: #fff; }\r\n.pagination-btn {\r\n padding: 7px 16px;\r\n margin: 0 6px;\r\n font-size: 15px;\r\n background: #f5f9fb;\r\n border: 1px solid #cdd5e0;\r\n color: #1d467d;\r\n border-radius: 5px;\r\n font-weight: 600;\r\n cursor: pointer;\r\n}\r\n.pagination-btn:disabled {\r\n background: #f5f5f5;\r\n color: #94a4c0;\r\n cursor: not-allowed;\r\n}\r\n.pagination-btn:hover:not(:disabled) {\r\n background: #e1eaf4;\r\n color: #1d467d;\r\n}\r\n" } ] \ No newline at end of file diff --git a/beams/hooks.py b/beams/hooks.py index 1616db984..cdb7a9344 100644 --- a/beams/hooks.py +++ b/beams/hooks.py @@ -562,6 +562,7 @@ "Availability and Attendance", "HR Message", "Adherence and Break", + "Ticket Summary", ], ] ],