feat: Phone Control Typeversion-14
@@ -0,0 +1,47 @@ | |||
export default { | |||
name: "Doctype With Phone", | |||
actions: [], | |||
custom: 1, | |||
is_submittable: 1, | |||
autoname: "field:title", | |||
creation: '2022-03-30 06:29:07.215072', | |||
doctype: 'DocType', | |||
engine: 'InnoDB', | |||
fields: [ | |||
{ | |||
fieldname: 'title', | |||
fieldtype: 'Data', | |||
label: 'title', | |||
unique: 1, | |||
}, | |||
{ | |||
fieldname: 'phone', | |||
fieldtype: 'Phone', | |||
label: 'Phone' | |||
} | |||
], | |||
links: [], | |||
modified: '2019-03-30 14:40:53.127615', | |||
modified_by: 'Administrator', | |||
naming_rule: "By fieldname", | |||
module: 'Custom', | |||
owner: 'Administrator', | |||
permissions: [ | |||
{ | |||
create: 1, | |||
delete: 1, | |||
email: 1, | |||
print: 1, | |||
read: 1, | |||
role: 'System Manager', | |||
share: 1, | |||
write: 1, | |||
submit: 1, | |||
cancel: 1 | |||
} | |||
], | |||
sort_field: 'modified', | |||
sort_order: 'ASC', | |||
track_changes: 1 | |||
}; |
@@ -0,0 +1,90 @@ | |||
import doctype_with_phone from '../fixtures/doctype_with_phone'; | |||
context("Control Phone", () => { | |||
before(() => { | |||
cy.login(); | |||
cy.visit("/app/website"); | |||
}); | |||
function get_dialog_with_phone() { | |||
return cy.dialog({ | |||
title: "Phone", | |||
fields: [{ | |||
"fieldname": "phone", | |||
"fieldtype": "Phone", | |||
}] | |||
}); | |||
} | |||
it("should set flag and data", () => { | |||
get_dialog_with_phone().as("dialog"); | |||
cy.get(".selected-phone").click(); | |||
cy.get(".phone-picker .phone-wrapper[id='afghanistan']").click(); | |||
cy.get(".selected-phone").click(); | |||
cy.get(".phone-picker .phone-wrapper[id='india']").click(); | |||
cy.get(".selected-phone .country").should("have.text", "+91"); | |||
cy.get(".selected-phone > img").should("have.attr", "src").and("include", "/in.svg"); | |||
let phone_number = "9312672712"; | |||
cy.get(".selected-phone > img").click().first(); | |||
cy.get_field("phone") | |||
.first() | |||
.click({multiple: true}); | |||
cy.get(".frappe-control[data-fieldname=phone]") | |||
.findByRole("textbox") | |||
.first() | |||
.type(phone_number, {force: true}); | |||
cy.get_field("phone").first().should("have.value", phone_number); | |||
cy.get_field("phone").first().blur({force: true}); | |||
cy.wait(100); | |||
cy.get("@dialog").then(dialog => { | |||
let value = dialog.get_value("phone"); | |||
expect(value).to.equal("+91-" + phone_number); | |||
}); | |||
}); | |||
it("case insensitive search for country and clear search", () => { | |||
let search_text = "india"; | |||
cy.get(".selected-phone").click().first(); | |||
cy.get(".phone-picker").findByRole("searchbox").click().type(search_text); | |||
cy.get(".phone-section .phone-wrapper:not(.hidden)").then(i => { | |||
cy.get(`.phone-section .phone-wrapper[id*="${search_text.toLowerCase()}"]`).then(countries => { | |||
expect(i.length).to.equal(countries.length); | |||
}); | |||
}); | |||
cy.get(".phone-picker").findByRole("searchbox").clear().blur(); | |||
cy.get(".phone-section .phone-wrapper").should("not.have.class", "hidden"); | |||
}); | |||
it("existing document should render phone field with data", () => { | |||
cy.visit("/app/doctype"); | |||
cy.insert_doc("DocType", doctype_with_phone, true); | |||
cy.clear_cache(); | |||
// Creating custom doctype | |||
cy.insert_doc("DocType", doctype_with_phone, true); | |||
cy.visit("/app/doctype-with-phone"); | |||
cy.click_listview_primary_button("Add Doctype With Phone"); | |||
// create a record | |||
cy.fill_field("title", "Test Phone 1"); | |||
cy.fill_field("phone", "+91-9823341234"); | |||
cy.get_field("phone").should("have.value", "9823341234"); | |||
cy.click_doc_primary_button("Save"); | |||
cy.get_doc("Doctype With Phone", "Test Phone 1").then((doc) => { | |||
let value = doc.data.phone; | |||
expect(value).to.equal("+91-9823341234"); | |||
}); | |||
// open the doc from list view | |||
cy.go_to_list("Doctype With Phone"); | |||
cy.clear_cache(); | |||
cy.click_listview_row_item(0); | |||
cy.title().should("eq", "Test Phone 1"); | |||
cy.get(".selected-phone .country").should("have.text", "+91"); | |||
cy.get(".selected-phone > img").should("have.attr", "src").and("include", "/in.svg"); | |||
cy.get_field("phone").should("have.value", "9823341234"); | |||
}); | |||
}); |
@@ -367,6 +367,10 @@ Cypress.Commands.add('click_listview_primary_button', (btn_name) => { | |||
cy.get('.primary-action').contains(btn_name).click({force: true}); | |||
}); | |||
Cypress.Commands.add('click_doc_primary_button', (btn_name) => { | |||
cy.get('.primary-action').contains(btn_name).click({force: true}); | |||
}); | |||
Cypress.Commands.add('click_timeline_action_btn', (btn_name) => { | |||
cy.get('.timeline-message-box .actions .action-btn').contains(btn_name).click(); | |||
}); | |||
@@ -11,6 +11,7 @@ from frappe.core.doctype.navbar_settings.navbar_settings import get_app_logo, ge | |||
from frappe.desk.doctype.route_history.route_history import frequently_visited_links | |||
from frappe.desk.form.load import get_meta_bundle | |||
from frappe.email.inbox import get_email_accounts | |||
from frappe.geo.country_info import get_all | |||
from frappe.model.base_document import get_controller | |||
from frappe.query_builder import DocType | |||
from frappe.query_builder.functions import Count | |||
@@ -67,6 +68,7 @@ def get_bootinfo(): | |||
bootinfo.home_folder = frappe.db.get_value("File", {"is_home_folder": 1}) | |||
bootinfo.navbar_settings = get_navbar_settings() | |||
bootinfo.notification_settings = get_notification_settings() | |||
get_country_codes(bootinfo) | |||
set_time_zone(bootinfo) | |||
# ipinfo | |||
@@ -384,6 +386,11 @@ def get_notification_settings(): | |||
return frappe.get_cached_doc("Notification Settings", frappe.session.user) | |||
def get_country_codes(bootinfo): | |||
country_codes = get_all() | |||
bootinfo.country_codes = frappe._dict(country_codes) | |||
@frappe.whitelist() | |||
def get_link_title_doctypes(): | |||
dts = frappe.get_all("DocType", {"show_title_field_in_link": 1}) | |||
@@ -99,7 +99,7 @@ | |||
"label": "Type", | |||
"oldfieldname": "fieldtype", | |||
"oldfieldtype": "Select", | |||
"options": "Autocomplete\nAttach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nJSON\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime", | |||
"options": "Autocomplete\nAttach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nJSON\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nPhone\nRead Only\nRating\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime", | |||
"reqd": 1, | |||
"search_index": 1 | |||
}, | |||
@@ -557,4 +557,4 @@ | |||
"sort_field": "modified", | |||
"sort_order": "ASC", | |||
"states": [] | |||
} | |||
} |
@@ -123,7 +123,7 @@ | |||
"label": "Field Type", | |||
"oldfieldname": "fieldtype", | |||
"oldfieldtype": "Select", | |||
"options": "Autocomplete\nAttach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nJSON\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime", | |||
"options": "Autocomplete\nAttach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nJSON\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nPhone\nRead Only\nRating\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime", | |||
"reqd": 1 | |||
}, | |||
{ | |||
@@ -87,7 +87,7 @@ | |||
"label": "Type", | |||
"oldfieldname": "fieldtype", | |||
"oldfieldtype": "Select", | |||
"options": "Autocomplete\nAttach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime", | |||
"options": "Autocomplete\nAttach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nPhone\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime", | |||
"reqd": 1, | |||
"search_index": 1 | |||
}, | |||
@@ -477,4 +477,4 @@ | |||
"sort_field": "modified", | |||
"sort_order": "ASC", | |||
"states": [] | |||
} | |||
} |
@@ -53,6 +53,7 @@ class MariaDBDatabase(Database): | |||
"Geolocation": ("longtext", ""), | |||
"Duration": ("decimal", "21,9"), | |||
"Icon": ("varchar", self.VARCHAR_LEN), | |||
"Phone": ("varchar", self.VARCHAR_LEN), | |||
"Autocomplete": ("varchar", self.VARCHAR_LEN), | |||
"JSON": ("json", ""), | |||
} | |||
@@ -65,6 +65,7 @@ class PostgresDatabase(Database): | |||
"Geolocation": ("text", ""), | |||
"Duration": ("decimal", "21,9"), | |||
"Icon": ("varchar", self.VARCHAR_LEN), | |||
"Phone": ("varchar", self.VARCHAR_LEN), | |||
"Autocomplete": ("varchar", self.VARCHAR_LEN), | |||
"JSON": ("json", ""), | |||
} | |||
@@ -36,6 +36,7 @@ data_fieldtypes = ( | |||
"Geolocation", | |||
"Duration", | |||
"Icon", | |||
"Phone", | |||
"Autocomplete", | |||
"JSON", | |||
) | |||
@@ -768,6 +768,10 @@ class BaseDocument(object): | |||
def _validate_data_fields(self): | |||
# data_field options defined in frappe.model.data_field_options | |||
for phone_field in self.meta.get_phone_fields(): | |||
phone = self.get(phone_field.fieldname) | |||
frappe.utils.validate_phone_number_with_country_code(phone, phone_field.fieldname) | |||
for data_field in self.meta.get_data_fields(): | |||
data = self.get(data_field.fieldname) | |||
data_field_options = data_field.get("options") | |||
@@ -162,6 +162,9 @@ class Meta(Document): | |||
def get_data_fields(self): | |||
return self.get("fields", {"fieldtype": "Data"}) | |||
def get_phone_fields(self): | |||
return self.get("fields", {"fieldtype": "Phone"}) | |||
def get_dynamic_link_fields(self): | |||
if not hasattr(self, "_dynamic_link_fields"): | |||
self._dynamic_link_fields = self.get("fields", {"fieldtype": "Dynamic Link"}) | |||
@@ -39,6 +39,7 @@ import './multiselect_list'; | |||
import './rating'; | |||
import './duration'; | |||
import './icon'; | |||
import './phone'; | |||
import './json'; | |||
frappe.ui.form.make_control = function (opts) { | |||
@@ -11,7 +11,7 @@ frappe.ui.form.ControlIcon = class ControlIcon extends frappe.ui.form.ControlDat | |||
get_all_icons() { | |||
frappe.symbols = []; | |||
$("#frappe-symbols > symbol[id]").each(function() { | |||
frappe.symbols.push(this.id.replace('icon-', '')); | |||
this.id.includes('icon-') && frappe.symbols.push(this.id.replace('icon-', '')); | |||
}); | |||
} | |||
@@ -0,0 +1,197 @@ | |||
import PhonePicker from '../../phone_picker/phone_picker'; | |||
frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlData { | |||
make_input() { | |||
super.make_input(); | |||
this.setup_country_code_picker(); | |||
this.input_events(); | |||
} | |||
input_events() { | |||
this.$input.keydown((e) => { | |||
const key_code = e.keyCode; | |||
if ([frappe.ui.keyCode.BACKSPACE].includes(key_code)) { | |||
if (this.$input.val().length == 0) { | |||
this.country_code_picker.reset(); | |||
} | |||
} | |||
}); | |||
// Replaces code when selected and removes previously selected. | |||
this.country_code_picker.on_change = (country) => { | |||
if (!country) { | |||
return this.reset_inputx(); | |||
} | |||
const country_code = frappe.boot.country_codes[country].code; | |||
const country_isd = frappe.boot.country_codes[country].isd; | |||
this.set_flag(country_code); | |||
this.$icon = this.selected_icon.find('svg'); | |||
this.$flag = this.selected_icon.find('img'); | |||
if (!this.$icon.hasClass('hide')) { | |||
this.$icon.toggleClass('hide'); | |||
} | |||
if (!this.$flag.length) { | |||
this.selected_icon.prepend(this.get_country_flag(country)); | |||
} | |||
if (!this.$isd.length) { | |||
this.selected_icon.append($(`<span class= "country"> ${country_isd}</span>`)); | |||
} else { | |||
this.$isd.text(country_isd); | |||
} | |||
if (this.$input.val()) { | |||
this.set_value(this.get_country(country) +'-'+ this.$input.val()); | |||
} | |||
this.update_padding(); | |||
// hide popover and focus input | |||
this.$wrapper.popover('hide'); | |||
this.$input.focus(); | |||
}; | |||
this.$wrapper.find('.selected-phone').on('click', (e) => { | |||
this.$wrapper.popover('toggle'); | |||
e.stopPropagation(); | |||
$('body').on('click.phone-popover', (ev) => { | |||
if (!$(ev.target).parents().is('.popover')) { | |||
this.$wrapper.popover('hide'); | |||
} | |||
}); | |||
$(window).on('hashchange.phone-popover', () => { | |||
this.$wrapper.popover('hide'); | |||
}); | |||
}); | |||
} | |||
setup_country_code_picker() { | |||
let picker_wrapper = $('<div>'); | |||
this.country_code_picker = new PhonePicker({ | |||
parent: picker_wrapper, | |||
countries: frappe.boot.country_codes | |||
}); | |||
this.$wrapper.popover({ | |||
trigger: 'manual', | |||
offset: `${-this.$wrapper.width() / 4.5}, 5`, | |||
boundary: 'viewport', | |||
placement: 'bottom', | |||
template: ` | |||
<div class="popover phone-picker-popover"> | |||
<div class="picker-arrow arrow"></div> | |||
<div class="popover-body popover-content"></div> | |||
</div> | |||
`, | |||
content: () => picker_wrapper, | |||
html: true | |||
}).on('show.bs.popover', () => { | |||
setTimeout(() => { | |||
this.country_code_picker.refresh(); | |||
this.country_code_picker.search_input.focus(); | |||
}, 10); | |||
}).on('hidden.bs.popover', () => { | |||
$('body').off('click.phone-popover'); | |||
$(window).off('hashchange.phone-popover'); | |||
}); | |||
// Default icon when nothing is selected. | |||
this.selected_icon = this.$wrapper.find('.selected-phone'); | |||
let input_value = this.get_input_value(); | |||
if (!this.selected_icon.length) { | |||
this.selected_icon = $(`<div class="selected-phone">${frappe.utils.icon("down", "sm")}</div>`); | |||
this.selected_icon.insertAfter(this.$input); | |||
this.selected_icon.append($(`<span class= "country"></span>`)); | |||
this.$isd = this.selected_icon.find('.country'); | |||
if (input_value && input_value.split("-").length == 2) { | |||
this.$isd.text(this.value.split("-")[0]); | |||
} | |||
} | |||
} | |||
refresh() { | |||
super.refresh(); | |||
// Previously opened doc values showing up on a new doc | |||
if (this.frm && this.frm.doc.__islocal && !this.get_value()) { | |||
this.reset_input(); | |||
} | |||
} | |||
reset_input() { | |||
this.$input.val(""); | |||
this.$wrapper.find('.country').text(""); | |||
if (this.selected_icon.find('svg').hasClass('hide')) { | |||
this.selected_icon.find('svg').toggleClass('hide'); | |||
this.selected_icon.find('img').addClass('hide'); | |||
} | |||
this.$input.css("padding-left", 30); | |||
} | |||
set_formatted_input(value) { | |||
if (value && value.includes('-') && value.split('-').length == 2) { | |||
let isd = this.value.split("-")[0]; | |||
this.get_country_code_and_change_flag(isd); | |||
this.country_code_picker.set_country(isd); | |||
this.country_code_picker.refresh(); | |||
if (this.country_code_picker.country && this.country_code_picker.country !== this.$isd.text()) { | |||
this.$isd.length && this.$isd.text(isd); | |||
} | |||
this.update_padding(); | |||
this.$input.val(value.split('-').pop()); | |||
} else if (this.$isd.text().trim() && this.value) { | |||
let code_number = this.$isd.text() + '-' + value; | |||
this.set_value(code_number); | |||
} | |||
} | |||
get_value() { | |||
return this.value; | |||
} | |||
set_flag(country_code) { | |||
this.selected_icon.find('img').attr('src', `https://flagcdn.com/${country_code}.svg`); | |||
this.$icon = this.selected_icon.find('img'); | |||
this.$icon.hasClass('hide') && this.$icon.toggleClass('hide'); | |||
} | |||
// country_code for India is 'in' | |||
get_country_code_and_change_flag(isd) { | |||
let country_data = frappe.boot.country_codes; | |||
let flag = this.selected_icon.find('img'); | |||
for (const country in country_data) { | |||
if (Object.values(country_data[country]).includes(isd)) { | |||
let code = country_data[country].code; | |||
flag = this.selected_icon.find('img'); | |||
if (!flag.length) { | |||
this.selected_icon.prepend(this.get_country_flag(country)); | |||
this.selected_icon.find('svg').addClass('hide'); | |||
} else { | |||
this.set_flag(code); | |||
} | |||
} | |||
} | |||
} | |||
get_country(country) { | |||
const country_codes = frappe.boot.country_codes; | |||
return country_codes[country].isd; | |||
} | |||
get_country_flag(country) { | |||
const country_codes = frappe.boot.country_codes; | |||
let code = country_codes[country].code; | |||
return frappe.utils.flag(code); | |||
} | |||
update_padding() { | |||
let len = this.$isd.text().length; | |||
let diff = len - 2; | |||
if (len > 2) { | |||
this.$input.css("padding-left", 60 + (diff * 7)); | |||
} else { | |||
this.$input.css("padding-left", 60); | |||
} | |||
} | |||
}; |
@@ -0,0 +1,103 @@ | |||
class PhonePicker { | |||
constructor(opts) { | |||
this.parent = opts.parent; | |||
this.width = opts.width; | |||
this.height = opts.height; | |||
this.country = opts.country; | |||
opts.country && this.set_country(opts.country); | |||
this.countries = opts.countries; | |||
this.setup_picker(); | |||
} | |||
refresh() { | |||
this.update_icon_selected(true); | |||
} | |||
setup_picker() { | |||
this.phone_picker_wrapper = $(` | |||
<div class="phone-picker"> | |||
<div class="search-phones"> | |||
<input type="search" placeholder="${__('Search for countries...')}" class="form-control"> | |||
<span class="search-phone">${frappe.utils.icon('search', "sm")}</span> | |||
</div> | |||
<div class="phone-section"> | |||
<div class="phones"></div> | |||
</div> | |||
</div> | |||
`); | |||
this.parent.append(this.phone_picker_wrapper); | |||
this.phone_wrapper = this.phone_picker_wrapper.find('.phones'); | |||
this.search_input = this.phone_picker_wrapper.find('.search-phones > input'); | |||
this.refresh(); | |||
this.setup_countries(); | |||
} | |||
setup_countries() { | |||
Object.entries(this.countries).forEach(([country, info]) => { | |||
if (!info.isd) { | |||
return; | |||
} | |||
let $country = $(` | |||
<div id="${country.toLowerCase()}" class="phone-wrapper"> | |||
${frappe.utils.flag(info.code)} | |||
<span class="country">${country} (${info.isd})</span> | |||
</div> | |||
`); | |||
this.phone_wrapper.append($country); | |||
const set_values = () => { | |||
this.set_country(country); | |||
this.update_icon_selected(); | |||
}; | |||
$country.on('click', () => { | |||
set_values(); | |||
}); | |||
$country.hover(() => { | |||
$country.toggleClass("bg-gray-100"); | |||
}); | |||
this.search_input.keydown((e) => { | |||
const key_code = e.keyCode; | |||
if ([13].includes(key_code)) { | |||
e.preventDefault(); | |||
set_values(); | |||
} | |||
}); | |||
this.search_input.keyup((e) => { | |||
e.preventDefault(); | |||
this.filter_icons(); | |||
}); | |||
this.search_input.on('search', () => { | |||
this.filter_icons(); | |||
}); | |||
}); | |||
} | |||
filter_icons() { | |||
let value = this.search_input.val(); | |||
if (!value) { | |||
this.phone_wrapper.find(".phone-wrapper").removeClass('hidden'); | |||
} else { | |||
this.phone_wrapper.find(".phone-wrapper").addClass('hidden'); | |||
this.phone_wrapper.find(`.phone-wrapper[id*='${value.toLowerCase()}']`).removeClass('hidden'); | |||
} | |||
} | |||
update_icon_selected(silent) { | |||
!silent && this.on_change && this.on_change(this.get_country()); | |||
} | |||
set_country(country) { | |||
this.country = country || ''; | |||
} | |||
get_country() { | |||
return this.country; | |||
} | |||
reset() { | |||
this.set_country(); | |||
this.update_icon_selected(); | |||
} | |||
} | |||
export default PhonePicker; |
@@ -1192,6 +1192,12 @@ Object.assign(frappe.utils, { | |||
</svg>`; | |||
}, | |||
flag(country_code) { | |||
return `<img | |||
src="https://flagcdn.com/${country_code}.svg" | |||
width="20" height="15">`; | |||
}, | |||
make_chart(wrapper, custom_options={}) { | |||
let chart_args = { | |||
type: 'bar', | |||
@@ -2,6 +2,7 @@ | |||
@import "color_picker"; | |||
@import "icon_picker"; | |||
@import "datepicker"; | |||
@import "phone_picker"; | |||
// password | |||
.form-control[data-fieldtype="Password"] { | |||
@@ -0,0 +1,144 @@ | |||
.phone-picker { | |||
font-size: var(--text-xs); | |||
color: var(--text-muted); | |||
--phone-picker-width: 290px; | |||
width: var(--phone-picker-width); | |||
.phones { | |||
margin-top: 10px; | |||
display: flex; | |||
flex-wrap: wrap; | |||
overflow-y: scroll; | |||
max-height: 210px; | |||
cursor: pointer; | |||
/* Hide scrollbar for IE, Edge and Firefox */ | |||
-ms-overflow-style: none; /* IE and Edge */ | |||
scrollbar-width: none; /* Firefox */ | |||
/* Hide scrollbar for Chrome, Safari and Opera */ | |||
&::-webkit-scrollbar { | |||
display: none; | |||
} | |||
.phone-wrapper { | |||
display: flex; | |||
width: 290px; | |||
height: 30px; | |||
text-align: center; | |||
align-items: center; | |||
border-radius: 0.375rem; | |||
padding: 0.5rem; | |||
img { | |||
height: 15px; | |||
} | |||
.country { | |||
display: flex; | |||
margin-left: 0.6rem; | |||
flex-grow: 1; | |||
width: 290px; | |||
} | |||
} | |||
} | |||
.search-phones { | |||
position: relative; | |||
input[type='search'] { | |||
height: inherit; | |||
padding-left: 30px; | |||
} | |||
.search-phone { | |||
position: absolute; | |||
top: 7px; | |||
left: 7px; | |||
} | |||
} | |||
} | |||
.phone-picker-popover { | |||
max-width: 325px; | |||
left: 29px !important; | |||
.picker-arrow { | |||
left: 15px !important; | |||
} | |||
@media (max-width: 992px) { | |||
max-width: 325px; | |||
left: 48px !important; | |||
} | |||
} | |||
.frappe-control[data-fieldtype='Phone'] | |||
{ | |||
input { | |||
padding-left: 30px; | |||
} | |||
.selected-phone { | |||
display: flex; | |||
cursor: pointer; | |||
height: 20px; | |||
border-radius: 5px; | |||
position: absolute; | |||
top: calc(50% + 2px); | |||
left: 8px; | |||
content: ' '; | |||
align-items: center; | |||
z-index: 1; | |||
.country { | |||
display: flex; | |||
margin-left: 0.6rem; | |||
align-items: flex-end; | |||
flex-grow: 1; | |||
} | |||
img { | |||
height: 15px; | |||
} | |||
} | |||
.like-disabled-input { | |||
.phone-value { | |||
padding-left: 25px; | |||
} | |||
.selected-phone { | |||
top: 20%; | |||
cursor: default; | |||
} | |||
} | |||
} | |||
.modal-body { | |||
.frappe-control[data-fieldtype='Phone'] | |||
{ | |||
.selected-phone { | |||
top: calc(50% - 0.5px); | |||
} | |||
} | |||
} | |||
.data-row.row { | |||
.selected-phone { | |||
top: calc(50% - 10.1px); | |||
z-index: 2; | |||
} | |||
} | |||
.bg-gray-100 { | |||
--tw-bg-opacity: 1; | |||
background-color: rgba(244,245,246,var(--tw-bg-opacity)); | |||
} | |||
.dt-cell__content { | |||
.selected-phone { | |||
display: contents; | |||
} | |||
} | |||
.dt-cell__edit, .filter-field { | |||
.selected-phone { | |||
top: 5.5px !important; | |||
} | |||
} |
@@ -83,6 +83,33 @@ def extract_email_id(email): | |||
return email_id | |||
def validate_phone_number_with_country_code(phone_number, fieldname): | |||
from phonenumbers import NumberParseException, is_valid_number, parse | |||
from frappe import _ | |||
if not phone_number: | |||
return | |||
valid_number = False | |||
error_message = _("Phone Number {0} set in field {1} is not valid.") | |||
error_title = _("Invalid Phone Number") | |||
try: | |||
if valid_number := is_valid_number(parse(phone_number)): | |||
return True | |||
except NumberParseException as e: | |||
if e.error_type == NumberParseException.INVALID_COUNTRY_CODE: | |||
error_message = _("Please select a country code for field {1}.") | |||
error_title = _("Country Code Required") | |||
finally: | |||
if not valid_number: | |||
frappe.throw( | |||
error_message.format(frappe.bold(phone_number), frappe.bold(fieldname)), | |||
title=error_title, | |||
exc=frappe.InvalidPhoneNumberError, | |||
) | |||
def validate_phone_number(phone_number, throw=False): | |||
"""Returns True if valid phone number""" | |||
if not phone_number: | |||
@@ -72,3 +72,5 @@ zxcvbn-python~=4.4.24 | |||
tenacity~=8.0.1 | |||
cairocffi==1.2.0 | |||
WeasyPrint==52.5 | |||
phonenumbers==8.12.40 | |||