communication fix

This commit is contained in:
Tsanie Lily 2023-04-13 17:36:42 +08:00
parent d9beb0d3b3
commit d702197a3f
11 changed files with 365 additions and 218 deletions

View File

@ -3,7 +3,7 @@
box-sizing: border-box;
display: flex;
flex-direction: column;
overflow-x: hidden;
overflow: visible;
& {
--hover-bg-color: lightyellow;

View File

@ -9,7 +9,7 @@
}
.tooltip-wrapper {
position: fixed;
position: absolute;
word-wrap: break-word;
height: auto;
text-align: left;

View File

@ -45,6 +45,38 @@ class Contact {
txt.maxLength = 2000;
txt.style.height = '100px';
});
const buttons = [];
if (this.#option.company) {
buttons.push({
text: c == null ? r('addContactRecord', 'Add Contact Record') : r('editContactRecord', 'Edit Contact Record'),
trigger: () => {
const item = this.prepare();
if (item == null) {
return false;
}
item.SaveToCustomer = 1;
if (typeof this.#option.onSave === 'function') {
return this.#option.onSave.call(this, item, c == null);
}
}
});
}
buttons.push(
{
text: r('workOrderOnly', 'Work Order Only'),
trigger: () => {
const item = this.prepare();
if (item == null) {
return false;
}
item.SaveToCustomer = 0;
if (typeof this.#option.onSave === 'function') {
return this.#option.onSave.call(this, item, c == null);
}
}
},
{ text: r('cancel', 'Cancel') }
);
const popup = createPopup(
c == null ? r('addContact', 'Add Contact') : r('editContact', 'Edit Contact'),
createElement('div', wrapper => {
@ -76,33 +108,7 @@ class Contact {
contactNotes
)
),
{
text: c == null ? r('addContactRecord', 'Add Contact Record') : r('editContactRecord', 'Edit Contact Record'),
trigger: () => {
const item = this.prepare();
if (item == null) {
return false;
}
item.SaveToCustomer = 1;
if (typeof this.#option.onSave === 'function') {
return this.#option.onSave.call(this, item, c == null);
}
}
},
{
text: r('workOrderOnly', 'Work Order Only'),
trigger: () => {
const item = this.prepare();
if (item == null) {
return false;
}
item.SaveToCustomer = 0;
if (typeof this.#option.onSave === 'function') {
return this.#option.onSave.call(this, item, c == null);
}
}
},
{ text: r('cancel', 'Cancel') }
...buttons
)
if (c != null) {
contactName.value = c.Name;
@ -128,35 +134,42 @@ class Contact {
}
prepare() {
const item = {
'Id': this.#option.contact?.Id,
'Name': this.#refs.contactName.value,
'ContactPreference': this.#refs.preferences.selected.value,
'Email': this.#refs.contactEmail.value,
'MobilePhone': this.#refs.contactMobile.value,
'OptOut': this.#refs.checkOpt.querySelector('input').checked,
'Notes': this.#refs.contactNotes.value
};
const name = this.#refs.contactName.value;
const pref = this.#refs.preferences.selected.value;
const email = this.#refs.contactEmail.value;
const phone = this.#refs.contactMobile.value;
const opt = this.#refs.checkOpt.querySelector('input').checked;
const notes = this.#refs.contactNotes.value;
const title = this.#option.contact == null ? r('addContact', 'Add Contact') : r('editContact', 'Edit Contact');
if (nullOrEmpty(item.Name)) {
if (nullOrEmpty(name)) {
showAlert(title, r('contactNameRequired', 'Contact Name cannot be empty.'), 'warn')
.then(() => this.#refs.contactName.focus());
return null;
}
if (nullOrEmpty(item.Email) && nullOrEmpty(item.MobilePhone)) {
if (nullOrEmpty(email) && nullOrEmpty(phone)) {
showAlert(title, r('contactEmailPhoneRequired', 'Email and Mobile Phone cannot both be empty.'), 'warn')
.then(() => nullOrEmpty(item.Email) ?
.then(() => nullOrEmpty(email) ?
this.#refs.contactEmail.focus() :
this.#refs.contactMobile.focus());
return null;
}
if (!nullOrEmpty(item.Email) && !isEmail(item.Email)) {
if (!nullOrEmpty(email) && !isEmail(email)) {
showAlert(title, r('contactEmailInvalid', 'The email address is invalid.'), 'warn')
.then(() => this.#refs.contactEmail.focus());
return null;
}
return item;
let contact = this.#option.contact;
if (contact == null) {
contact = {};
}
contact.Name = name;
contact.ContactPreference = pref;
contact.Email = email;
contact.MobilePhone = phone;
contact.OptOut = opt;
contact.Notes = notes;
return contact;
}
}

View File

@ -1,4 +1,4 @@
import { Grid, createElement, setTooltip, createIcon, createCheckbox, createRadiobox, createPopup, showAlert, showConfirm } from "../../ui";
import { Grid, createElement, setTooltip, setTooltipNext, createIcon, createCheckbox, createRadiobox, createPopup, showAlert, showConfirm } from "../../ui";
import { r, nullOrEmpty, formatUrl, isEmail, isPhone } from "../../utility";
import { createBox } from "./lib";
import Contact from "./contact";
@ -13,11 +13,11 @@ class NoteCol extends Grid.GridColumn {
return wrapper;
}
static setValue(element, _val, item) {
static setValue(element, _val, item, _col, grid) {
const name = element.querySelector('.contact-name');
name.innerText = item.Name;
if (name.scrollWidth > name.offsetWidth) {
setTooltip(name, item.Name);
setTooltip(name, item.Name, false, grid.element);
}
element.querySelector('.contact-note').innerText = item.Notes;
}
@ -28,6 +28,7 @@ class CustomerCommunication {
#option;
#contacts;
#followers;
#buttonFollower;
#enter;
#message;
#data = {};
@ -115,20 +116,42 @@ class CustomerCommunication {
const mp = String(c.MobilePhone).trim();
const email = String(c.Email).trim();
const pref = String(c.ContactPreference);
if ((pref === '0' || pref === '2') && !isPhone(mp) ||
if ((pref !== '1') && !isPhone(mp) ||
pref === '1' && !isEmail(email)) {
continue;
}
const to = pref === '0' || pref === '2' ? mp : email;
const to = pref === '1' ? email : mp;
let icon;
let method;
switch (pref) {
case '0':
icon = 'comment-lines';
method = r('textsToColon', 'Texts to:');
break;
case '2':
icon = 'mobile';
method = r('callsToColon', 'Calls to:');
break;
default:
icon = 'envelope';
method = r('emailsToColon', 'Emails to:');
break;
}
const span = createElement('span', span => {
span.dataset.to = to;
span.dataset.name = c.Name;
span.innerText = c.Name;
});
const item = createElement('div', 'contact-item',
createIcon('fa-light', pref === '0' ? 'comment-lines' : pref === '2' ? 'mobile' : 'envelope'),
setTooltip(createElement('span', span => {
span.dataset.to = to;
span.dataset.name = c.Name;
span.innerText = nullOrEmpty(to) ? c.Name : to;
}), to, true)
createIcon('fa-light', icon),
span
);
this.#contacts.appendChild(item);
let tip = `${method} ${to}`;
if (span.scrollWidth > span.offsetWidth) {
tip = r('nameColon', 'Name:') + ` ${c.Name}\n${tip}`;
}
setTooltip(span, tip);
}
this.#message.scrollTop = this.#message.scrollHeight
}
@ -145,6 +168,7 @@ class CustomerCommunication {
this.#container.querySelector('.button-edit-contacts').style.display = flag === true ? 'none' : '';
this.#container.querySelector('.button-edit-followers').style.display = flag === true ? 'none' : '';
this.#enter.disabled = flag === true;
this.#container.querySelector('.button-send-message').style.display = flag === true ? 'none' : '';
}
/**
@ -156,6 +180,21 @@ class CustomerCommunication {
return;
}
this.#container.querySelector('.button-edit-contacts').style.display = flag === true ? 'none' : '';
this.#container.querySelector('.button-edit-followers').style.display = flag === true ? 'none' : '';
}
/**
* @param {String} name
*/
set companyName(name) {
this.#option.companyName = name;
const div = this.#container.querySelector('.title-company');
if (nullOrEmpty(name)) {
div.style.display = 'none';
} else {
div.innerText = name;
div.style.display = '';
}
}
get followers() {
@ -169,6 +208,7 @@ class CustomerCommunication {
this.#followers.replaceChildren();
if (followers?.length > 0) {
this.#container.querySelector('.follower-bar').style.display = '';
this.#container.querySelector('.follower-bar>.bar-list').appendChild(this.#buttonFollower);
for (let f of followers) {
if (f.OptOut) {
continue;
@ -177,28 +217,40 @@ class CustomerCommunication {
const email = String(f.Email).trim();
const tips = [];
if (f.SendEmail) {
tips.push(r('emailColon', 'Email:') + ` ${email}`);
tips.push(r('emailsToColon', 'Emails to:') + ` ${email}`);
}
if (f.SendText) {
tips.push(r('phoneColon', 'Mobile Phone:' + ` ${mp}`));
tips.push(r('textsToColon', 'Texts to:' + ` ${mp}`));
}
let icon;
if (f.SendText && f.SendEmail) {
icon = 'at';
} else {
icon = f.SendText ? 'comment-lines' : 'envelope';
}
const span = createElement('span', span => {
if (f.SendEmail) {
span.dataset.email = email;
}
if (f.SendText) {
span.dataset.mp = mp;
}
span.dataset.name = f.Name;
span.innerText = f.Name;
});
const item = createElement('div', 'contact-item',
createIcon('fa-light', f.SendText ? 'comment-lines' : 'envelope'),
setTooltip(createElement('span', span => {
if (f.SendEmail) {
span.dataset.email = email;
}
if (f.SendText) {
span.dataset.mp = mp;
}
span.dataset.name = f.Name;
span.innerText = f.SendText ? mp : email;
}), tips.join('\n'), true)
createIcon('fa-light', icon),
span
);
this.#followers.appendChild(item);
if (span.scrollWidth > span.offsetWidth) {
tips.splice(0, 0, r('nameColon', 'Name:') + ` ${c.Name}`);
}
setTooltip(span, tips.join('\n'));
}
} else {
this.#container.querySelector('.follower-bar').style.display = 'none';
this.#container.querySelector('.button-edit-contacts').insertAdjacentElement('beforebegin', this.#buttonFollower)
}
this.#message.scrollTop = this.#message.scrollHeight
}
@ -251,6 +303,7 @@ class CustomerCommunication {
);
// contacts
const readonly = option.readonly;
const recordReadonly = option.recordReadonly;
const contacts = createElement('div');
container.append(
createElement('div', 'contact-bar',
@ -264,7 +317,7 @@ class CustomerCommunication {
createElement('button', button => {
button.className = 'roundbtn button-edit-contacts';
button.style.backgroundColor = 'rgb(1, 199, 172)';
if (readonly === true || option.recordReadonly) {
if (readonly === true) {
button.style.display = 'none';
}
button.appendChild(createIcon('fa-solid', 'user-edit'));
@ -273,6 +326,7 @@ class CustomerCommunication {
const pop = createPopup(
createElement('div', div => {
div.style.display = 'flex';
div.style.alignItems = 'center';
div.append(
createElement('div', div => {
div.className = 'popup-move';
@ -293,12 +347,16 @@ class CustomerCommunication {
button.style.backgroundColor = 'rgb(1, 199, 172)';
button.style.marginRight = '10px';
button.className = 'roundbtn button-add-contact';
if (recordReadonly) {
button.style.display = 'none';
}
button.appendChild(createIcon('fa-solid', 'user-plus', {
width: '16px',
height: '16px'
}));
button.addEventListener('click', () => {
const add = new Contact({
company: !nullOrEmpty(this.#data.companyCode),
onSave: (item) => {
const exists = this.#gridContact.source.some(s => s.Name === item.Name && s.MobilePhone === item.MobilePhone);
if (exists) {
@ -326,10 +384,16 @@ class CustomerCommunication {
}),
createElement('div', null,
createElement('div', div => {
if (nullOrEmpty(this.#data.companyCode)) {
div.style.display = 'none';
}
div.style.fontWeight = 'bold';
div.innerText = r('contactFromRecord', 'Contacts from Customer Record');
}),
createElement('div', div => {
if (nullOrEmpty(this.#data.companyCode)) {
div.style.display = 'none';
}
div.className = 'contacts-record';
div.style.maxHeight = '400px';
div.style.width = '660px';
@ -377,6 +441,7 @@ class CustomerCommunication {
const buttonCol = {
type: Grid.ColumnTypes.Icon,
width: 40,
visible: !recordReadonly,
align: 'center',
iconType: 'fa-light'
};
@ -390,6 +455,7 @@ class CustomerCommunication {
onclick: function () {
const edit = new Contact({
contact: this,
company: !nullOrEmpty(This.#data.companyCode),
onSave: item => {
const exists =
This.#gridContact.source.some(s => s !== this && s.Name === item.Name && s.MobilePhone === item.MobilePhone) ||
@ -427,7 +493,7 @@ class CustomerCommunication {
nameCol,
{ key: 'Email', width: 180 },
{ key: 'MobilePhone', width: 130 },
createEditCol(grid),
createEditCol(this),
{
key: 'delete',
...buttonCol,
@ -490,6 +556,11 @@ class CustomerCommunication {
});
grid.extraRows = customerRecords.filter(c => !nullOrEmpty(c.Notes)).length;
grid.source = customerRecords;
grid.selectedRowChanged = index => {
if (index >= 0 && this.#gridWo.selectedIndexes?.length > 0) {
this.#gridWo.selectedIndexes = [];
}
};
this.#gridContact = grid;
// contacts from work order only
@ -503,14 +574,14 @@ class CustomerCommunication {
nameCol,
{ key: 'Email', width: 180 },
{ key: 'MobilePhone', width: 130 },
createEditCol(gridWo),
createEditCol(this),
{
key: 'delete',
...buttonCol,
text: 'times',
tooltip: r('delete', 'Delete'),
events: {
onclick: () => {
onclick: function () {
showConfirm(r('remoteContact', 'Remove Contact'), r('removeFromWorkorder', 'You are removing {name} from work order.\n\nDo you want to Continue?').replace('{name}', this.Name), [
{ key: 'continue', text: r('continue', 'Continue') },
{ key: 'cancel', text: r('cancel', 'Cancel') }
@ -544,6 +615,11 @@ class CustomerCommunication {
});
gridWo.extraRows = workOrderOnly.filter(c => !nullOrEmpty(c.Notes)).length;
gridWo.source = workOrderOnly;
gridWo.selectedRowChanged = index => {
if (index >= 0 && this.#gridContact.selectedIndexes?.length > 0) {
this.#gridContact.selectedIndexes = [];
}
};
this.#gridWo = gridWo;
});
});
@ -554,6 +630,131 @@ class CustomerCommunication {
this.#contacts = contacts;
// followers
const followers = createElement('div');
const buttonEditFollower = createElement('button', button => {
button.className = 'roundbtn button-edit-followers';
button.style.backgroundColor = 'rgb(48, 107, 255)';
if (readonly === true || recordReadonly) {
button.style.display = 'none';
}
button.appendChild(createIcon('fa-solid', 'pen'));
setTooltip(button, r('editFollower', 'Edit Followers'));
button.addEventListener('click', () => {
const pop = createPopup(
createElement('div', div => {
div.style.display = 'flex';
div.style.alignItems = 'center';
div.append(
createElement('div', div => {
div.className = 'popup-move';
div.style.flex = '1 1 auto';
div.innerText = r('editContacts', 'Edit Contacts') + '\n' + r('followers', 'Followers');
}),
createElement('button', button => {
button.style.flex = '0 0 auto';
button.style.backgroundColor = 'rgb(1, 199, 172)';
button.style.marginRight = '10px';
button.className = 'roundbtn button-add-follower';
button.appendChild(createIcon('fa-solid', 'user-plus', {
width: '16px',
height: '16px'
}));
button.addEventListener('click', () => {
if (typeof this.#option.onInitFollower === 'function') {
this.#option.onInitFollower().then(data => {
if (typeof data === 'string') {
showAlert(r('customerRecord', 'Customer Record'), data, 'warn');
return;
}
const add = new Follower({
followers: data,
onOk: list => {
if (typeof this.#option.onAddFollower === 'function') {
const result = this.#option.onAddFollower(list);
if (typeof result?.then === 'function') {
return result.then(r => {
this.#gridFollower.source = r;
return r;
});
}
return false;
}
}
});
add.show(container);
})
}
});
setTooltip(button, r('addFollower', 'Add Follower'))
})
)
}),
createElement('div', null,
createElement('div', div => {
div.style.fontWeight = 'bold';
div.innerText = r('contactFromRecord', 'Contacts from Customer Record');
}),
createElement('div', div => {
div.className = 'followers-record';
div.style.maxHeight = '400px';
div.style.width = '660px';
})
)
);
pop.show(container).then(() => {
const grid = new Grid();
grid.height = 0;
grid.allowHtml = true;
grid.headerVisible = false;
grid.columns = [
{
key: 'type',
type: Grid.ColumnTypes.Icon,
width: 50,
filter: c => c.SendText ? 'comment-lines' : 'envelope',
className: 'icon-contact-type',
iconType: 'fa-light'
},
{ key: 'Name', width: 160 },
{ key: 'Email', width: 180 },
{ key: 'MobilePhone', width: 130 },
{
key: 'delete',
type: Grid.ColumnTypes.Icon,
width: 40,
align: 'center',
iconType: 'fa-light',
text: 'times',
tooltip: r('delete', 'Delete'),
events: {
onclick: function () {
showConfirm(
r('deleteFollower', 'Delete Follower'),
r('promptDeleteFollower', 'Do you want to delete this follower?'))
.then(result => {
if (result?.key === 'yes') {
if (typeof option.onDeleteFollower === 'function') {
option.onDeleteFollower(result.key, this);
}
const index = grid.source.indexOf(this);
if (index >= 0) {
const source = grid.source;
source.splice(index, 1);
grid.extraRows = source.filter(c => !nullOrEmpty(c.Notes)).length;
grid.source = source;
}
}
});
}
}
}
];
grid.init(pop.container.querySelector('.followers-record'));
grid.source = this.#data.followers;
this.#gridFollower = grid;
});
});
});
this.#buttonFollower = buttonEditFollower;
container.append(
createElement('div', div => {
div.className = 'contact-bar follower-bar';
@ -570,129 +771,7 @@ class CustomerCommunication {
), r('copied', 'Copied')),
createElement('div', 'bar-list',
followers,
createElement('button', button => {
button.className = 'roundbtn button-edit-followers';
button.style.backgroundColor = 'rgb(48, 107, 255)';
if (readonly === true) {
button.style.display = 'none';
}
button.appendChild(createIcon('fa-solid', 'pen'));
setTooltip(button, r('editFollower', 'Edit Followers'));
button.addEventListener('click', () => {
const pop = createPopup(
createElement('div', div => {
div.style.display = 'flex';
div.style.alignItems = 'center';
div.append(
createElement('div', div => {
div.className = 'popup-move';
div.style.flex = '1 1 auto';
div.innerText = r('editContacts', 'Edit Contacts') + '\n' + r('followers', 'Followers');
}),
createElement('button', button => {
button.style.flex = '0 0 auto';
button.style.backgroundColor = 'rgb(1, 199, 172)';
button.style.marginRight = '10px';
button.className = 'roundbtn button-add-follower';
button.appendChild(createIcon('fa-solid', 'user-plus', {
width: '16px',
height: '16px'
}));
button.addEventListener('click', () => {
if (typeof this.#option.onInitFollower === 'function') {
this.#option.onInitFollower().then(data => {
if (typeof data === 'string') {
showAlert(r('customerRecord', 'Customer Record'), data, 'warn');
return;
}
const add = new Follower({
followers: data,
onOk: list => {
if (typeof this.#option.onAddFollower === 'function') {
const result = this.#option.onAddFollower(list);
if (typeof result?.then === 'function') {
return result.then(r => {
this.#gridFollower.source = r;
return r;
});
}
return false;
}
}
});
add.show(container);
})
}
});
setTooltip(button, r('addFollower', 'Add Follower'))
})
)
}),
createElement('div', null,
createElement('div', div => {
div.style.fontWeight = 'bold';
div.innerText = r('contactFromRecord', 'Contacts from Customer Record');
}),
createElement('div', div => {
div.className = 'followers-record';
div.style.maxHeight = '400px';
div.style.width = '660px';
})
)
);
pop.show(container).then(() => {
const grid = new Grid();
grid.height = 0;
grid.allowHtml = true;
grid.headerVisible = false;
grid.columns = [
{
key: 'type',
type: Grid.ColumnTypes.Icon,
width: 50,
filter: c => c.SendText ? 'comment-lines' : 'envelope',
className: 'icon-contact-type',
iconType: 'fa-light'
},
{ key: 'Name', width: 160 },
{ key: 'Email', width: 180 },
{ key: 'MobilePhone', width: 130 },
{
key: 'delete',
type: Grid.ColumnTypes.Icon,
width: 40,
align: 'center',
iconType: 'fa-light',
text: 'times',
tooltip: r('delete', 'Delete'),
events: {
onclick: function () {
showConfirm(
r('deleteFollower', 'Delete Follower'),
r('promptDeleteFollower', 'Do you want to delete this follower?')).then(result => {
if (result?.key === 'yes') {
if (typeof option.onDeleteFollower === 'function') {
option.onDeleteFollower(result.key, this);
}
const index = grid.source.indexOf(this);
if (index >= 0) {
const source = grid.source;
source.splice(index, 1);
grid.extraRows = source.filter(c => !nullOrEmpty(c.Notes)).length;
grid.source = source;
}
}
});
}
}
}
];
grid.init(pop.container.querySelector('.followers-record'));
grid.source = this.#data.followers;
this.#gridFollower = grid;
});
});
})
buttonEditFollower
)
)
);
@ -718,6 +797,9 @@ class CustomerCommunication {
createElement('button', button => {
button.className = 'roundbtn button-send-message';
button.style.backgroundColor = 'rgb(19, 150, 204)';
if (readonly === true) {
button.style.display = 'none';
}
button.appendChild(createIcon('fa-solid', 'paper-plane'));
setTooltip(button, r('sendMessage', 'Send Message'));
button.addEventListener('click', () => {

View File

@ -22,7 +22,7 @@ class Follower {
if (nullOrEmpty(key)) {
this.#grid.source = this.#option.followers;
} else {
this.#grid.source = this.#option.followers.filter(f => contains(f.DisplayName, key, true));
this.#grid.source = this.#option.followers.filter(f => f.Text || f.Email || contains(f.DisplayName, key, true));
}
});
}),

View File

@ -22,12 +22,26 @@ class InternalComment {
}
}
/**
* @param {boolean} flag
*/
set readonly(flag) {
this.#option.readonly = flag;
if (this.#container == null) {
return;
}
this.#enter.disabled = flag === true;
this.#container.querySelector('.button-send-message').style.display = flag === true ? 'none' : '';
this.#container.querySelector('.button-post-note').style.display = flag === true ? 'none' : '';
}
create() {
const container = createBox(
createElement('div', null,
createElement('div', div => div.innerText = r('internalComments', 'Internal Comments'))
), []
);
const readonly = this.#option.readonly;
// enter box
const enter = createElement('textarea');
enter.placeholder = r('typeComment', 'Enter Comment Here');
@ -37,6 +51,9 @@ class InternalComment {
const s = String(nullOrEmpty(val) ? 0 : val.length) + '/3000';
this.#container.querySelector('.message-bar .prompt-count').innerText = s;
});
if (readonly === true) {
enter.disabled = true;
}
this.#enter = enter;
container.appendChild(
createElement('div', 'message-bar',
@ -46,6 +63,9 @@ class InternalComment {
createElement('button', button => {
button.className = 'roundbtn button-send-message';
button.style.backgroundColor = 'rgb(19, 150, 204)';
if (readonly === true) {
button.style.display = 'none';
}
button.appendChild(createIcon('fa-solid', 'paper-plane'));
setTooltip(button, r('sendMessage', 'Send Message'));
button.addEventListener('click', () => {
@ -58,6 +78,9 @@ class InternalComment {
button.className = 'roundbtn button-post-note';
button.style.border = '1px solid rgb(19, 150, 204)';
button.style.fill = 'rgb(19, 150, 204)';
if (readonly === true) {
button.style.display = 'none';
}
button.appendChild(createIcon('fa-solid', 'comment-alt-lines'));
setTooltip(button, r('postNote', 'Post Note'));
button.addEventListener('click', () => {
@ -89,7 +112,7 @@ class InternalComment {
div.innerText = comment.UserName;
}));
const content = createElement('div', 'item-content');
content.appendChild(createElement('span', span => span.innerText = escapeHtml(comment.Comment)));
content.appendChild(createElement('span', span => span.innerHTML = escapeHtml(comment.Comment)));
if (comment.FollowUp?.length > 0) {
div.classList.add('item-sent');
const sendto = r('sendToColon', 'Send To :') + '\r\n' + comment.FollowUp.split(';').join('\r\n');

View File

@ -176,9 +176,10 @@
}
>span {
flex: 1 1 auto;
// flex: 1 1 auto;
color: var(--strong-color);
font-size: var(--font-larger-size);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 10px;
@ -319,7 +320,6 @@
.grid {
height: 100%;
min-height: 120px;
overflow: hidden;
>.grid-body .grid-body-content>.grid-row>td {
vertical-align: top;

View File

@ -200,7 +200,7 @@ class GridCheckboxColumn extends GridColumn {
class GridIconColumn extends GridColumn {
static create() { return createElement('span', 'col-icon') }
static setValue(element, val, item, col) {
static setValue(element, val, item, col, grid) {
let className = col.className;
if (typeof className === 'function') {
className = className.call(col, item);
@ -219,7 +219,7 @@ class GridIconColumn extends GridColumn {
const icon = createIcon(type, val);
// const layer = element.children[0];
element.replaceChildren(icon);
!nullOrEmpty(col.tooltip) && setTooltip(element, col.tooltip);
!nullOrEmpty(col.tooltip) && setTooltip(element, col.tooltip, false, grid.element);
element.dataset.type = type;
element.dataset.icon = val;
}
@ -312,6 +312,8 @@ class Grid {
this.#parent = container;
}
get element() { return this.#el }
get source() { return this.#source?.map(s => s.values) }
set source(list) {
if (this.#el == null) {

2
lib/ui/tooltip.d.ts vendored
View File

@ -1,2 +1,2 @@
export function setTooltip(container: HTMLElement, content: string | HTMLElement, flag?: boolean): HTMLElement
export function setTooltip(container: HTMLElement, content: string | HTMLElement, flag?: boolean, parent?: HTMLElement): HTMLElement
export function resolveTooltip(container?: HTMLElement): HTMLElement

View File

@ -5,7 +5,7 @@
给某个元素或者页面上含有 title 属性的元素设置一个统一样式的 tooltip。
</p>
<h2>setTooltip</h2>
<code>function setTooltip(container: HTMLElement, content: string | HTMLElement, flag?: boolean): void</code>
<code>function setTooltip(container: HTMLElement, content: string | HTMLElement, flag?: boolean, parent?: HTMLElement): void</code>
<h3>container: HTMLElement</h3>
<p>
要设置 tooltip 的元素
@ -18,6 +18,10 @@
<p>
是否启用严格模式,只有显示不完整时才显示 tooltip
</p>
<h3>parent?: HTMLElement</h3>
<p>
创建在哪个元素内,默认创建在目标元素之内
</p>
<h2>resolveTooltip</h2>
<code>function resolveTooltip(container?: HTMLElement): HTMLElement</code>
<h3>container?: HTMLElement</h3>

View File

@ -1,14 +1,22 @@
import { createElement } from "../functions";
// import { global } from "../utility";
function setTooltip(container, content, flag = false) {
const tip = container.querySelector('.tooltip-wrapper');
if (tip != null) {
tip.remove();
function setTooltip(container, content, flag = false, parent = null) {
const isParent = parent instanceof HTMLElement;
if (isParent) {
const tipid = container.dataset.tipId;
const tip = parent.querySelector(`.tooltip-wrapper[data-tip-id="${tipid}"]`);
tip?.remove();
} else {
const tip = container.querySelector('.tooltip-wrapper');
tip?.remove();
}
const wrapper = createElement('div', wrapper => {
wrapper.className = 'tooltip-wrapper tooltip-color';
wrapper.style.visibility = 'hidden';
wrapper.style.opacity = 0;
wrapper.style.top = '0';
wrapper.style.left = '0';
},
createElement('div', 'tooltip-pointer tooltip-color'),
createElement('div', 'tooltip-curtain tooltip-color'),
@ -22,7 +30,14 @@ function setTooltip(container, content, flag = false) {
})
);
// container.insertAdjacentElement('afterend', wrapper);
container.appendChild(wrapper);
if (isParent) {
const tipId = String(Math.random()).substring(2);
container.dataset.tipId = tipId;
wrapper.dataset.tipId = tipId;
parent.appendChild(wrapper);
} else {
container.appendChild(wrapper);
}
let tid;
container.addEventListener('mouseenter', () => {
@ -36,19 +51,27 @@ function setTooltip(container, content, flag = false) {
}
if (!flag || c.scrollWidth > c.offsetWidth) {
tid = setTimeout(() => {
let parent = c;
let left = c.offsetLeft;
let top = c.offsetTop;
while ((parent = parent.offsetParent) != null) {
left += parent.offsetLeft;
top += parent.offsetTop;
let p;
let left;
let top;
left = c.offsetLeft;
top = c.offsetTop;
if (isParent) {
p = c.offsetParent;
while (p != null && p !== parent) {
left += p.offsetLeft;
top += p.offsetTop;
p = p.offsetParent;
}
}
parent = c;
while ((parent = parent.parentElement) != null) {
left -= parent.scrollLeft;
top -= parent.scrollTop;
p = c.parentElement;
const offsetParent = c.offsetParent;
while (p != null && p !== (isParent ? parent : offsetParent)) {
left -= p.scrollLeft;
top -= p.scrollTop;
p = p.parentElement;
}
left -= wrapper.offsetWidth / 2 - c.offsetWidth / 2;
left += (c.offsetWidth - wrapper.offsetWidth) / 2;
top -= wrapper.offsetHeight + 14;
wrapper.style.left = `${left}px`;
wrapper.style.top = `${top}px`;