fat_free_crm 0.24.3 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of fat_free_crm might be problematic. Click here for more details.

Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +345 -7
  3. data/README.md +22 -19
  4. data/app/assets/stylesheets/advanced_search.css.scss +1 -1
  5. data/app/assets/stylesheets/base.scss +11 -7
  6. data/app/assets/stylesheets/common.scss +20 -17
  7. data/app/assets/stylesheets/header.scss +2 -2
  8. data/app/assets/stylesheets/rails.scss +2 -2
  9. data/app/controllers/comments_controller.rb +1 -1
  10. data/app/controllers/entities/contacts_controller.rb +3 -1
  11. data/app/controllers/entities/leads_controller.rb +3 -1
  12. data/app/controllers/home_controller.rb +1 -1
  13. data/app/controllers/users_controller.rb +1 -1
  14. data/app/helpers/application_helper.rb +42 -4
  15. data/app/helpers/contacts_helper.rb +28 -0
  16. data/app/mailers/subscription_mailer.rb +1 -1
  17. data/app/models/entities/account.rb +2 -0
  18. data/app/models/observers/opportunity_observer.rb +1 -1
  19. data/app/views/accounts/_contact_info.html.haml +14 -6
  20. data/app/views/accounts/_index_long.html.haml +1 -1
  21. data/app/views/accounts/_sidebar_show.html.haml +23 -5
  22. data/app/views/admin/users/_user.html.haml +6 -4
  23. data/app/views/comments/_comment.html.haml +3 -3
  24. data/app/views/contacts/_extra.html.haml +3 -3
  25. data/app/views/contacts/_index_full.html.haml +2 -2
  26. data/app/views/contacts/_index_long.html.haml +2 -2
  27. data/app/views/contacts/_section_general.html.haml +1 -1
  28. data/app/views/contacts/_sidebar_show.html.haml +3 -3
  29. data/app/views/contacts/_top_section.html.haml +2 -2
  30. data/app/views/contacts/_web.html.haml +1 -1
  31. data/app/views/home/_account.html.haml +1 -1
  32. data/app/views/layouts/application.html.haml +4 -0
  33. data/app/views/leads/_contact.html.haml +3 -3
  34. data/app/views/leads/_index_long.html.haml +2 -2
  35. data/app/views/leads/_opportunity.html.haml +2 -2
  36. data/app/views/leads/_sidebar_show.html.haml +2 -2
  37. data/app/views/leads/_status.html.haml +1 -1
  38. data/app/views/leads/_top_section.html.haml +1 -1
  39. data/app/views/opportunities/_top_section.html.haml +2 -2
  40. data/app/views/tasks/create.js.haml +1 -1
  41. data/app/views/users/_profile.html.haml +2 -2
  42. data/app/views/users/_user.html.haml +6 -5
  43. data/config/initializers/simple_form.rb +1 -1
  44. data/config/locales/fat_free_crm.cs.yml +3 -3
  45. data/config/locales/fat_free_crm.de.yml +3 -3
  46. data/config/locales/fat_free_crm.en-GB.yml +3 -3
  47. data/config/locales/fat_free_crm.en-US.yml +3 -3
  48. data/config/locales/fat_free_crm.es-CL.yml +3 -3
  49. data/config/locales/fat_free_crm.es.yml +3 -3
  50. data/config/locales/fat_free_crm.et.yml +3 -3
  51. data/config/locales/fat_free_crm.fr-CA.yml +3 -3
  52. data/config/locales/fat_free_crm.fr.yml +3 -3
  53. data/config/locales/fat_free_crm.it.yml +3 -3
  54. data/config/locales/fat_free_crm.ja.yml +3 -3
  55. data/config/locales/fat_free_crm.nl.yml +3 -3
  56. data/config/locales/fat_free_crm.pl.yml +3 -3
  57. data/config/locales/fat_free_crm.pt-BR.yml +3 -3
  58. data/config/locales/fat_free_crm.ru.yml +3 -3
  59. data/config/locales/fat_free_crm.sv-SE.yml +3 -3
  60. data/config/locales/fat_free_crm.th.yml +3 -3
  61. data/config/locales/fat_free_crm.zh-CN.yml +3 -3
  62. data/config/settings.default.yml +10 -2
  63. data/db/demo/accounts.yml +2 -0
  64. data/db/fat_free_crm_development.sqlite3 +0 -0
  65. data/db/{fat_free_crm_test.sqlite3-shm → fat_free_crm_development.sqlite3-shm} +0 -0
  66. data/db/fat_free_crm_development.sqlite3-wal +0 -0
  67. data/db/fat_free_crm_test.sqlite3 +0 -0
  68. data/db/migrate/20100928030623_create_addresses.rb +1 -1
  69. data/db/migrate/20250805093408_add_latitude_and_longitude_to_accounts.rb +8 -0
  70. data/db/schema.rb +12 -1
  71. data/lib/fat_free_crm/fields.rb +3 -0
  72. data/lib/fat_free_crm/mail_processor/dropbox.rb +1 -1
  73. data/lib/fat_free_crm/version.rb +2 -2
  74. metadata +10 -9
  75. data/db/fat_free_crm_test.sqlite3-wal +0 -0
@@ -87,11 +87,11 @@ $sidebar_width: 210px;
87
87
  -moz-border-radius: 6px;
88
88
  -webkit-border-radius: 6px;
89
89
  input[type="text"], input[type="email"], input[type="password"] {
90
- font-size: 16px;
90
+ font-size: 1.2em;
91
91
  padding: 2px;
92
92
  width: 355px; }
93
93
  input[type="submit"] {
94
- font-size: 14px; } }
94
+ font-size: 1.1em; } }
95
95
 
96
96
  .tabbed {
97
97
  padding: 12px 12px 12px 16px; }
@@ -304,7 +304,6 @@ $bg_color9: #FFDD4A;
304
304
  .amount {
305
305
  background: palegreen;
306
306
  color: black;
307
- font-size: 11px;
308
307
  text-align: center;
309
308
  padding: 0px 3px 0px 3px;
310
309
  -moz-border-radius: {
@@ -322,7 +321,7 @@ $bg_color9: #FFDD4A;
322
321
  background: gainsboro;
323
322
  color: black;
324
323
  float: left;
325
- font-size: 11px;
324
+ font-size: 0.85em;
326
325
  margin: 0px 6px 0px 0px;
327
326
  padding: 1px 4px 1px 3px;
328
327
  text-align: right;
@@ -351,8 +350,7 @@ $bg_color9: #FFDD4A;
351
350
  margin: 0px 0px 10px 0px; }
352
351
 
353
352
  .prefix {
354
- width: 30px;
355
- font-size: 10px; }
353
+ width: 30px; }
356
354
 
357
355
  .comment {
358
356
  background: #f2f2f2;
@@ -372,7 +370,7 @@ $bg_color9: #FFDD4A;
372
370
  input[type=text] {
373
371
  border: 1px #cfcfcf solid;
374
372
  color: grey;
375
- font-size: 11px;
373
+ font-size: 0.9em;
376
374
  margin: 3px 0px 0px 0px;
377
375
  padding: 3px;
378
376
  width: 500px; }
@@ -381,9 +379,8 @@ $bg_color9: #FFDD4A;
381
379
  margin: 3px 10px 3px 3px; }
382
380
  .body {
383
381
  display: inline;
384
- font-size: 11px; }
382
+ font-size: 0.9em; }
385
383
  tt {
386
- font-size: 10px;
387
384
  color: #666666; }
388
385
  dt {
389
386
  padding: 0px;
@@ -394,10 +391,10 @@ $bg_color9: #FFDD4A;
394
391
  margin-top: 7px;
395
392
  margin-left: 4px;
396
393
  a {
397
- font-size: 11px; } } }
394
+ font-size: 0.9em; } } }
398
395
 
399
396
  .comment textarea, textarea#comment_body {
400
- font-size: 11px;
397
+ font-size: 0.9em;
401
398
  height: 110px;
402
399
  margin: 3px 0px 0px 0px;
403
400
  padding: 3px;
@@ -418,9 +415,8 @@ $bg_color9: #FFDD4A;
418
415
  color: black; }
419
416
  .body {
420
417
  display: inline;
421
- font-size: 11px; }
418
+ font-size: 0.9em; }
422
419
  tt {
423
- font-size: 10px;
424
420
  color: #666666; }
425
421
  dt {
426
422
  padding: 0px;
@@ -432,12 +428,12 @@ $bg_color9: #FFDD4A;
432
428
  font-weight: normal; }
433
429
 
434
430
  #export {
435
- font-size: 9px;
431
+ font-size: 0.8em;
436
432
  margin: 12px 0px 0px 0px; }
437
433
 
438
434
  #paginate {
439
435
  float: right;
440
- font-size: 10px;
436
+ font-size: 0.9em;
441
437
  margin: 4px 0px 0px 0px; }
442
438
 
443
439
  #paging {
@@ -447,7 +443,7 @@ $bg_color9: #FFDD4A;
447
443
  .confirm {
448
444
  background: #cdfecd;
449
445
  border-left: 10px palegreen solid;
450
- font-size: 11px;
446
+ font-size: 0.9em;
451
447
  margin: 0px 0px 6px 0px;
452
448
  padding: 6px; }
453
449
 
@@ -544,7 +540,7 @@ $bg_color9: #FFDD4A;
544
540
  margin: 4px 0px 4px 0px; } } }
545
541
 
546
542
  .arrow {
547
- font-size: 9px;
543
+ font-size: 0.8em;
548
544
  padding: 0px 2px 3px 0px; }
549
545
 
550
546
  .left {
@@ -785,6 +781,13 @@ table.asset_attributes {
785
781
  }
786
782
  }
787
783
 
784
+ .web-presence-icons a {
785
+ font-size: larger;
786
+ color: black;
787
+ :hover {
788
+ color: black;
789
+ }
790
+ }
788
791
 
789
792
  // Login page
790
793
  .form-group.user_remember_me {
@@ -29,7 +29,7 @@ $color_footer: grey;
29
29
  text-decoration: none; }
30
30
  #welcome {
31
31
  float: right;
32
- font-size: 11px;
32
+ font-size: 0.9em;
33
33
  a {
34
34
  color: lightyellow;
35
35
  padding: 1px 4px 2px 4px;
@@ -108,6 +108,6 @@ $color_footer: grey;
108
108
 
109
109
  #footer {
110
110
  color: $color_footer;
111
- font-size: 10px;
111
+ font-size: 0.8em;
112
112
  padding: 0px 0px 0px 16px;
113
113
  text-align: center; }
@@ -50,7 +50,7 @@ p, div {
50
50
  &.flash_exception {
51
51
  background: #ddff99;
52
52
  border: 5px limegreen solid;
53
- font-size: 14px;
53
+ font-size: 1.1em;
54
54
  margin: 50px auto;
55
55
  padding: 50px;
56
56
  width: 450px;
@@ -94,7 +94,7 @@ div {
94
94
  .pagination, .per_page_options {
95
95
  background: white;
96
96
  float: right;
97
- font-size: 11px;
97
+ font-size: 0.9em;
98
98
  margin: 6px 0px 0px 0px;
99
99
  a, span, em {
100
100
  padding: 0.2em 0.5em;
@@ -59,7 +59,7 @@ class CommentsController < ApplicationController
59
59
 
60
60
  # PUT /comments/1
61
61
  # PUT /comments/1.json
62
- # PUT /comments/1.xml not implemened
62
+ # PUT /comments/1.xml not implemented
63
63
  #----------------------------------------------------------------------------
64
64
  def update
65
65
  @comment = Comment.find(params[:id])
@@ -26,7 +26,9 @@ class ContactsController < EntitiesController
26
26
  @stage = Setting.unroll(:opportunity_stage)
27
27
  @comment = Comment.new
28
28
  @timeline = timeline(@contact)
29
- respond_with(@contact)
29
+ respond_with(@contact) do |format|
30
+ format.vcf { send_data helpers.vcard_for(@contact).to_s, filename: "#{@contact.full_name}.vcf", disposition: 'attachment', type: 'text/x-vcard' }
31
+ end
30
32
  end
31
33
 
32
34
  # GET /contacts/new
@@ -26,7 +26,9 @@ class LeadsController < EntitiesController
26
26
  def show
27
27
  @comment = Comment.new
28
28
  @timeline = timeline(@lead)
29
- respond_with(@lead)
29
+ respond_with(@lead) do |format|
30
+ format.vcf { send_data helpers.vcard_for(@lead).to_s, filename: "#{@lead.full_name}.vcf", disposition: 'attachment', type: 'text/x-vcard' }
31
+ end
30
32
  end
31
33
 
32
34
  # GET /leads/new
@@ -130,7 +130,7 @@ class HomeController < ApplicationController
130
130
 
131
131
  #----------------------------------------------------------------------------
132
132
  # TODO: this is ugly, ugly code. It's being security patched now but urgently
133
- # needs refactoring to use user id instead. Permuations based on name or email
133
+ # needs refactoring to use user id instead. Permutations based on name or email
134
134
  # yield incorrect results.
135
135
  def activity_user
136
136
  return nil if current_user.pref[:activity_user] == "all_users"
@@ -6,7 +6,7 @@
6
6
  # See MIT-LICENSE file or http://www.opensource.org/licenses/mit-license.php
7
7
  #------------------------------------------------------------------------------
8
8
  class UsersController < ApplicationController
9
- before_action :set_current_tab, only: %i[show opportunities_overview] # Don't hightlight any tabs.
9
+ before_action :set_current_tab, only: %i[show opportunities_overview] # Don't highlight any tabs.
10
10
 
11
11
  check_authorization
12
12
 
@@ -170,6 +170,25 @@ module ApplicationHelper
170
170
  end
171
171
  end
172
172
 
173
+ # Format a phone number as a tel: hyperlink.
174
+ #----------------------------------------------------------------------------
175
+ def link_to_phone(number)
176
+ return nil if number.blank?
177
+
178
+ sanitized_number = number.gsub(/[^0-9+]/, '')
179
+ link_to number, "tel:#{sanitized_number}"
180
+ end
181
+
182
+ # Render a phone field with an optional pattern for international format.
183
+ #----------------------------------------------------------------------------
184
+ def phone_field_with_pattern(form, method, options = {})
185
+ if Setting.enforce_international_phone_format
186
+ options[:pattern] ||= '\+[0-9]{1,3}\s?[0-9]{1,14}'
187
+ options[:placeholder] ||= '+1 123 456 7890'
188
+ end
189
+ form.phone_field(method, options)
190
+ end
191
+
173
192
  #----------------------------------------------------------------------------
174
193
  def jumpbox(current)
175
194
  tabs = %i[campaigns accounts leads contacts opportunities]
@@ -248,7 +267,15 @@ module ApplicationHelper
248
267
  # Display web presence mini-icons for Contact or Lead.
249
268
  #----------------------------------------------------------------------------
250
269
  def web_presence_icons(person)
251
- %i[blog linkedin facebook twitter skype].map do |site|
270
+ sites = []
271
+ icon_for_site = {
272
+ skype: "skype",
273
+ facebook: "facebook",
274
+ linkedin: "linkedin",
275
+ twitter: "twitter",
276
+ blog: "external-link"
277
+ }
278
+ %i[blog linkedin facebook twitter skype].each do |site|
252
279
  url = person.send(site)
253
280
  next if url.blank?
254
281
 
@@ -257,8 +284,19 @@ module ApplicationHelper
257
284
  else
258
285
  url = "http://" + url unless url.match?(%r{^https?://})
259
286
  end
260
- link_to(image_tag("#{site}.gif", size: "15x15"), h(url), "data-popup": true, title: t(:open_in_window, h(url)))
261
- end.compact.join("\n").html_safe
287
+ sites << if icon_for_site[site]
288
+ link_to(content_tag(:i, "", { class: "fa fa-#{icon_for_site[site]}" }), h(url), "data-popup": true, title: t(:open_in_window, h(url)))
289
+ else
290
+ link_to(image_tag("#{site}.gif", size: "15x15"), h(url), "data-popup": true, title: t(:open_in_window, h(url)))
291
+ end
292
+ end
293
+
294
+ if person.is_a?(Contact)
295
+ sites << link_to(content_tag(:i, "", { class: "fa fa-address-card" }), contact_path(person, format: :vcf), title: "VCard")
296
+ elsif person.is_a?(Lead)
297
+ sites << link_to(content_tag(:i, "", { class: "fa fa-address-card" }), lead_path(person, format: :vcf), title: "VCard")
298
+ end
299
+ content_tag(:span, class: "web-presence-icons") { safe_join(sites, "\n") }
262
300
  end
263
301
 
264
302
  # Ajax helper to refresh current index page once the user selects an option.
@@ -319,7 +357,7 @@ module ApplicationHelper
319
357
 
320
358
  # Entities can have associated avatars or gravatars. Only calls Gravatar
321
359
  # in production env. Gravatar won't serve default images if they are not
322
- # publically available: https://en.gravatar.com/site/implement/images
360
+ # public: https://en.gravatar.com/site/implement/images
323
361
  #----------------------------------------------------------------------------
324
362
  def avatar_for(model, args = {})
325
363
  args = { class: 'gravatar', size: :large }.merge(args)
@@ -18,4 +18,32 @@ module ContactsHelper
18
18
  summary << "#{t(:mobile_small)}: #{contact.mobile}" if contact.mobile.present?
19
19
  summary.join(', ')
20
20
  end
21
+
22
+ def vcard_for(contact)
23
+ card = VCardigan.create
24
+ card.name contact.last_name, contact.first_name
25
+ card.fullname "#{contact.first_name} #{contact.last_name}"
26
+ card.title contact.title if contact.title.present?
27
+ if contact.respond_to?(:account) # Contact
28
+ card.org contact.account.name, contact.department if contact.account.present?
29
+ elsif contact.respond_to?(:company) # Lead
30
+ card.org contact.company if contact.company.present?
31
+ end
32
+ card.email contact.email, type: %w[internet work] if contact.email.present?
33
+ card.email contact.alt_email, type: %w[internet work] if contact.alt_email.present?
34
+ card.tel contact.phone, type: 'work' if contact.phone?
35
+ card.tel contact.mobile, type: %w[cell voice] if contact.mobile.present?
36
+ card.note "Exported from Fat Free CRM"
37
+
38
+ if contact.business_address
39
+ card.adr contact.business_address.street1,
40
+ contact.business_address.street2,
41
+ contact.business_address.city,
42
+ contact.business_address.state,
43
+ contact.business_address.zipcode,
44
+ contact.business_address.country, type: 'work'
45
+ end
46
+
47
+ card
48
+ end
21
49
  end
@@ -14,7 +14,7 @@ class SubscriptionMailer < ActionMailer::Base
14
14
  @comment = comment
15
15
  @user = comment.user
16
16
 
17
- # If entity has tags, join them and wrap in parantheses
17
+ # If entity has tags, join them and wrap in parentheses
18
18
  subject = "RE: [#{@entity_type.downcase}:#{@entity.id}] #{@entity_name}"
19
19
  subject += " (#{@entity.tags.join(', ')})" if @entity.tags.any?
20
20
 
@@ -77,6 +77,8 @@ class Account < ActiveRecord::Base
77
77
  validates_uniqueness_of :name, scope: :deleted_at, if: -> { Setting.require_unique_account_names }
78
78
  validates :rating, inclusion: { in: 0..5 }, allow_blank: true
79
79
  validates :category, inclusion: { in: proc { Setting.unroll(:account_category).map { |s| s.last.to_s } } }, allow_blank: true
80
+ validates :latitude, numericality: { greater_than_or_equal_to: -90, less_than_or_equal_to: 90, allow_blank: true }
81
+ validates :longitude, numericality: { greater_than_or_equal_to: -180, less_than_or_equal_to: 180, allow_blank: true }
80
82
  validate :users_for_shared_access
81
83
 
82
84
  before_save :nullify_blank_category
@@ -25,7 +25,7 @@ class OpportunityObserver < ActiveRecord::Observer
25
25
  update_campaign_revenue(item.campaign, item.amount.to_f - item.discount.to_f)
26
26
  item.update_attribute(:probability, 100) # Set probability to 100% if won
27
27
  log_activity(item, :won)
28
- elsif original.stage == "won" && item.stage != "won" # :won to :other -- substract from total campaign revenue.
28
+ elsif original.stage == "won" && item.stage != "won" # :won to :other -- subtract from total campaign revenue.
29
29
  update_campaign_revenue(original.campaign, -(original.amount.to_f - original.discount.to_f))
30
30
  elsif original.stage != "lost" && item.stage == "lost"
31
31
  item.update_attribute(:probability, 0) # Set probability to 0% if lost
@@ -1,5 +1,5 @@
1
1
  - edit ||= false
2
- - collapsed = session[:account_contact].nil?
2
+ - collapsed = false
3
3
  = subtitle :account_contact, collapsed, t(:contact_info)
4
4
  .section
5
5
  %small#account_contact_intro{ hidden_if(!collapsed) }
@@ -12,23 +12,31 @@
12
12
  %tr
13
13
  %td
14
14
  .label #{t :phone_toll_free}:
15
- = f.text_field :toll_free_phone, style: "width:154px"
15
+ = phone_field_with_pattern(f, :toll_free_phone, style: "width:154px")
16
16
  %td= spacer
17
17
  %td
18
18
  .label #{t :phone}:
19
- = f.text_field :phone, style: "width:154px"
19
+ = phone_field_with_pattern(f, :phone, style: "width:154px")
20
20
  %td= spacer
21
21
  %td
22
22
  .label #{t :fax}:
23
- = f.text_field :fax, style: "width:154px"
23
+ = phone_field_with_pattern(f, :fax, style: "width:154px")
24
24
  %tr
25
25
  %td
26
26
  .label.top #{t :website}:
27
- = f.text_field :website
27
+ = f.url_field :website
28
28
  %td= spacer
29
29
  %td
30
30
  .label.top Email:
31
- = f.text_field :email
31
+ = f.email_field :email
32
+ %tr
33
+ %td
34
+ .label.top Latitude:
35
+ = f.text_field :latitude, pattern: "^-?\\d+(\\.\\d+)?$"
36
+ %td= spacer
37
+ %td
38
+ .label.top Longitude:
39
+ = f.text_field :longitude, pattern: "^-?\\d+(\\.\\d+)?$"
32
40
  %tr
33
41
  %td
34
42
  = render "shared/address", f: f, asset: @account, type: :billing, title: :billing_address
@@ -33,7 +33,7 @@
33
33
  = stars_for(account)
34
34
  = " | ".html_safe << link_to(account.website, account.website.to_url) if account.website.present?
35
35
  = " | ".html_safe << link_to_email(account.email) if account.email.present?
36
- = " | ".html_safe << t(:phone_small) << ": " << (account.toll_free_phone? ? account.toll_free_phone : account.phone) if account.toll_free_phone? || account.phone?
36
+ = " | ".html_safe << t(:phone_small) << ": " << (account.toll_free_phone? ? link_to_phone(account.toll_free_phone) : link_to_phone(account.phone)) if account.toll_free_phone? || account.phone?
37
37
 
38
38
  - if account.tags.present?
39
39
  %dt
@@ -12,16 +12,26 @@
12
12
 
13
13
  %div
14
14
  - if @account.toll_free_phone
15
- #{t :phone_toll_free}: <b>#{@account.toll_free_phone}</b><br />
15
+ == #{t :phone_toll_free}: <b>#{link_to_phone(@account.toll_free_phone)}</b><br />
16
16
 
17
17
  - if @account.phone
18
- #{t :phone}: <b>#{@account.phone}</b><br />
18
+ == #{t :phone}: <b>#{link_to_phone(@account.phone)}</b><br />
19
19
 
20
20
  - if @account.fax
21
- #{t :fax}: <b>#{@account.fax}</b><br />
21
+ == #{t :fax}: <b>#{link_to_phone(@account.fax)}</b><br />
22
22
 
23
- %div= render "shared/address_show", asset: @account, type: 'billing', title: :billing_address
24
- %div= render "shared/address_show", asset: @account, type: 'shipping', title: :shipping_address
23
+ %div= render "shared/address_show", asset: @account, type: 'billing', title: :billing_address
24
+ %div= render "shared/address_show", asset: @account, type: 'shipping', title: :shipping_address
25
+
26
+
27
+ - if @account.latitude.present? && @account.longitude.present?
28
+ #map{style: "width: 100%; height: 150px;"}
29
+
30
+ %p.text-end
31
+ %small
32
+ %b= link_to "Google Maps", "https://www.google.com/maps/search/?api=1&query=#{@account.latitude},#{@account.longitude}", target: "_blank"
33
+ |
34
+ %b= link_to "Other", "geo:#{@account.latitude},#{@account.longitude}"
25
35
 
26
36
  .caption #{t :account_summary}
27
37
  %dl
@@ -55,3 +65,11 @@
55
65
  .tags= tags_for_index(@account)
56
66
 
57
67
  = hook(:show_account_sidebar_bottom, self, account: @account)
68
+
69
+ - if @account.latitude.present? && @account.longitude.present?
70
+ :javascript
71
+ var map = L.map('map').setView([#{@account.latitude}, #{@account.longitude}], 18);
72
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
73
+ attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
74
+ }).addTo(map);
75
+ L.marker([#{@account.latitude}, #{@account.longitude}]).addTo(map);
@@ -47,11 +47,13 @@
47
47
  %dt{ style: "padding: 2px 0px 0px 0px" }
48
48
  = link_to_email(user.email.to_s) + " | "
49
49
  - if user.phone?
50
- = t(:phone_small) + ":"
51
- = content_tag(:b, user.phone) + " | "
50
+ = (t(:phone_small) + ": ").html_safe
51
+ %b= link_to_phone(user.phone)
52
+ = " | ".html_safe
52
53
  - if user.mobile?
53
- = t(:mobile_small) + ":"
54
- = content_tag(:b, user.mobile) + " | "
54
+ = (t(:mobile_small) + ": ").html_safe
55
+ %b= link_to_phone(user.mobile)
56
+ = " | ".html_safe
55
57
 
56
58
  - if !user.suspended?
57
59
  %span #{t(:user_since, l(user.created_at.to_date, format: :mmddyy))}
@@ -1,6 +1,6 @@
1
1
  - truncated = simple_format(truncate(comment.comment.gsub("\n", " "), length: 125))
2
2
  - formatted = defined?(RedCloth) ? RedCloth.new(comment.comment).to_html : simple_format(comment.comment)
3
- - collapsable = formatted.size > 150
3
+ - collapsible = formatted.size > 150
4
4
  - commentable = comment.commentable
5
5
 
6
6
  %li.comment.highlight[comment]
@@ -15,12 +15,12 @@
15
15
  = link_to(comment.user.full_name, user_path(comment.user)) + ","
16
16
  %tt
17
17
  = t(:added_note, value: timeago(comment.created_at) ).html_safe
18
- - if collapsable && can?(:read, commentable)
18
+ - if collapsible && can?(:read, commentable)
19
19
  = " | "
20
20
  = link_to_function(comment.collapsed? ? t(:more) : t(:less), "crm.flip_note_or_email(this, '#{t(:more)}', '#{t(:less)}')", class: "toggle")
21
21
 
22
22
  - if can?(:read, commentable)
23
- - if collapsable
23
+ - if collapsible
24
24
  %dt{ hidden_if(comment.expanded?), id: dom_id(comment, :truncated) }
25
25
  = truncated
26
26
  %dt.textile{ hidden_if(comment.collapsed?), id: dom_id(comment, :formatted) }
@@ -18,18 +18,18 @@
18
18
  %tr
19
19
  %td
20
20
  .label #{t :alt_email}:
21
- = f.text_field :alt_email
21
+ = f.email_field :alt_email
22
22
  %td= spacer
23
23
  %td
24
24
  .label #{t :mobile}:
25
- = f.text_field :mobile
25
+ = phone_field_with_pattern(f, :mobile)
26
26
  %tr
27
27
  %td
28
28
  = render "shared/address", f: f, asset: @contact, type: 'business', title: :address
29
29
  %td= spacer
30
30
  %td
31
31
  .label #{t :fax}:
32
- = f.text_field :fax
32
+ = phone_field_with_pattern(f, :fax)
33
33
  %div{style: "margin-top:6px"}
34
34
  .check_box
35
35
  = f.check_box :do_not_call, {}, true
@@ -35,11 +35,11 @@
35
35
  |
36
36
  - if contact.phone.present?
37
37
  == #{t :phone_small}:
38
- %b= contact.phone
38
+ %b= link_to_phone(contact.phone)
39
39
  |
40
40
  - if contact.mobile.present?
41
41
  == #{t :mobile_small}:
42
- %b= contact.mobile
42
+ %b= link_to_phone(contact.mobile)
43
43
  |
44
44
  = t(:added_ago, value: timeago(contact.created_at)).html_safe
45
45
  - if contact.tags.present?
@@ -29,11 +29,11 @@
29
29
  |
30
30
  - if contact.phone.present?
31
31
  == #{t :phone_small}:
32
- %b= contact.phone
32
+ %b= link_to_phone(contact.phone)
33
33
  |
34
34
  - if contact.mobile.present?
35
35
  == #{t :mobile_small}:
36
- %b= contact.mobile
36
+ %b= link_to_phone(contact.mobile)
37
37
  |
38
38
  = t(:added_ago, value: timeago(contact.created_at)).html_safe
39
39
 
@@ -8,7 +8,7 @@
8
8
  = col(t(:last_name), contact.last_name)
9
9
  %tr
10
10
  = col(t(:email), contact.email, false, true)
11
- = col(t(:phone), contact.phone)
11
+ = col(t(:phone)) { link_to_phone(contact.phone) }
12
12
  %tr
13
13
  = col(t(:account)) do
14
14
  = account_with_url_for(contact)
@@ -6,11 +6,11 @@
6
6
 
7
7
  %div
8
8
  - unless @contact.phone.blank?
9
- == #{t :phone}: <b>#{@contact.do_not_call ? content_tag(:strike, h(@contact.phone)) : h(@contact.phone)}</b><br />
9
+ == #{t :phone}: <b>#{@contact.do_not_call ? content_tag(:strike, @contact.phone) : link_to_phone(@contact.phone)}</b><br />
10
10
  - unless @contact.mobile.blank?
11
- == #{t :mobile}: <b>#{@contact.do_not_call ? content_tag(:strike, h(@contact.mobile)) : h(@contact.mobile)}</b><br />
11
+ == #{t :mobile}: <b>#{@contact.do_not_call ? content_tag(:strike, @contact.mobile) : link_to_phone(@contact.mobile)}</b><br />
12
12
  - unless @contact.fax.blank?
13
- == #{t :fax}: <b>#{@contact.do_not_call ? content_tag(:strike, h(@contact.fax)) : h(@contact.fax)}</b><br />
13
+ == #{t :fax}: <b>#{@contact.do_not_call ? content_tag(:strike, @contact.fax) : link_to_phone(@contact.fax)}</b><br />
14
14
  %div
15
15
  - unless @contact.email.blank?
16
16
  == #{t :email}: <b>#{link_to_email(@contact.email)}</b><br />
@@ -13,11 +13,11 @@
13
13
  %tr
14
14
  %td
15
15
  .label #{t :email}:
16
- = f.text_field :email
16
+ = f.email_field :email
17
17
  %td= spacer
18
18
  %td
19
19
  .label #{t :phone}:
20
- = f.text_field :phone
20
+ = f.phone_field :phone
21
21
 
22
22
  %table
23
23
  = fields_for(@account) do |a|
@@ -9,7 +9,7 @@
9
9
  %tr
10
10
  %td
11
11
  .label.top #{t :blog}:
12
- = f.text_field :blog
12
+ = f.url_field :blog
13
13
  %td= spacer
14
14
  %td
15
15
  .label.top #{t :twitter}:
@@ -20,7 +20,7 @@
20
20
  %dt
21
21
  = link_to(account.website, account.website.to_url) + " | " if account.website.present?
22
22
  = link_to_email(account.email) + " | " if account.email.present?
23
- = t(:phone_small) + ": " + (account.toll_free_phone || account.phone) << " | " if account.toll_free_phone? || account.phone?
23
+ = (t(:phone_small) + ": ").html_safe + link_to_phone(account.toll_free_phone || account.phone) + " | ".html_safe if account.toll_free_phone? || account.phone?
24
24
  = t('pluralize.contact', account.contacts_count) + " | "
25
25
  = t('pluralize.opportunity', account.opportunities_count)
26
26
  - if account.tags.present?
@@ -2,6 +2,7 @@
2
2
  %html
3
3
  %head
4
4
  %meta{ "http-equiv" => "Content-Type", content: "text/html; charset=utf-8" }
5
+ %meta{ name: "viewport", content: "width=device-width, initial-scale=1.0" }
5
6
  %title Fat Free CRM
6
7
  == <!-- #{controller.controller_name} : #{controller.action_name} -->
7
8
  = stylesheet_link_tag :application
@@ -10,6 +11,9 @@
10
11
  #{yield :stylesheet_includes}
11
12
  %style= yield :styles
12
13
 
14
+ %link{ rel: "stylesheet", href: "https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" }
15
+ %script{ src: "https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" }
16
+
13
17
  = javascript_include_tag :application
14
18
 
15
19
  - unless tabless_layout? || %w(en-US en-GB).include?(I18n.locale.to_s)
@@ -1,5 +1,5 @@
1
1
  - edit ||= false
2
- - collapsed = session[:lead_contact].nil? # && @lead.errors.empty?
2
+ - collapsed = false
3
3
  = subtitle :lead_contact, collapsed, t(:contact_info)
4
4
  .section
5
5
  %small#lead_contact_intro{ hidden_if(!collapsed) }
@@ -17,11 +17,11 @@
17
17
  %tr
18
18
  %td
19
19
  .label #{t :alt_email}:
20
- = f.text_field :alt_email
20
+ = f.email_field :alt_email
21
21
  %td= spacer
22
22
  %td
23
23
  .label #{t :mobile}:
24
- = f.text_field :mobile
24
+ = phone_field_with_pattern(f, :mobile)
25
25
  %tr
26
26
  %td
27
27
  = render "shared/address", f: f, asset: @lead, type: 'business', title: :address