diff --git a/crm/www/crm.py b/crm/www/crm.py index 80335d99..04b14037 100644 --- a/crm/www/crm.py +++ b/crm/www/crm.py @@ -3,8 +3,14 @@ from __future__ import unicode_literals import frappe -from frappe.utils import add_user_info, cint, get_system_timezone +from frappe.utils import cint, get_system_timezone from frappe.utils.telemetry import capture +from frappe.locale import ( + get_date_format, + get_first_day_of_the_week, + get_number_format, + get_time_format, +) no_cache = 1 @@ -34,24 +40,22 @@ def get_boot(): "read_only_mode": frappe.flags.read_only, "csrf_token": frappe.sessions.get_csrf_token(), "setup_complete": cint(frappe.get_system_settings("setup_complete")), + "sysdefaults": { + "float_precision": cint(frappe.get_system_settings("float_precision")) + or 3, + "date_format": get_date_format(), + "time_format": get_time_format(), + "first_day_of_the_week": get_first_day_of_the_week(), + "number_format": get_number_format().string, + }, "timezone": { "system": get_system_timezone(), - "user": get_user_info() - .get(frappe.session.user, {}) - .get("time_zone", None) + "user": frappe.db.get_value("User", frappe.session.user, "time_zone") or get_system_timezone(), - } + }, } ) -def get_user_info(): - # get info for current user - user_info = frappe._dict() - add_user_info(frappe.session.user, user_info) - - return user_info - - def get_default_route(): return "/crm" diff --git a/frontend/src/components/Fields.vue b/frontend/src/components/Fields.vue index cc39778c..bb5e0125 100644 --- a/frontend/src/components/Fields.vue +++ b/frontend/src/components/Fields.vue @@ -134,6 +134,7 @@ v-else-if="field.type === 'Datetime'" v-model="data[field.name]" icon-left="" + :formatter="(date) => getFormat(date, '', true, true)" :placeholder="getPlaceholder(field)" input-class="border-none" /> @@ -141,6 +142,7 @@ v-else-if="field.type === 'Date'" icon-left="" v-model="data[field.name]" + :formatter="(date) => getFormat(date, '', true)" :placeholder="getPlaceholder(field)" input-class="border-none" /> @@ -178,6 +180,7 @@ import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue' import UserAvatar from '@/components/UserAvatar.vue' import Link from '@/components/Controls/Link.vue' import { usersStore } from '@/stores/users' +import { getFormat } from '@/utils' import { Tooltip, DatePicker, DateTimePicker } from 'frappe-ui' const { getUser } = usersStore() diff --git a/frontend/src/components/Modals/TaskModal.vue b/frontend/src/components/Modals/TaskModal.vue index 5c98e679..2c85c237 100644 --- a/frontend/src/components/Modals/TaskModal.vue +++ b/frontend/src/components/Modals/TaskModal.vue @@ -93,6 +93,7 @@ class="datepicker w-36" v-model="_task.due_date" :placeholder="__('01/04/2024 11:30 PM')" + :formatter="(date) => getFormat(date, '', true, true)" input-class="border-none" /> @@ -114,7 +115,7 @@ import TaskPriorityIcon from '@/components/Icons/TaskPriorityIcon.vue' import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue' import UserAvatar from '@/components/UserAvatar.vue' import Link from '@/components/Controls/Link.vue' -import { taskStatusOptions, taskPriorityOptions } from '@/utils' +import { taskStatusOptions, taskPriorityOptions, getFormat } from '@/utils' import { usersStore } from '@/stores/users' import { capture } from '@/telemetry' import { TextEditor, Dropdown, Tooltip, call, DateTimePicker } from 'frappe-ui' diff --git a/frontend/src/components/SectionFields.vue b/frontend/src/components/SectionFields.vue index 3233ff94..e18d65fc 100644 --- a/frontend/src/components/SectionFields.vue +++ b/frontend/src/components/SectionFields.vue @@ -152,6 +152,7 @@ { return callLogs.value?.data.data.map((callLog) => { let _rows = {} callLogs.value?.data.rows.forEach((row) => { - _rows[row] = getCallLogDetail(row, callLog) + _rows[row] = getCallLogDetail(row, callLog, callLogs.value?.data.columns) }) return _rows }) diff --git a/frontend/src/pages/Contacts.vue b/frontend/src/pages/Contacts.vue index d89f288d..30ed4551 100644 --- a/frontend/src/pages/Contacts.vue +++ b/frontend/src/pages/Contacts.vue @@ -109,6 +109,18 @@ const rows = computed(() => { contacts.value?.data.rows.forEach((row) => { _rows[row] = contact[row] + let fieldType = contacts.value?.data.columns?.find( + (col) => (col.key || col.value) == row, + )?.type + + if ( + fieldType && + ['Date', 'Datetime'].includes(fieldType) && + !['modified', 'creation'].includes(row) + ) { + _rows[row] = formatDate(contact[row], '', true, fieldType == 'Datetime') + } + if (row == 'full_name') { _rows[row] = { label: contact.full_name, diff --git a/frontend/src/pages/Deals.vue b/frontend/src/pages/Deals.vue index aaa71e82..7a3162b8 100644 --- a/frontend/src/pages/Deals.vue +++ b/frontend/src/pages/Deals.vue @@ -335,15 +335,16 @@ const rows = computed(() => { return getGroupedByRows( deals.value?.data.data, deals.value?.data.group_by_field, + deals.value.data.columns, ) } else if (deals.value.data.view_type === 'kanban') { - return getKanbanRows(deals.value.data.data) + return getKanbanRows(deals.value.data.data, deals.value.data.fields) } else { - return parseRows(deals.value?.data.data) + return parseRows(deals.value?.data.data, deals.value.data.columns) } }) -function getGroupedByRows(listRows, groupByField) { +function getGroupedByRows(listRows, groupByField, columns) { let groupedRows = [] groupByField.options?.forEach((option) => { @@ -359,7 +360,7 @@ function getGroupedByRows(listRows, groupByField) { label: groupByField.label, group: option || __(' '), collapsed: false, - rows: parseRows(filteredRows), + rows: parseRows(filteredRows, columns), } if (groupByField.name == 'status') { groupDetail.icon = () => @@ -373,22 +374,34 @@ function getGroupedByRows(listRows, groupByField) { return groupedRows || listRows } -function getKanbanRows(data) { +function getKanbanRows(data, columns) { let _rows = [] data.forEach((column) => { column.data?.forEach((row) => { _rows.push(row) }) }) - return parseRows(_rows) + return parseRows(_rows, columns) } -function parseRows(rows) { +function parseRows(rows, columns = []) { return rows.map((deal) => { let _rows = {} deals.value.data.rows.forEach((row) => { _rows[row] = deal[row] + let fieldType = columns?.find( + (col) => (col.key || col.value) == row, + )?.type + + if ( + fieldType && + ['Date', 'Datetime'].includes(fieldType) && + !['modified', 'creation'].includes(row) + ) { + _rows[row] = formatDate(deal[row], '', true, fieldType == 'Datetime') + } + if (row == 'organization') { _rows[row] = { label: deal.organization, diff --git a/frontend/src/pages/EmailTemplates.vue b/frontend/src/pages/EmailTemplates.vue index 5a20eb7b..73e3ec5f 100644 --- a/frontend/src/pages/EmailTemplates.vue +++ b/frontend/src/pages/EmailTemplates.vue @@ -98,6 +98,23 @@ const rows = computed(() => { emailTemplates.value?.data.rows.forEach((row) => { _rows[row] = emailTemplate[row] + let fieldType = emailTemplates.value?.data.columns?.find( + (col) => (col.key || col.value) == row, + )?.type + + if ( + fieldType && + ['Date', 'Datetime'].includes(fieldType) && + !['modified', 'creation'].includes(row) + ) { + _rows[row] = formatDate( + emailTemplate[row], + '', + true, + fieldType == 'Datetime', + ) + } + if (['modified', 'creation'].includes(row)) { _rows[row] = { label: formatDate(emailTemplate[row]), diff --git a/frontend/src/pages/Leads.vue b/frontend/src/pages/Leads.vue index 37f9e860..8be6fa7b 100644 --- a/frontend/src/pages/Leads.vue +++ b/frontend/src/pages/Leads.vue @@ -349,15 +349,16 @@ const rows = computed(() => { return getGroupedByRows( leads.value?.data.data, leads.value?.data.group_by_field, + leads.value.data.columns, ) } else if (leads.value.data.view_type === 'kanban') { - return getKanbanRows(leads.value.data.data) + return getKanbanRows(leads.value.data.data, leads.value.data.fields) } else { - return parseRows(leads.value?.data.data) + return parseRows(leads.value?.data.data, leads.value.data.columns) } }) -function getGroupedByRows(listRows, groupByField) { +function getGroupedByRows(listRows, groupByField, columns) { let groupedRows = [] groupByField.options?.forEach((option) => { @@ -373,7 +374,7 @@ function getGroupedByRows(listRows, groupByField) { label: groupByField.label, group: option || __(' '), collapsed: false, - rows: parseRows(filteredRows), + rows: parseRows(filteredRows, columns), } if (groupByField.name == 'status') { groupDetail.icon = () => @@ -387,22 +388,34 @@ function getGroupedByRows(listRows, groupByField) { return groupedRows || listRows } -function getKanbanRows(data) { +function getKanbanRows(data, columns) { let _rows = [] data.forEach((column) => { column.data?.forEach((row) => { _rows.push(row) }) }) - return parseRows(_rows) + return parseRows(_rows, columns) } -function parseRows(rows) { +function parseRows(rows, columns = []) { return rows.map((lead) => { let _rows = {} leads.value?.data.rows.forEach((row) => { _rows[row] = lead[row] + let fieldType = columns?.find( + (col) => (col.key || col.value) == row, + )?.type + + if ( + fieldType && + ['Date', 'Datetime'].includes(fieldType) && + !['modified', 'creation'].includes(row) + ) { + _rows[row] = formatDate(lead[row], '', true, fieldType == 'Datetime') + } + if (row == 'lead_name') { _rows[row] = { label: lead.lead_name, diff --git a/frontend/src/pages/Organizations.vue b/frontend/src/pages/Organizations.vue index 0ddadcb5..9474af27 100644 --- a/frontend/src/pages/Organizations.vue +++ b/frontend/src/pages/Organizations.vue @@ -103,6 +103,23 @@ const rows = computed(() => { organizations.value?.data.rows.forEach((row) => { _rows[row] = organization[row] + let fieldType = organizations.value?.data.columns?.find( + (col) => (col.key || col.value) == row, + )?.type + + if ( + fieldType && + ['Date', 'Datetime'].includes(fieldType) && + !['modified', 'creation'].includes(row) + ) { + _rows[row] = formatDate( + organization[row], + '', + true, + fieldType == 'Datetime', + ) + } + if (row === 'organization_name') { _rows[row] = { label: organization.organization_name, diff --git a/frontend/src/pages/Tasks.vue b/frontend/src/pages/Tasks.vue index 7787d864..e86de996 100644 --- a/frontend/src/pages/Tasks.vue +++ b/frontend/src/pages/Tasks.vue @@ -237,28 +237,40 @@ const rows = computed(() => { if (!tasks.value?.data?.data) return [] if (tasks.value.data.view_type === 'kanban') { - return getKanbanRows(tasks.value.data.data) + return getKanbanRows(tasks.value.data.data, tasks.value.data.fields) } - return parseRows(tasks.value?.data.data) + return parseRows(tasks.value?.data.data, tasks.value?.data.columns) }) -function getKanbanRows(data) { +function getKanbanRows(data, columns) { let _rows = [] data.forEach((column) => { column.data?.forEach((row) => { _rows.push(row) }) }) - return parseRows(_rows) + return parseRows(_rows, columns) } -function parseRows(rows) { +function parseRows(rows, columns = []) { return rows.map((task) => { let _rows = {} tasks.value?.data.rows.forEach((row) => { _rows[row] = task[row] + let fieldType = columns?.find( + (col) => (col.key || col.value) == row, + )?.type + + if ( + fieldType && + ['Date', 'Datetime'].includes(fieldType) && + !['modified', 'creation'].includes(row) + ) { + _rows[row] = formatDate(task[row], '', true, fieldType == 'Datetime') + } + if (['modified', 'creation'].includes(row)) { _rows[row] = { label: formatDate(task[row]), diff --git a/frontend/src/utils/callLog.js b/frontend/src/utils/callLog.js index 54bf732a..ee006d1a 100644 --- a/frontend/src/utils/callLog.js +++ b/frontend/src/utils/callLog.js @@ -5,7 +5,7 @@ import { contactsStore } from '@/stores/contacts' const { getUser } = usersStore() const { getContact, getLeadContact } = contactsStore() -export function getCallLogDetail(row, log) { +export function getCallLogDetail(row, log, columns = []) { let incoming = log.type === 'Incoming' if (row === 'caller') { @@ -51,6 +51,13 @@ export function getCallLogDetail(row, log) { timeAgo: __(timeAgo(log[row])), } } + + let fieldType = columns?.find((col) => (col.key || col.value) == row)?.type + + if (fieldType && ['Date', 'Datetime'].includes(fieldType)) { + return formatDate(log[row], '', true, fieldType == 'Datetime') + } + return log[row] } diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.js index 4bb9b7f6..b0e44f5e 100644 --- a/frontend/src/utils/index.js +++ b/frontend/src/utils/index.js @@ -3,7 +3,7 @@ import TaskPriorityIcon from '@/components/Icons/TaskPriorityIcon.vue' import { usersStore } from '@/stores/users' import { gemoji } from 'gemoji' import { useTimeAgo } from '@vueuse/core' -import { toast, convertToUserTimezone } from 'frappe-ui' +import { toast, convertToUserTimezone, luxonDate } from 'frappe-ui' import { h } from 'vue' export function createToast(options) { @@ -38,10 +38,34 @@ export function formatTime(seconds) { return formattedTime.trim() } -export function formatDate(date, format = 'EEE, MMM d, yyyy h:mm a') { +export function formatDate(date, format, onlyDate = false, onlyTime = false) { + format = getFormat(date, format, onlyDate, onlyTime, false) return convertToUserTimezone(date, format) } +export function getFormat( + date, + format, + onlyDate = false, + onlyTime = false, + withDate = true, +) { + if (!date) return '' + let dateFormat = + window.sysdefaults.date_format.replace('mm', 'MM') || 'yyyy-MM-dd' + let timeFormat = window.sysdefaults.time_format || 'HH:mm:ss' + format = format || 'EEE, MMM d, yyyy h:mm a' + + if (onlyDate) format = dateFormat + if (onlyTime) format = timeFormat + if (onlyTime && onlyDate) format = `${dateFormat} ${timeFormat}` + + if (withDate) { + return luxonDate(date).toFormat(format) + } + return format +} + export function timeAgo(date) { return useTimeAgo(date).value }