diff --git a/crm/api/report.py b/crm/api/report.py
new file mode 100644
index 000000000..07413b515
--- /dev/null
+++ b/crm/api/report.py
@@ -0,0 +1,20 @@
+import frappe
+from frappe.desk.query_report import run
+
+@frappe.whitelist(allow_guest=True)
+def get_report(report_name, filters=None):
+ print("Fetching report data...")
+ if isinstance(filters, str):
+ import json
+ filters = json.loads(filters)
+
+ # Run the report
+ try:
+ report_output = run(report_name, filters or {})
+ return {
+ "columns": report_output.get("columns"),
+ "data": report_output.get("result")
+ }
+ except Exception as e:
+ frappe.log_error(frappe.get_traceback(), "Error fetching report data")
+ return {"error": str(e)}
diff --git a/crm/fcrm/api.py b/crm/fcrm/api.py
new file mode 100644
index 000000000..71727c0bc
--- /dev/null
+++ b/crm/fcrm/api.py
@@ -0,0 +1,19 @@
+
+import frappe
+from frappe import _
+
+@frappe.whitelist(allow_guest=True)
+def get_fcrm_reports():
+ """Fetch all standard and custom reports for FCRM module (Redsoft CRM)."""
+ reports = frappe.get_all(
+ "Report",
+ filters={"module": "Redsoft CRM"},
+ fields=["name", "ref_doctype", "report_type", "is_standard", "modified"]
+ )
+
+ return {
+ "status": "success",
+ "module": "Redsoft CRM",
+ "count": len(reports),
+ "data": reports
+ }
diff --git a/crm/fcrm/doctype/crm_lead/crm_lead.json b/crm/fcrm/doctype/crm_lead/crm_lead.json
index e39af407f..c5e84c820 100644
--- a/crm/fcrm/doctype/crm_lead/crm_lead.json
+++ b/crm/fcrm/doctype/crm_lead/crm_lead.json
@@ -290,7 +290,7 @@
"image_field": "image",
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2025-01-02 22:14:01.991054",
+ "modified": "2025-04-21 11:56:49.339554",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Lead",
@@ -329,8 +329,18 @@
"report": 1,
"role": "All",
"share": 1
+ },
+ {
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Guest",
+ "share": 1
}
],
+ "row_format": "Dynamic",
"sender_field": "email",
"sender_name_field": "first_name",
"show_title_field_in_link": 1,
diff --git a/frontend/components.d.ts b/frontend/components.d.ts
index 82afdb191..06b1915a3 100644
--- a/frontend/components.d.ts
+++ b/frontend/components.d.ts
@@ -188,6 +188,8 @@ declare module 'vue' {
ReloadIcon: typeof import('./src/components/Icons/ReloadIcon.vue')['default']
ReplyAllIcon: typeof import('./src/components/Icons/ReplyAllIcon.vue')['default']
ReplyIcon: typeof import('./src/components/Icons/ReplyIcon.vue')['default']
+ ReportDetailModal: typeof import('./src/components/Modals/ReportDetailModal.vue')['default']
+ ReportsListView: typeof import('./src/components/ListViews/ReportsListView.vue')['default']
Resizer: typeof import('./src/components/Resizer.vue')['default']
RightSideLayoutIcon: typeof import('./src/components/Icons/RightSideLayoutIcon.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
diff --git a/frontend/src/components/Layouts/AppSidebar.vue b/frontend/src/components/Layouts/AppSidebar.vue
index 05d5bbb0e..3b2cf2e95 100644
--- a/frontend/src/components/Layouts/AppSidebar.vue
+++ b/frontend/src/components/Layouts/AppSidebar.vue
@@ -164,6 +164,7 @@ import SidebarLink from '@/components/SidebarLink.vue'
import Notifications from '@/components/Notifications.vue'
import Settings from '@/components/Settings/Settings.vue'
import { viewsStore } from '@/stores/views'
+import DashboardIcon from '@/components/Icons/DashboardIcon.vue'
import {
unreadNotificationsCount,
notificationsStore,
@@ -195,14 +196,14 @@ const isDemoSite = ref(window.is_demo_site)
const links = [
{
- label: 'Leads',
+ label: 'Patients',
icon: LeadsIcon,
- to: 'Leads',
+ to: 'Patients',
},
{
- label: 'Deals',
+ label: 'Bookings',
icon: DealsIcon,
- to: 'Deals',
+ to: 'Bookings',
},
{
label: 'Contacts',
@@ -234,6 +235,11 @@ const links = [
icon: Email2Icon,
to: 'Email Templates',
},
+ {
+ label: 'Reports',
+ icon: DashboardIcon,
+ to: 'Reports',
+ },
]
const allViews = computed(() => {
@@ -281,9 +287,9 @@ function getIcon(routeName, icon) {
if (icon) return h('div', { class: 'size-auto' }, icon)
switch (routeName) {
- case 'Leads':
+ case 'Patients':
return LeadsIcon
- case 'Deals':
+ case 'Bookings':
return DealsIcon
case 'Contacts':
return ContactsIcon
@@ -292,7 +298,9 @@ function getIcon(routeName, icon) {
case 'Notes':
return NoteIcon
case 'Call Logs':
- return PhoneIcon
+ return
+ case 'Dashboard':
+ return DashboardIcon
default:
return PinIcon
}
@@ -324,7 +332,7 @@ const steps = reactive([
completed: false,
onClick: () => {
minimize.value = true
- router.push({ name: 'Leads' })
+ router.push({ name: 'Patients' })
},
},
{
@@ -358,7 +366,7 @@ const steps = reactive([
if (lead) {
router.push({ name: 'Lead', params: { leadId: lead } })
} else {
- router.push({ name: 'Leads' })
+ router.push({ name: 'Patients' })
}
},
}
@@ -421,7 +429,7 @@ const steps = reactive([
hash: '#comments',
})
} else {
- router.push({ name: 'Leads' })
+ router.push({ name: 'Patients' })
}
},
},
@@ -441,7 +449,7 @@ const steps = reactive([
hash: '#emails',
})
} else {
- router.push({ name: 'Leads' })
+ router.push({ name: 'Patients' })
}
},
},
@@ -469,7 +477,7 @@ const steps = reactive([
hash: '#activity',
})
} else {
- router.push({ name: 'Leads' })
+ router.push({ name: 'Patients' })
}
},
}
diff --git a/frontend/src/components/ListViews/ReportsListView.vue b/frontend/src/components/ListViews/ReportsListView.vue
new file mode 100644
index 000000000..67f1066ff
--- /dev/null
+++ b/frontend/src/components/ListViews/ReportsListView.vue
@@ -0,0 +1,207 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ emit('applyFilter', {
+ event,
+ idx,
+ column,
+ item,
+ firstColumn: columns[0],
+ })"
+ >
+
+ {{ item.timeAgo }}
+
+
+
+
+
+
+
+
+
+ emit('applyFilter', {
+ event,
+ idx,
+ column,
+ item,
+ firstColumn: columns[0],
+ })"
+ >
+ {{ label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/Mobile/MobileSidebar.vue b/frontend/src/components/Mobile/MobileSidebar.vue
index af313d635..eebe8a768 100644
--- a/frontend/src/components/Mobile/MobileSidebar.vue
+++ b/frontend/src/components/Mobile/MobileSidebar.vue
@@ -108,19 +108,20 @@ import { createResource } from 'frappe-ui'
import { TrialBanner } from 'frappe-ui/frappe'
import { computed, h, provide } from 'vue'
import { mobileSidebarOpened as sidebarOpened } from '@/composables/settings'
+import DashboardIcon from '@/components/Icons/DashboardIcon.vue'
const { getPinnedViews, getPublicViews } = viewsStore()
const links = [
{
- label: 'Leads',
+ label: 'Patients',
icon: LeadsIcon,
- to: 'Leads',
+ to: 'Patients',
},
{
- label: 'Deals',
+ label: 'Bookings',
icon: DealsIcon,
- to: 'Deals',
+ to: 'Bookings',
},
{
label: 'Contacts',
@@ -152,6 +153,11 @@ const links = [
icon: Email2Icon,
to: 'Email Templates',
},
+ {
+ label: 'Reports',
+ icon: DashboardIcon,
+ to: 'Reports',
+ },
]
const allViews = computed(() => {
@@ -199,9 +205,9 @@ function getIcon(routeName, icon) {
if (icon) return h('div', { class: 'size-auto' }, icon)
switch (routeName) {
- case 'Leads':
+ case 'Patients':
return LeadsIcon
- case 'Deals':
+ case 'Patients':
return DealsIcon
case 'Contacts':
return ContactsIcon
@@ -211,6 +217,8 @@ function getIcon(routeName, icon) {
return NoteIcon
case 'Call Logs':
return PhoneIcon
+ case 'Dashboard':
+ return DashboardIcon
default:
return PinIcon
}
diff --git a/frontend/src/components/Modals/DealModal.vue b/frontend/src/components/Modals/DealModal.vue
index 7bd0b592b..4ad3dc04c 100644
--- a/frontend/src/components/Modals/DealModal.vue
+++ b/frontend/src/components/Modals/DealModal.vue
@@ -5,7 +5,7 @@
- {{ __('Create Deal') }}
+ {{ __('Create Booking') }}
diff --git a/frontend/src/components/Modals/LeadModal.vue b/frontend/src/components/Modals/LeadModal.vue
index 95e0add20..09f1dc2aa 100644
--- a/frontend/src/components/Modals/LeadModal.vue
+++ b/frontend/src/components/Modals/LeadModal.vue
@@ -5,7 +5,7 @@
- {{ __('Create Lead') }}
+ {{ __('Create Patient') }}
diff --git a/frontend/src/components/Modals/ReportDetailModal.vue b/frontend/src/components/Modals/ReportDetailModal.vue
new file mode 100644
index 000000000..840fa14fc
--- /dev/null
+++ b/frontend/src/components/Modals/ReportDetailModal.vue
@@ -0,0 +1,174 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/Modals/ViewModal.vue b/frontend/src/components/Modals/ViewModal.vue
index a3ba26f7f..3b4d6f0bc 100644
--- a/frontend/src/components/Modals/ViewModal.vue
+++ b/frontend/src/components/Modals/ViewModal.vue
@@ -37,7 +37,7 @@
class="flex-1"
size="md"
type="text"
- :placeholder="__('My Open Deals')"
+ :placeholder="__('My Open Bookings')"
v-model="view.label"
/>
diff --git a/frontend/src/pages/Contact.vue b/frontend/src/pages/Contact.vue
index bfe7ea76f..60f739c98 100644
--- a/frontend/src/pages/Contact.vue
+++ b/frontend/src/pages/Contact.vue
@@ -150,7 +150,7 @@
deals.data?.length),
},
diff --git a/frontend/src/pages/Deal.vue b/frontend/src/pages/Deal.vue
index eb8446960..0dd3e17fc 100644
--- a/frontend/src/pages/Deal.vue
+++ b/frontend/src/pages/Deal.vue
@@ -414,7 +414,7 @@ const deal = createResource({
errorTitle.value = __('Not permitted')
errorMessage.value = __(err.messages?.[0])
} else {
- router.push({ name: 'Deals' })
+ router.push({ name: 'Bookings' })
}
},
})
@@ -499,7 +499,7 @@ function validateRequired(fieldname, value) {
}
const breadcrumbs = computed(() => {
- let items = [{ label: __('Deals'), route: { name: 'Deals' } }]
+ let items = [{ label: __('Bookings'), route: { name: 'Bookings' } }]
if (route.query.view || route.query.viewType) {
let view = getView(route.query.view, route.query.viewType, 'CRM Deal')
@@ -508,7 +508,7 @@ const breadcrumbs = computed(() => {
label: __(view.label),
icon: view.icon,
route: {
- name: 'Deals',
+ name: 'Bookings',
params: { viewType: route.query.viewType },
query: { view: route.query.view },
},
@@ -742,7 +742,7 @@ async function deleteDeal(name) {
doctype: 'CRM Deal',
name,
})
- router.push({ name: 'Deals' })
+ router.push({ name: 'Bookings' })
}
const activities = ref(null)
diff --git a/frontend/src/pages/Deals.vue b/frontend/src/pages/Deals.vue
index 093acddff..774e7cd6c 100644
--- a/frontend/src/pages/Deals.vue
+++ b/frontend/src/pages/Deals.vue
@@ -1,7 +1,7 @@
-
+
- {{ __('No {0} Found', [__('Deals')]) }}
+ {{ __('No {0} Found', [__('Bookings')]) }}
diff --git a/frontend/src/pages/InvalidPage.vue b/frontend/src/pages/InvalidPage.vue
index 875cec2d4..5b40b0ede 100644
--- a/frontend/src/pages/InvalidPage.vue
+++ b/frontend/src/pages/InvalidPage.vue
@@ -4,9 +4,9 @@
>
Invalid page or not permitted to access
-
diff --git a/frontend/src/pages/Lead.vue b/frontend/src/pages/Lead.vue
index 19aa6e669..60f739c98 100644
--- a/frontend/src/pages/Lead.vue
+++ b/frontend/src/pages/Lead.vue
@@ -1,5 +1,5 @@
-
+
@@ -7,494 +7,267 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ __(lead.data.name) }}
-
- updateField('image', file.file_url)"
- :validateFile="validateFile"
- >
-
-
-
-
-
-
- {{ title }}
-
-
-
-
-
-
- lead.data.mobile_no
- ? makeCall(lead.data.mobile_no)
- : _errorMessage(__('No phone number set'))
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ {{ contact.data.salutation + '. ' }}
+
+ {{ contact.data.full_name }}
-
-
-
-
-
-
+
+
+
{{ contact.data.company_name }}
-
+
+
+
+
-
-
-
-
-
+
+
+
-
-
-