You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

454 line
12 KiB

  1. import BaseTimeline from "../../../public/js/frappe/form/footer/base_timeline";
  2. frappe.provide('frappe.energy_points');
  3. class UserProfile {
  4. constructor(wrapper) {
  5. this.wrapper = $(wrapper);
  6. this.page = frappe.ui.make_app_page({
  7. parent: wrapper,
  8. });
  9. this.sidebar = this.wrapper.find('.layout-side-section');
  10. this.main_section = this.wrapper.find('.layout-main-section');
  11. this.wrapper.bind('show', () => {
  12. this.show();
  13. });
  14. }
  15. show() {
  16. let route = frappe.get_route();
  17. this.user_id = route[1] || frappe.session.user;
  18. //validate if user
  19. if (route.length > 1) {
  20. frappe.dom.freeze(__('Loading user profile') + '...');
  21. frappe.db.exists('User', this.user_id).then(exists => {
  22. frappe.dom.unfreeze();
  23. if (exists) {
  24. this.make_user_profile();
  25. } else {
  26. frappe.msgprint(__('User does not exist'));
  27. }
  28. });
  29. } else {
  30. frappe.set_route('user-profile', frappe.session.user);
  31. }
  32. }
  33. make_user_profile() {
  34. this.user = frappe.user_info(this.user_id);
  35. this.page.set_title(this.user.fullname);
  36. this.setup_user_search();
  37. this.main_section.empty().append(frappe.render_template('user_profile'));
  38. this.energy_points = 0;
  39. this.review_points = 0;
  40. this.rank = 0;
  41. this.month_rank = 0;
  42. this.render_user_details();
  43. this.render_points_and_rank();
  44. this.render_heatmap();
  45. this.render_line_chart();
  46. this.render_percentage_chart('type', 'Type Distribution');
  47. this.create_percentage_chart_filters();
  48. this.setup_user_activity_timeline();
  49. }
  50. setup_user_search() {
  51. this.$user_search_button = this.page.set_secondary_action(
  52. __('Change User'),
  53. () => this.show_user_search_dialog(),
  54. { icon: 'change', size: 'sm' }
  55. );
  56. }
  57. show_user_search_dialog() {
  58. let dialog = new frappe.ui.Dialog({
  59. title: __('Change User'),
  60. fields: [
  61. {
  62. fieldtype: 'Link',
  63. fieldname: 'user',
  64. options: 'User',
  65. label: __('User'),
  66. }
  67. ],
  68. primary_action_label: __('Go'),
  69. primary_action: ({ user }) => {
  70. dialog.hide();
  71. this.user_id = user;
  72. this.make_user_profile();
  73. }
  74. });
  75. dialog.show();
  76. }
  77. render_heatmap() {
  78. this.heatmap = new frappe.Chart('.performance-heatmap', {
  79. type: 'heatmap',
  80. countLabel: 'Energy Points',
  81. data: {},
  82. discreteDomains: 1,
  83. radius: 3,
  84. height: 150
  85. });
  86. this.update_heatmap_data();
  87. this.create_heatmap_chart_filters();
  88. }
  89. update_heatmap_data(date_from) {
  90. frappe.xcall('frappe.desk.page.user_profile.user_profile.get_energy_points_heatmap_data', {
  91. user: this.user_id,
  92. date: date_from || frappe.datetime.year_start(),
  93. }).then((r) => {
  94. this.heatmap.update({ dataPoints: r });
  95. });
  96. }
  97. render_line_chart() {
  98. this.line_chart_filters = [
  99. ['Energy Point Log', 'user', '=', this.user_id, false],
  100. ['Energy Point Log', 'type', '!=', 'Review', false]
  101. ];
  102. this.line_chart_config = {
  103. timespan: 'Last Month',
  104. time_interval: 'Daily',
  105. type: 'Line',
  106. value_based_on: 'points',
  107. chart_type: 'Sum',
  108. document_type: 'Energy Point Log',
  109. name: 'Energy Points',
  110. width: 'half',
  111. based_on: 'creation'
  112. };
  113. this.line_chart = new frappe.Chart('.performance-line-chart', {
  114. type: 'line',
  115. height: 200,
  116. data: {
  117. labels: [],
  118. datasets: [{}]
  119. },
  120. colors: ['purple'],
  121. axisOptions: {
  122. xIsSeries: 1
  123. }
  124. });
  125. this.update_line_chart_data();
  126. this.create_line_chart_filters();
  127. }
  128. update_line_chart_data() {
  129. this.line_chart_config.filters_json = JSON.stringify(this.line_chart_filters);
  130. frappe.xcall('frappe.desk.doctype.dashboard_chart.dashboard_chart.get', {
  131. chart: this.line_chart_config,
  132. no_cache: 1,
  133. }).then(chart => {
  134. this.line_chart.update(chart);
  135. });
  136. }
  137. // eslint-disable-next-line no-unused-vars
  138. render_percentage_chart(field, title) {
  139. frappe.xcall('frappe.desk.page.user_profile.user_profile.get_energy_points_percentage_chart_data', {
  140. user: this.user_id,
  141. field: field
  142. }).then(chart => {
  143. if (chart.labels.length) {
  144. this.percentage_chart = new frappe.Chart('.performance-percentage-chart', {
  145. type: 'percentage',
  146. data: {
  147. labels: chart.labels,
  148. datasets: chart.datasets
  149. },
  150. truncateLegends: 1,
  151. barOptions: {
  152. height: 11,
  153. depth: 1
  154. },
  155. height: 200,
  156. maxSlices: 8,
  157. colors: ['purple', 'blue', 'cyan', 'teal', 'pink', 'red', 'orange', 'yellow'],
  158. });
  159. } else {
  160. this.wrapper.find('.percentage-chart-container').hide();
  161. }
  162. });
  163. }
  164. create_line_chart_filters() {
  165. let filters = [
  166. {
  167. label: 'All',
  168. options: ['All', 'Auto', 'Criticism', 'Appreciation', 'Revert'],
  169. action: (selected_item) => {
  170. if (selected_item === 'All') {
  171. this.line_chart_filters = [
  172. ['Energy Point Log', 'user', '=', this.user_id, false],
  173. ['Energy Point Log', 'type', '!=', 'Review', false]
  174. ];
  175. } else {
  176. this.line_chart_filters[1] = ['Energy Point Log', 'type', '=', selected_item, false];
  177. }
  178. this.update_line_chart_data();
  179. }
  180. },
  181. {
  182. label: 'Last Month',
  183. options: ['Last Week', 'Last Month', 'Last Quarter', 'Last Year'],
  184. action: (selected_item) => {
  185. this.line_chart_config.timespan = selected_item;
  186. this.update_line_chart_data();
  187. }
  188. },
  189. {
  190. label: 'Daily',
  191. options: ['Daily', 'Weekly', 'Monthly'],
  192. action: (selected_item) => {
  193. this.line_chart_config.time_interval = selected_item;
  194. this.update_line_chart_data();
  195. }
  196. },
  197. ];
  198. frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', '.line-chart-options', 1);
  199. }
  200. create_percentage_chart_filters() {
  201. let filters = [
  202. {
  203. label: 'Type',
  204. options: ['Type', 'Reference Doctype', 'Rule'],
  205. fieldnames: ['type', 'reference_doctype', 'rule'],
  206. action: (selected_item, fieldname) => {
  207. let title = selected_item + ' Distribution';
  208. this.render_percentage_chart(fieldname, title);
  209. }
  210. },
  211. ];
  212. frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', '.percentage-chart-options');
  213. }
  214. create_heatmap_chart_filters() {
  215. let filters = [
  216. {
  217. label: frappe.dashboard_utils.get_year(frappe.datetime.now_date()),
  218. options: frappe.dashboard_utils.get_years_since_creation(frappe.boot.user.creation),
  219. action: (selected_item) => {
  220. this.update_heatmap_data(frappe.datetime.obj_to_str(selected_item));
  221. }
  222. },
  223. ];
  224. frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', '.heatmap-options');
  225. }
  226. edit_profile() {
  227. let edit_profile_dialog = new frappe.ui.Dialog({
  228. title: __('Edit Profile'),
  229. fields: [
  230. {
  231. fieldtype: 'Attach Image',
  232. fieldname: 'user_image',
  233. label: 'Profile Image',
  234. },
  235. {
  236. fieldtype: 'Data',
  237. fieldname: 'interest',
  238. label: 'Interests',
  239. },
  240. {
  241. fieldtype: 'Column Break'
  242. },
  243. {
  244. fieldtype: 'Data',
  245. fieldname: 'location',
  246. label: 'Location',
  247. },
  248. {
  249. fieldtype: 'Section Break',
  250. fieldname: 'Interest',
  251. },
  252. {
  253. fieldtype: 'Small Text',
  254. fieldname: 'bio',
  255. label: 'Bio',
  256. }
  257. ],
  258. primary_action: values => {
  259. edit_profile_dialog.disable_primary_action();
  260. frappe.xcall('frappe.desk.page.user_profile.user_profile.update_profile_info', {
  261. profile_info: values
  262. }).then(user => {
  263. user.image = user.user_image;
  264. this.user = Object.assign(values, user);
  265. edit_profile_dialog.hide();
  266. this.render_user_details();
  267. }).finally(() => {
  268. edit_profile_dialog.enable_primary_action();
  269. });
  270. },
  271. primary_action_label: __('Save')
  272. });
  273. edit_profile_dialog.set_values({
  274. user_image: this.user.image,
  275. location: this.user.location,
  276. interest: this.user.interest,
  277. bio: this.user.bio
  278. });
  279. edit_profile_dialog.show();
  280. }
  281. render_user_details() {
  282. this.sidebar.empty().append(frappe.render_template('user_profile_sidebar', {
  283. user_image: this.user.image,
  284. user_abbr: this.user.abbr,
  285. user_location: this.user.location,
  286. user_interest: this.user.interest,
  287. user_bio: this.user.bio,
  288. }));
  289. this.setup_user_profile_links();
  290. }
  291. setup_user_profile_links() {
  292. if (this.user_id !== frappe.session.user) {
  293. this.wrapper.find('.profile-links').hide();
  294. } else {
  295. this.wrapper.find('.edit-profile-link').on('click', () => {
  296. this.edit_profile();
  297. });
  298. this.wrapper.find('.user-settings-link').on('click', () => {
  299. this.go_to_user_settings();
  300. });
  301. }
  302. }
  303. get_user_rank() {
  304. return frappe.xcall('frappe.desk.page.user_profile.user_profile.get_user_rank', {
  305. user: this.user_id,
  306. }).then(r => {
  307. if (r.monthly_rank.length) this.month_rank = r.monthly_rank[0];
  308. if (r.all_time_rank.length) this.rank = r.all_time_rank[0];
  309. });
  310. }
  311. get_user_points() {
  312. return frappe.xcall(
  313. 'frappe.social.doctype.energy_point_log.energy_point_log.get_user_energy_and_review_points',
  314. {
  315. user: this.user_id,
  316. }
  317. ).then(r => {
  318. if (r[this.user_id]) {
  319. this.energy_points = r[this.user_id].energy_points;
  320. this.review_points = r[this.user_id].review_points;
  321. }
  322. });
  323. }
  324. render_points_and_rank() {
  325. let $profile_details = this.wrapper.find('.user-stats');
  326. let $profile_details_wrapper = this.wrapper.find('.user-stats-detail');
  327. const _get_stat_dom = (value, label, icon) => {
  328. return `<div class="user-stats-item mt-4">
  329. ${frappe.utils.icon(icon, "lg", "no-stroke")}
  330. <div>
  331. <div class="stat-value">${value}</div>
  332. <div class="stat-label">${label}</div>
  333. </div>
  334. </div>`;
  335. };
  336. this.get_user_rank().then(() => {
  337. this.get_user_points().then(() => {
  338. let html = $(`
  339. ${_get_stat_dom(this.energy_points, __('Energy Points'), "color-energy-points")}
  340. ${_get_stat_dom(this.review_points, __('Review Points'), "color-review-points")}
  341. ${_get_stat_dom(this.rank, __('Rank'), "color-rank")}
  342. ${_get_stat_dom(this.month_rank, __('Monthly Rank'), "color-monthly-rank")}
  343. `);
  344. $profile_details.append(html);
  345. $profile_details_wrapper.removeClass("hide");
  346. });
  347. });
  348. }
  349. go_to_user_settings() {
  350. frappe.set_route('Form', 'User', this.user_id);
  351. }
  352. setup_user_activity_timeline() {
  353. this.user_activity_timeline = new UserProfileTimeline({
  354. parent: this.wrapper.find('.recent-activity-list'),
  355. footer: this.wrapper.find('.recent-activity-footer'),
  356. user: this.user_id
  357. });
  358. this.user_activity_timeline.refresh();
  359. }
  360. }
  361. class UserProfileTimeline extends BaseTimeline {
  362. make() {
  363. super.make();
  364. this.activity_start = 0;
  365. this.activity_limit = 20;
  366. this.setup_show_more_activity();
  367. }
  368. prepare_timeline_contents() {
  369. return this.get_user_activity_data().then((activities) => {
  370. if (!activities.length) {
  371. this.show_more_button.hide();
  372. this.timeline_wrapper.html(`<div>${__('No activities to show')}</div>`);
  373. return;
  374. }
  375. this.show_more_button.toggle(activities.length === this.activity_limit);
  376. this.timeline_items = activities.map((activity) => this.get_activity_timeline_item(activity));
  377. });
  378. }
  379. get_user_activity_data() {
  380. return frappe.xcall('frappe.desk.page.user_profile.user_profile.get_energy_points_list', {
  381. start: this.activity_start,
  382. limit: this.activity_limit,
  383. user: this.user
  384. });
  385. }
  386. get_activity_timeline_item(data) {
  387. let icon = data.type == 'Appreciation' ? 'clap': data.type == 'Criticism' ? 'criticize': null;
  388. return {
  389. icon: icon,
  390. creation: data.creation,
  391. is_card: true,
  392. content: frappe.energy_points.format_history_log(data),
  393. };
  394. }
  395. setup_show_more_activity() {
  396. this.show_more_button = $(`<a class="show-more-activity-btn">${__('Show More Activity')}</a>`);
  397. this.show_more_button.hide();
  398. this.footer.append(this.show_more_button);
  399. this.show_more_button.on('click', () => this.show_more_activity());
  400. }
  401. show_more_activity() {
  402. this.activity_start += this.activity_limit;
  403. this.get_user_activity_data().then(activities => {
  404. if (!activities.length || activities.length < this.activity_limit) {
  405. this.show_more_button.hide();
  406. }
  407. let timeline_items = activities.map((activity) => this.get_activity_timeline_item(activity));
  408. timeline_items.map((item) => this.add_timeline_item(item, true));
  409. });
  410. }
  411. }
  412. frappe.provide('frappe.ui');
  413. frappe.ui.UserProfile = UserProfile;