Skip to content
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

feat: Notification Panel #63

Merged
merged 21 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d95aa9e
feat: added notification doctype
shariquerik Jan 29, 2024
e5e64c2
fix: create notification when someone mentions in comment
shariquerik Jan 29, 2024
d6b0bee
fix: added notifications button in sidebar
shariquerik Jan 29, 2024
a9d51f8
fix: created notifications store to get all notifications
shariquerik Jan 29, 2024
8a5b5a6
fix: added id on comment in activity
shariquerik Jan 29, 2024
f4be8cc
fix: allow scroll to comment if hash is present
shariquerik Jan 29, 2024
a5071a1
fix: get all notifications in desc order
shariquerik Jan 29, 2024
fe047ac
fix: moved isSidebarCollapsed value to globalStore
shariquerik Jan 29, 2024
b3a86ab
fix: show all notifications
shariquerik Jan 29, 2024
ee66df3
fix: close notification panel if click outside
shariquerik Jan 29, 2024
8a3502b
fix: mark all as read and mark comment as read
shariquerik Jan 29, 2024
39fd9a6
fix: added Mark as done icon
shariquerik Jan 29, 2024
f95f211
fix: added unread notification count
shariquerik Jan 29, 2024
f94c566
fix: removed isSidebarCollapsed from global and use absolute position…
shariquerik Jan 29, 2024
a0ba6dd
fix: added reference doctype and name in notification doctype
shariquerik Jan 29, 2024
4af4d12
fix: consider 1px border
shariquerik Jan 29, 2024
939176f
fix: clear params after mark as read
shariquerik Jan 29, 2024
2abdfe1
fix: reload doctype before running after_install
shariquerik Jan 29, 2024
50b4930
revert: reload doctype before running after_install
shariquerik Jan 29, 2024
cf1148e
chore: cleanup
shariquerik Jan 29, 2024
227a495
fix: reload doctype before install
shariquerik Jan 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crm/api/activities.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def get_deal_activities(name):

for comment in docinfo.comments:
activity = {
"name": comment.name,
"activity_type": "comment",
"creation": comment.creation,
"owner": comment.owner,
Expand Down Expand Up @@ -203,6 +204,7 @@ def get_lead_activities(name):

for comment in docinfo.comments:
activity = {
"name": comment.name,
"activity_type": "comment",
"creation": comment.creation,
"owner": comment.owner,
Expand Down
43 changes: 43 additions & 0 deletions crm/api/comment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import frappe
from bs4 import BeautifulSoup

def on_update(self, method):
notify_mentions(self)


def notify_mentions(doc):
"""
Extract mentions from `content`, and notify.
`content` must have `HTML` content.
"""
content = getattr(doc, "content", None)
if not content:
return
mentions = extract_mentions(content)
for mention in mentions:
values = frappe._dict(
doctype="CRM Notification",
from_user=doc.owner,
to_user=mention.email,
type="Mention",
message=doc.content,
comment=doc.name,
reference_doctype=doc.reference_doctype,
reference_name=doc.reference_name,
)

if frappe.db.exists("CRM Notification", values):
return
frappe.get_doc(values).insert()


def extract_mentions(html):
if not html:
return []
soup = BeautifulSoup(html, "html.parser")
mentions = []
for d in soup.find_all("span", attrs={"data-type": "mention"}):
mentions.append(
frappe._dict(full_name=d.get("data-label"), email=d.get("data-id"))
)
return mentions
59 changes: 59 additions & 0 deletions crm/api/notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import frappe
from frappe.query_builder import Order


@frappe.whitelist()
def get_notifications():
if frappe.session.user == "Guest":
frappe.throw("Authentication failed", exc=frappe.AuthenticationError)

Notification = frappe.qb.DocType("CRM Notification")
query = (
frappe.qb.from_(Notification)
.select("*")
.where(Notification.to_user == frappe.session.user)
.orderby("creation", order=Order.desc)
)
notifications = query.run(as_dict=True)

_notifications = []
for notification in notifications:
_notifications.append(
{
"creation": notification.creation,
"from_user": {
"name": notification.from_user,
"full_name": frappe.get_value(
"User", notification.from_user, "full_name"
),
},
"type": notification.type,
"to_user": notification.to_user,
"read": notification.read,
"comment": notification.comment,
"reference_doctype": "deal"
if notification.reference_doctype == "CRM Deal"
else "lead",
"reference_name": notification.reference_name,
"route_name": "Deal"
if notification.reference_doctype == "CRM Deal"
else "Lead",
}
)

return _notifications


@frappe.whitelist()
def mark_as_read(user=None, comment=None):
if frappe.session.user == "Guest":
frappe.throw("Authentication failed", exc=frappe.AuthenticationError)

user = user or frappe.session.user
filters = {"to_user": user, "read": False}
if comment:
filters["comment"] = comment
for n in frappe.get_all("CRM Notification", filters=filters):
d = frappe.get_doc("CRM Notification", n.name)
d.read = True
d.save()
Empty file.
8 changes: 8 additions & 0 deletions crm/fcrm/doctype/crm_notification/crm_notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt

// frappe.ui.form.on("CRM Notification", {
// refresh(frm) {

// },
// });
129 changes: 129 additions & 0 deletions crm/fcrm/doctype/crm_notification/crm_notification.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2024-01-29 19:31:13.613929",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"from_user",
"type",
"reference_doctype",
"reference_name",
"column_break_dduu",
"to_user",
"comment",
"read",
"section_break_vpwa",
"message"
],
"fields": [
{
"fieldname": "from_user",
"fieldtype": "Link",
"label": "From User",
"options": "User"
},
{
"fieldname": "type",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Type",
"options": "Mention",
"reqd": 1
},
{
"fieldname": "column_break_dduu",
"fieldtype": "Column Break"
},
{
"fieldname": "to_user",
"fieldtype": "Link",
"in_list_view": 1,
"label": "To User",
"options": "User",
"reqd": 1
},
{
"fieldname": "comment",
"fieldtype": "Link",
"label": "Comment",
"link_filters": "[[{\"fieldname\":\"comment\",\"field_option\":\"Comment\"},\"comment_type\",\"=\",\"Comment\"]]",
"options": "Comment"
},
{
"default": "0",
"fieldname": "read",
"fieldtype": "Check",
"label": "Read"
},
{
"fieldname": "section_break_vpwa",
"fieldtype": "Section Break"
},
{
"fieldname": "message",
"fieldtype": "HTML Editor",
"label": "Message"
},
{
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"label": "Reference Doc",
"options": "reference_doctype"
},
{
"fieldname": "reference_doctype",
"fieldtype": "Link",
"label": "Reference Doctype",
"options": "DocType"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-01-30 01:04:27.946030",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Notification",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales User",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
9 changes: 9 additions & 0 deletions crm/fcrm/doctype/crm_notification/crm_notification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt

# import frappe
from frappe.model.document import Document


class CRMNotification(Document):
pass
9 changes: 9 additions & 0 deletions crm/fcrm/doctype/crm_notification/test_crm_notification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt

# import frappe
from frappe.tests.utils import FrappeTestCase


class TestCRMNotification(FrappeTestCase):
pass
3 changes: 3 additions & 0 deletions crm/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@
"ToDo": {
"after_insert": ["crm.api.todo.after_insert"],
},
"Comment": {
"on_update": ["crm.api.comment.on_update"],
}
}

# Scheduled Tasks
Expand Down
4 changes: 3 additions & 1 deletion crm/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields

def before_install():
pass
frappe.reload_doc("fcrm", "doctype", "crm_lead_status")
frappe.reload_doc("fcrm", "doctype", "crm_deal_status")
frappe.reload_doc("fcrm", "doctype", "crm_communication_status")

def after_install():
add_default_lead_statuses()
Expand Down
21 changes: 17 additions & 4 deletions frontend/src/components/Activities.vue
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,11 @@
</div>
</div>
</div>
<div class="mb-4" v-else-if="activity.activity_type == 'comment'">
<div
class="mb-4"
:id="activity.name"
v-else-if="activity.activity_type == 'comment'"
>
<div
class="mb-0.5 flex items-start justify-stretch gap-2 py-1.5 text-base"
>
Expand Down Expand Up @@ -765,6 +769,7 @@ import {
} from 'frappe-ui'
import { useElementVisibility } from '@vueuse/core'
import { ref, computed, h, defineModel, markRaw, watch, nextTick } from 'vue'
import { useRoute } from 'vue-router'

const { makeCall } = globalStore()
const { getUser } = usersStore()
Expand Down Expand Up @@ -1106,11 +1111,14 @@ watch([reload, reload_email], ([reload_value, reload_email_value]) => {
}
})

function scroll(el) {
function scroll(hash) {
setTimeout(() => {
if (!el) {
let el
if (!hash) {
let e = document.getElementsByClassName('activity')
el = e[e.length - 1]
} else {
el = document.getElementById(hash)
}
if (el && !useElementVisibility(el).value) {
el.scrollIntoView({ behavior: 'smooth' })
Expand All @@ -1121,7 +1129,12 @@ function scroll(el) {

defineExpose({ emailBox })

nextTick(() => scroll())
const route = useRoute()

nextTick(() => {
const hash = route.hash.slice(1) || null
scroll(hash)
})
</script>

<style scoped>
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/components/Icons/MarkAsDoneIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M15.1443 4.56673C15.3259 4.35875 15.3046 4.04289 15.0966 3.86123C14.8886 3.67957 14.5728 3.7009 14.3911 3.90887L8.18815 11.0103L7.78736 10.57C7.60147 10.3657 7.28524 10.3509 7.08103 10.5368C6.87682 10.7227 6.86196 11.0389 7.04785 11.2431L7.82609 12.0981C7.92205 12.2035 8.05843 12.2629 8.20097 12.2615C8.34351 12.26 8.47864 12.1978 8.57242 12.0904L15.1443 4.56673ZM11.2939 4.56674C11.4756 4.35876 11.4543 4.0429 11.2463 3.86124C11.0383 3.67957 10.7225 3.7009 10.5408 3.90888L4.33783 11.0103L1.6023 8.00507C1.41642 7.80086 1.10018 7.786 0.895974 7.97189C0.691764 8.15777 0.676909 8.47401 0.862793 8.67822L3.97577 12.0981C4.07172 12.2035 4.20811 12.2629 4.35065 12.2615C4.49318 12.26 4.62832 12.1978 4.7221 12.0904L11.2939 4.56674Z"
fill="currentColor"
/>
</svg>
</template>
26 changes: 26 additions & 0 deletions frontend/src/components/Icons/NotificationsIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<template>
<svg
width="18"
height="19"
viewBox="0 0 18 19"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0_3181_20053)">
<path
d="M10.6097 2.9398L10.1121 2.98864L10.1445 3.31827L10.4606 3.41705L10.6097 2.9398ZM11.1567 3.14404L11.3569 2.68587L11.1567 3.14404ZM6.573 3.27134L6.79841 3.71765L6.573 3.27134ZM7.3908 2.93968L7.53988 3.41694L7.85603 3.31818L7.88841 2.98855L7.3908 2.93968ZM10.4606 3.41705C10.63 3.46998 10.7955 3.53188 10.9564 3.60221L11.3569 2.68587C11.1628 2.60107 10.9632 2.5264 10.7589 2.46255L10.4606 3.41705ZM10.9564 3.60221C12.6848 4.35744 13.891 6.08158 13.891 8.08634H14.891C14.891 5.66998 13.4363 3.59451 11.3569 2.68587L10.9564 3.60221ZM13.891 8.08634V12.1891H14.891V8.08634H13.891ZM13.891 12.1891C13.891 12.5068 14.1486 12.7643 14.4663 12.7643V11.7643C14.7009 11.7643 14.891 11.9545 14.891 12.1891H13.891ZM14.4663 12.7643C14.744 12.7643 14.9692 12.9895 14.9692 13.2673H15.9692C15.9692 12.4372 15.2963 11.7643 14.4663 11.7643V12.7643ZM14.9692 13.2673V13.3425H15.9692V13.2673H14.9692ZM14.9692 13.3425C14.9692 13.6618 14.7104 13.9207 14.391 13.9207V14.9207C15.2626 14.9207 15.9692 14.2141 15.9692 13.3425H14.9692ZM14.391 13.9207H3.60932V14.9207H14.391V13.9207ZM3.60932 13.9207C3.28999 13.9207 3.03113 13.6618 3.03113 13.3425H2.03113C2.03113 14.2141 2.73771 14.9207 3.60932 14.9207V13.9207ZM3.03113 13.3425V13.2671H2.03113V13.3425H3.03113ZM3.03113 13.2671C3.03113 12.9894 3.25624 12.7643 3.53392 12.7643V11.7643C2.70395 11.7643 2.03113 12.4371 2.03113 13.2671H3.03113ZM3.53392 12.7643C3.85159 12.7643 4.10913 12.5068 4.10913 12.1891H3.10913C3.10913 11.9545 3.29932 11.7643 3.53392 11.7643V12.7643ZM4.10913 12.1891V8.08634H3.10913V12.1891H4.10913ZM4.10913 8.08634C4.10913 6.17794 5.20204 4.52391 6.79841 3.71765L6.34758 2.82504C4.42753 3.79478 3.10913 5.78615 3.10913 8.08634H4.10913ZM6.79841 3.71765C7.03522 3.59804 7.28302 3.49717 7.53988 3.41694L7.24173 2.46242C6.93186 2.55921 6.63302 2.68087 6.34758 2.82504L6.79841 3.71765ZM7.88841 2.98855C7.94387 2.42393 8.42082 1.98242 9.00027 1.98242V0.982422C7.90133 0.982422 6.99845 1.81927 6.8932 2.8908L7.88841 2.98855ZM9.00027 1.98242C9.57975 1.98242 10.0567 2.42397 10.1121 2.98864L11.1074 2.89096C11.0022 1.81935 10.0993 0.982422 9.00027 0.982422V1.98242ZM10.6567 14.4207C10.6567 15.3355 9.91506 16.0771 9.00027 16.0771V17.0771C10.4673 17.0771 11.6567 15.8878 11.6567 14.4207H10.6567ZM9.00027 16.0771C8.08548 16.0771 7.34389 15.3355 7.34389 14.4207H6.34389C6.34389 15.8878 7.53319 17.0771 9.00027 17.0771V16.0771Z"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_3181_20053">
<rect
width="18"
height="18"
fill="white"
transform="translate(0 0.0292969)"
/>
</clipPath>
</defs>
</svg>
</template>
Loading
Loading