fat_free_crm 0.25.0 → 0.26.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.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/bootstrap-custom.scss +1 -0
  3. data/app/assets/stylesheets/common.scss +9 -3
  4. data/app/controllers/admin/research_tools_controller.rb +45 -0
  5. data/app/controllers/admin/settings_controller.rb +71 -0
  6. data/app/controllers/admin/users_controller.rb +0 -1
  7. data/app/controllers/comments_controller.rb +2 -2
  8. data/app/controllers/emails_controller.rb +2 -1
  9. data/app/controllers/users_controller.rb +2 -1
  10. data/app/helpers/application_helper.rb +28 -12
  11. data/app/models/entities/contact.rb +1 -3
  12. data/app/models/entities/lead.rb +0 -1
  13. data/app/models/observers/entity_observer.rb +1 -1
  14. data/app/models/polymorphic/comment.rb +1 -1
  15. data/app/models/research_tool.rb +4 -0
  16. data/app/models/users/ability.rb +6 -0
  17. data/app/models/users/user.rb +0 -1
  18. data/app/views/accounts/_sidebar_show.html.haml +2 -0
  19. data/app/views/admin/research_tools/_form.html.haml +12 -0
  20. data/app/views/admin/research_tools/_new.html.haml +11 -0
  21. data/app/views/admin/research_tools/_research_tool.html.haml +8 -0
  22. data/app/views/admin/research_tools/_top_section.html.haml +6 -0
  23. data/app/views/admin/research_tools/create.js.haml +7 -0
  24. data/app/views/admin/research_tools/destroy.js.haml +1 -0
  25. data/app/views/admin/research_tools/edit.html.haml +1 -0
  26. data/app/views/admin/research_tools/index.html.haml +19 -0
  27. data/app/views/admin/research_tools/new.js.haml +7 -0
  28. data/app/views/admin/research_tools/update.js.haml +4 -0
  29. data/app/views/admin/settings/_ai_prompts.html.haml +15 -0
  30. data/app/views/admin/settings/_application.html.haml +44 -0
  31. data/app/views/admin/settings/_customization.html.haml +43 -0
  32. data/app/views/admin/settings/_email.html.haml +112 -0
  33. data/app/views/admin/settings/_user_signup.html.haml +13 -0
  34. data/app/views/admin/settings/_validations.html.haml +19 -0
  35. data/app/views/admin/settings/index.html.haml +10 -2
  36. data/app/views/admin/tags/index.html.haml +4 -0
  37. data/app/views/contacts/_index_brief.html.haml +2 -2
  38. data/app/views/contacts/_index_full.html.haml +1 -1
  39. data/app/views/contacts/_index_long.html.haml +1 -1
  40. data/app/views/contacts/_sidebar_show.html.haml +3 -1
  41. data/app/views/contacts/_web.html.haml +1 -3
  42. data/app/views/contacts/index.xls.builder +0 -2
  43. data/app/views/devise/sessions/new.html.haml +1 -1
  44. data/app/views/entities/_title_bar.html.haml +1 -1
  45. data/app/views/layouts/admin/application.html.haml +1 -1
  46. data/app/views/layouts/application.html.haml +1 -1
  47. data/app/views/leads/_sidebar_show.html.haml +3 -1
  48. data/app/views/leads/_web.html.haml +1 -3
  49. data/app/views/leads/index.xls.builder +0 -2
  50. data/app/views/shared/_research_tools.html.haml +8 -0
  51. data/app/views/users/_avatar.html.haml +1 -1
  52. data/app/views/users/_profile.html.haml +11 -4
  53. data/app/views/users/_user.html.haml +3 -4
  54. data/config/application.rb +2 -0
  55. data/config/initializers/devise.rb +5 -1
  56. data/config/initializers/rack_attack.rb +29 -0
  57. data/config/locales/fat_free_crm.cs.yml +1 -2
  58. data/config/locales/{fat_free_crm.de.yml → fat_free_crm.de-DE.yml} +1 -2
  59. data/config/locales/fat_free_crm.en-GB.yml +74 -133
  60. data/config/locales/fat_free_crm.en-US.yml +18 -4
  61. data/config/locales/fat_free_crm.es-CL.yml +0 -1
  62. data/config/locales/fat_free_crm.es.yml +1 -2
  63. data/config/locales/fat_free_crm.et.yml +1 -2
  64. data/config/locales/fat_free_crm.fr-CA.yml +1 -2
  65. data/config/locales/fat_free_crm.fr.yml +1 -2
  66. data/config/locales/fat_free_crm.it.yml +1 -2
  67. data/config/locales/fat_free_crm.ja.yml +1 -2
  68. data/config/locales/fat_free_crm.nl.yml +1 -2
  69. data/config/locales/fat_free_crm.pl.yml +0 -1
  70. data/config/locales/fat_free_crm.pt-BR.yml +0 -1
  71. data/config/locales/fat_free_crm.ru.yml +1 -2
  72. data/config/locales/fat_free_crm.sv-SE.yml +1 -2
  73. data/config/locales/fat_free_crm.th.yml +0 -1
  74. data/config/locales/fat_free_crm.zh-CN.yml +1 -2
  75. data/config/routes.rb +8 -2
  76. data/config/settings.default.yml +19 -5
  77. data/db/demo/contacts.yml +0 -2
  78. data/db/demo/leads.yml +0 -1
  79. data/db/demo/research_tools.yml +12 -0
  80. data/db/demo/users.yml +0 -1
  81. data/db/fat_free_crm_development.sqlite3 +0 -0
  82. data/db/fat_free_crm_test.sqlite3 +0 -0
  83. data/db/{fat_free_crm_development.sqlite3-shm → fat_free_crm_test.sqlite3-shm} +0 -0
  84. data/db/fat_free_crm_test.sqlite3-wal +0 -0
  85. data/db/migrate/20230526212613_convert_to_active_storage.rb +2 -0
  86. data/db/migrate/20230526212614_create_research_tools.rb +13 -0
  87. data/db/migrate/20250502095012_remove_skype_from_users_contacts_and_leads.rb +15 -0
  88. data/db/migrate/20250806025815_add_email_preferences_to_users.rb +11 -0
  89. data/db/schema.rb +3 -5
  90. data/db/seeds.rb +2 -1
  91. data/lib/fat_free_crm/tabs.rb +2 -1
  92. data/lib/fat_free_crm/version.rb +1 -1
  93. data/lib/tasks/ffcrm/demo.rake +15 -0
  94. metadata +48 -16
  95. data/db/fat_free_crm_development.sqlite3-wal +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f2f7d077bdbf247654e265f12063aecc477308e6f20342ebf497ab1d8ffd2fd
4
- data.tar.gz: 6d00ebc247a1c7bc40df2e28fbe4893d9a57816b053d39e2d43039295a42f2b4
3
+ metadata.gz: ba3bf6976179155013188261941cad491438f42dda298d39f01f37502d1928d9
4
+ data.tar.gz: 616970ceed5e34145399c915e6f3ccf740091101d1491771f53904c2b98b1a9a
5
5
  SHA512:
6
- metadata.gz: 1585e7dd8acf9c6c43882527cc53164a22346ed73b1f77629aa5e0a59f66fe13797b5a2f1c4dee9a5c3303dd6cd95fc61ce35eb0437674d4d0bdec84b5cfad76
7
- data.tar.gz: 43956457732e8e30a7467f6ff4b996d309c113832b122d4c6214533810726b0960c11556c8d62971ae28be7b949ab1cc1c9dd31cc22d3635814c0bbbb8ccd9cf
6
+ metadata.gz: f5995672093e110187c94aabe0c6d5f4ea3e950fadd6192d1dc2989d361259eb5ee6baa3917f7ee6be80d6433d2eacf6d6bdaed848fbd55572286372e1edf87e
7
+ data.tar.gz: f1dcc4238b2ece5850b811f611d928dd4db015cc0b29807a143cacd8d2521367a50a16f486ad034377c020c67b7705d3e5323906ceb3c1da5c42c04bd73c19a0
@@ -33,6 +33,7 @@ $btn-font-size: '0.9rem';
33
33
 
34
34
  // 3. Include remainder of required Bootstrap stylesheets
35
35
  @import "bootstrap/variables";
36
+ @import "bootstrap/maps";
36
37
  @import "bootstrap/mixins";
37
38
 
38
39
  // 4. Include any optional Bootstrap components as you like
@@ -78,6 +78,11 @@ $sidebar_width: 210px;
78
78
  margin: auto;
79
79
  width: 480px; }
80
80
 
81
+ .gravatar {
82
+ border-radius: 50% !important;
83
+ box-shadow: 0 0 0 1px #aaa
84
+ }
85
+
81
86
  .standalone {
82
87
  background: whitesmoke;
83
88
  border: 20px lightsteelblue solid;
@@ -338,7 +343,7 @@ $bg_color9: #FFDD4A;
338
343
  margin-left: 88px; }
339
344
 
340
345
  .indentslim {
341
- margin-left: 38px; }
346
+ margin-left: 4em; }
342
347
 
343
348
  .indentwide {
344
349
  margin-left: 106px; }
@@ -376,7 +381,8 @@ $bg_color9: #FFDD4A;
376
381
  width: 500px; }
377
382
  .gravatar {
378
383
  float: left;
379
- margin: 3px 10px 3px 3px; }
384
+ margin: 3px 10px 3px 3px;
385
+ }
380
386
  .body {
381
387
  display: inline;
382
388
  font-size: 0.9em; }
@@ -483,7 +489,7 @@ $bg_color9: #FFDD4A;
483
489
  padding: 6px 0px 6px 0px;
484
490
  .gravatar {
485
491
  float: left;
486
- padding: 0px 8px 0px 0px; }
492
+ margin: 0 0.5em ; }
487
493
  tt {
488
494
  color: dimgray;
489
495
  }
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Admin::ResearchToolsController < Admin::ApplicationController
4
+ before_action :setup_current_tab, only: %i[index]
5
+
6
+ load_resource
7
+
8
+ def index
9
+ @research_tools = ResearchTool.all
10
+ respond_with(@research_tools)
11
+ end
12
+
13
+ def new
14
+ respond_with(@research_tool)
15
+ end
16
+
17
+ def edit
18
+ respond_with(@research_tool)
19
+ end
20
+
21
+ def create
22
+ @research_tool.update(research_tool_params)
23
+ respond_with(@research_tool, location: -> { admin_research_tools_path })
24
+ end
25
+
26
+ def update
27
+ @research_tool.update(research_tool_params)
28
+ respond_with(@research_tool, location: -> { admin_research_tools_path })
29
+ end
30
+
31
+ def destroy
32
+ @research_tool.destroy
33
+ respond_with(@research_tool)
34
+ end
35
+
36
+ protected
37
+
38
+ def research_tool_params
39
+ params.require(:research_tool).permit(:name, :url_template, :enabled)
40
+ end
41
+
42
+ def setup_current_tab
43
+ set_current_tab('admin/research_tools')
44
+ end
45
+ end
@@ -14,6 +14,77 @@ class Admin::SettingsController < Admin::ApplicationController
14
14
  def index
15
15
  end
16
16
 
17
+ # PUT /admin/settings
18
+ #----------------------------------------------------------------------------
19
+ def update
20
+ settings = settings_params.to_h.with_indifferent_access
21
+
22
+ # All settings are strings from the form.
23
+ # We need to convert them to their correct types before saving.
24
+
25
+ # Booleans
26
+ %w[per_user_locale compound_address task_calendar_with_time require_first_names require_last_names require_unique_account_names comments_visible_on_dashboard enforce_international_phone_format].each do |key|
27
+ settings[key] = (settings[key] == '1') if settings.key?(key)
28
+ end
29
+
30
+ # Nested booleans
31
+ settings[:email_dropbox][:ssl] = (settings[:email_dropbox][:ssl] == '1') if settings.key?(:email_dropbox) && settings[:email_dropbox].key?(:ssl)
32
+ settings[:email_comment_replies][:ssl] = (settings[:email_comment_replies][:ssl] == '1') if settings.key?(:email_comment_replies) && settings[:email_comment_replies].key?(:ssl)
33
+
34
+ # Arrays from textareas
35
+ %w[account_category campaign_status lead_status lead_source opportunity_stage task_category task_bucket task_completed priority_countries].each do |key|
36
+ settings[key] = settings[key].split(/\r?\n/).map(&:strip).compact_blank if settings[key].is_a?(String)
37
+ end
38
+
39
+ # Symbols
40
+ settings[:user_signup] = settings[:user_signup].to_sym if settings[:user_signup].is_a?(String)
41
+
42
+ # Save all settings
43
+ settings.each do |key, value|
44
+ Setting[key] = value
45
+ end
46
+
47
+ redirect_to admin_settings_path, notice: t('fat_free_crm.settings_updated')
48
+ end
49
+
50
+ private
51
+
52
+ def settings_params
53
+ params.require(:settings).permit(
54
+ :host, :base_url, :locale, :per_user_locale, :default_access, :user_signup,
55
+ :compound_address, :task_calendar_with_time, :require_first_names,
56
+ :require_last_names, :require_unique_account_names,
57
+ :comments_visible_on_dashboard, :enforce_international_phone_format,
58
+ :opportunity_default_stage,
59
+ background_info: [],
60
+ priority_countries: [],
61
+ account_category: [],
62
+ campaign_status: [],
63
+ lead_status: [],
64
+ lead_source: [],
65
+ opportunity_stage: [],
66
+ task_category: [],
67
+ task_bucket: [],
68
+ task_completed: [],
69
+ smtp: %i[
70
+ address from enable_starttls_auto port authentication
71
+ user_name password
72
+ ],
73
+ email_dropbox: [
74
+ :server, :port, :ssl, :address, :user, :password, :scan_folder,
75
+ :attach_to_account, :move_to_folder, :move_invalid_to_folder,
76
+ { address_aliases: [] }
77
+ ],
78
+ email_comment_replies: %i[
79
+ server port ssl address user password scan_folder
80
+ move_to_folder move_invalid_to_folder
81
+ ],
82
+ ai_prompts: %i[
83
+ about_my_business how_i_plan_to_use_ffcrm
84
+ ]
85
+ )
86
+ end
87
+
17
88
  def setup_current_tab
18
89
  set_current_tab('admin/settings')
19
90
  end
@@ -122,7 +122,6 @@ class Admin::UsersController < Admin::ApplicationController
122
122
  :aim,
123
123
  :yahoo,
124
124
  :google,
125
- :skype,
126
125
  :password,
127
126
  :password_confirmation,
128
127
  group_ids: []
@@ -6,6 +6,8 @@
6
6
  # See MIT-LICENSE file or http://www.opensource.org/licenses/mit-license.php
7
7
  #------------------------------------------------------------------------------
8
8
  class CommentsController < ApplicationController
9
+ load_and_authorize_resource
10
+
9
11
  # GET /comments
10
12
  # GET /comments.json
11
13
  # GET /comments.xml
@@ -62,7 +64,6 @@ class CommentsController < ApplicationController
62
64
  # PUT /comments/1.xml not implemented
63
65
  #----------------------------------------------------------------------------
64
66
  def update
65
- @comment = Comment.find(params[:id])
66
67
  @comment.update(comment_params)
67
68
  respond_with(@comment)
68
69
  end
@@ -72,7 +73,6 @@ class CommentsController < ApplicationController
72
73
  # DELETE /comments/1.xml not implemented
73
74
  #----------------------------------------------------------------------------
74
75
  def destroy
75
- @comment = Comment.find(params[:id])
76
76
  @comment.destroy
77
77
  respond_with(@comment)
78
78
  end
@@ -6,12 +6,13 @@
6
6
  # See MIT-LICENSE file or http://www.opensource.org/licenses/mit-license.php
7
7
  #------------------------------------------------------------------------------
8
8
  class EmailsController < ApplicationController
9
+ load_and_authorize_resource
10
+
9
11
  # DELETE /emails/1
10
12
  # DELETE /emails/1.json
11
13
  # DELETE /emails/1.xml AJAX
12
14
  #----------------------------------------------------------------------------
13
15
  def destroy
14
- @email = Email.find(params[:id])
15
16
  @email.destroy
16
17
  respond_with(@email)
17
18
  end
@@ -144,7 +144,8 @@ class UsersController < ApplicationController
144
144
  :aim,
145
145
  :yahoo,
146
146
  :google,
147
- :skype
147
+ :subscribe_to_comment_replies,
148
+ :receive_assigned_notifications
148
149
  )
149
150
  end
150
151
 
@@ -269,21 +269,16 @@ module ApplicationHelper
269
269
  def web_presence_icons(person)
270
270
  sites = []
271
271
  icon_for_site = {
272
- skype: "skype",
273
272
  facebook: "facebook",
274
273
  linkedin: "linkedin",
275
274
  twitter: "twitter",
276
275
  blog: "external-link"
277
276
  }
278
- %i[blog linkedin facebook twitter skype].each do |site|
277
+ %i[blog linkedin facebook twitter].each do |site|
279
278
  url = person.send(site)
280
279
  next if url.blank?
281
280
 
282
- if site == :skype
283
- url = "callto:" + url
284
- else
285
- url = "http://" + url unless url.match?(%r{^https?://})
286
- end
281
+ url = "http://" + url unless url.match?(%r{^https?://})
287
282
  sites << if icon_for_site[site]
288
283
  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
284
  else
@@ -338,7 +333,7 @@ module ApplicationHelper
338
333
  raw "$.get('#{timezone_path}', {offset: (new Date()).getTimezoneOffset()});" unless session[:timezone_offset]
339
334
  end
340
335
 
341
- STYLES = { large: "75x75#", medium: "50x50#", small: "25x25#", thumb: "16x16#" }.freeze
336
+ STYLES = { large: "180x180#", medium: "50x50#", small: "25x25#", thumb: "16x16#" }.freeze
342
337
 
343
338
  # Convert STYLE symbols to 'w x h' format for Gravatar and Rails
344
339
  # e.g. size_from_style(:size => :large) -> '75x75'
@@ -365,10 +360,22 @@ module ApplicationHelper
365
360
  if model.respond_to?(:avatar) && model.avatar.present?
366
361
  size = args[:size].split('x').map(&:to_i) # convert '75x75' into [75, 75]
367
362
 
368
- image_tag model.avatar.image.variant(resize_to_limit: size)
363
+ image_tag model.avatar.image.variant(resize_to_limit: size), args
369
364
  else
370
365
  gravatar_image_tag(model.email, args)
371
- end
366
+ end
367
+ end
368
+
369
+ def ai_prompt_link(prompt)
370
+ return unless Setting[:about_my_business].present? || Setting[:how_i_plan_to_use_ffcrm].present?
371
+
372
+ full_prompt = [Setting[:about_my_business], Setting[:how_i_plan_to_use_ffcrm], prompt].compact.compact_blank.join(". ")
373
+
374
+ link_to(t(:ai_prompt_link), "https://chat.openai.com/?model=gpt-4o&prompt=#{URI.encode_uri_component(full_prompt)}",
375
+ target: "_blank",
376
+ title: "#{t(:ai_prompt_link_tooltip)}: #{prompt}",
377
+ rel: "noopener noreferrer",
378
+ class: "ai-prompt-link")
372
379
  end
373
380
 
374
381
  # Returns default permissions intro.
@@ -473,7 +480,7 @@ module ApplicationHelper
473
480
  link_to_email(fmt_value)
474
481
  else
475
482
  fmt_value.gsub(%r{((http|ftp|https)://[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/\+#]*[\w\-\@?^=%&amp;/\+#])?)}, "<a href=\"\\1\">\\1</a>")
476
- end
483
+ end
477
484
  out << content_tag(:td, fmt_value, class: last_class)
478
485
  end
479
486
  out
@@ -511,7 +518,7 @@ module ApplicationHelper
511
518
  "#{h view.name}-button active"
512
519
  else
513
520
  "#{h view.name}-button"
514
- end
521
+ end
515
522
  lis << content_tag(:li) do
516
523
  url = show_or_index_action == "index" ? send("redraw_#{controller.controller_name}_path") : send("#{controller.controller_name.singularize}_path")
517
524
  link_to('#', title: t(view.name, default: h(view.title)), "data-view": h(view.name), "data-url": h(url), "data-context": show_or_index_action, class: classes) do
@@ -569,4 +576,13 @@ module ApplicationHelper
569
576
  def current_view_name
570
577
  current_user.pref[:"#{controller.controller_name}_#{show_or_index_action}_view"]
571
578
  end
579
+
580
+ def expand_research_tool_url(tool, entity)
581
+ template = Addressable::Template.new(tool.url_template)
582
+ mappings = {}
583
+ template.keys.each do |key| # rubocop:disable Style/HashEachMethods
584
+ mappings[key] = entity.send(key) if entity.respond_to?(key)
585
+ end
586
+ template.expand(mappings).to_s
587
+ end
572
588
  end
@@ -35,7 +35,6 @@
35
35
  # created_at :datetime
36
36
  # updated_at :datetime
37
37
  # background_info :string(255)
38
- # skype :string(128)
39
38
  #
40
39
 
41
40
  class Contact < ActiveRecord::Base
@@ -111,7 +110,6 @@ class Contact < ActiveRecord::Base
111
110
  validates_length_of :linkedin, maximum: 128
112
111
  validates_length_of :facebook, maximum: 128
113
112
  validates_length_of :twitter, maximum: 128
114
- validates_length_of :skype, maximum: 128
115
113
 
116
114
  # Default values provided through class methods.
117
115
  #----------------------------------------------------------------------------
@@ -177,7 +175,7 @@ class Contact < ActiveRecord::Base
177
175
  assigned_to: params[:account][:assigned_to],
178
176
  access: params[:access]
179
177
  }
180
- %w[first_name last_name title source email alt_email phone mobile blog linkedin facebook twitter skype do_not_call background_info].each do |name|
178
+ %w[first_name last_name title source email alt_email phone mobile blog linkedin facebook twitter do_not_call background_info].each do |name|
181
179
  attributes[name] = model.send(name.intern)
182
180
  end
183
181
 
@@ -35,7 +35,6 @@
35
35
  # created_at :datetime
36
36
  # updated_at :datetime
37
37
  # background_info :string(255)
38
- # skype :string(128)
39
38
  #
40
39
 
41
40
  class Lead < ActiveRecord::Base
@@ -19,7 +19,7 @@ class EntityObserver < ActiveRecord::Observer
19
19
  private
20
20
 
21
21
  def send_notification_to_assignee(item)
22
- UserMailer.assigned_entity_notification(item, current_user).deliver_later if item.assignee.present? && current_user.present? && can_send_email?
22
+ UserMailer.assigned_entity_notification(item, current_user).deliver_later if item.assignee.present? && item.assignee.receive_assigned_notifications? && current_user.present? && can_send_email?
23
23
  end
24
24
 
25
25
  # Need to have a host set before email can be sent
@@ -54,7 +54,7 @@ class Comment < ActiveRecord::Base
54
54
  def notify_subscribers
55
55
  users_to_notify = User.where(id: commentable.subscribed_users.reject { |user_id| user_id == user.id })
56
56
  users_to_notify.select(&:emailable?).each do |subscriber|
57
- SubscriptionMailer.comment_notification(subscriber, self).deliver_later
57
+ SubscriptionMailer.comment_notification(subscriber, self).deliver_later if subscriber.subscribe_to_comment_replies?
58
58
  end
59
59
  end
60
60
 
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ResearchTool < ActiveRecord::Base
4
+ end
@@ -31,6 +31,12 @@ class Ability
31
31
  can :manage, entities + [Task], user_id: user.id
32
32
  can :manage, entities + [Task], assigned_to: user.id
33
33
 
34
+ can :create, Comment
35
+ can :manage, Comment, user_id: user.id
36
+
37
+ can :create, Email
38
+ can :manage, Email, user_id: user.id
39
+
34
40
  #
35
41
  # Due to an obscure bug (see https://github.com/ryanb/cancan/issues/213)
36
42
  # we must switch on user.admin? here to avoid the nil constraints which
@@ -22,7 +22,6 @@
22
22
  # aim :string(32)
23
23
  # yahoo :string(32)
24
24
  # google :string(32)
25
- # skype :string(32)
26
25
  # encrypted_password :string(255) default(""), not null
27
26
  # password_salt :string(255) default(""), not null
28
27
  # last_sign_in_at :datetime
@@ -64,6 +64,8 @@
64
64
  %dt
65
65
  .tags= tags_for_index(@account)
66
66
 
67
+ = render "shared/research_tools", entity: @account
68
+
67
69
  = hook(:show_account_sidebar_bottom, self, account: @account)
68
70
 
69
71
  - if @account.latitude.present? && @account.longitude.present?
@@ -0,0 +1,12 @@
1
+ = form_for([:admin, @research_tool]) do |f|
2
+ .section
3
+ %table
4
+ %tr
5
+ %td
6
+ .label.top.req Name:
7
+ = f.text_field :name, placeholder: 'e.g. Google Scholar', required: true
8
+ %tr
9
+ %td
10
+ .label.top.req URL Template:
11
+ = f.text_field :url_template, placeholder: 'e.g. https://example.com/search?q={query}', required: true
12
+
@@ -0,0 +1,11 @@
1
+ - path = new_admin_research_tool_path
2
+
3
+ = form_for([:admin, @research_tool], html: one_submit_only, remote: true) do |f|
4
+ = link_to_close path
5
+ = f.error_messages
6
+ = render partial: "form", locals: { f: f }
7
+ .buttonbar
8
+ = f.submit t(:create_research_tool), id: :research_tool_submit
9
+ or
10
+ = link_to_cancel path
11
+
@@ -0,0 +1,8 @@
1
+ %li.highlight[research_tool]
2
+ = research_tool.name
3
+ .tools
4
+ = link_to_edit(research_tool, url: edit_admin_research_tool_path(research_tool))
5
+ |
6
+ = link_to_delete(research_tool, url: admin_research_tool_path(research_tool),
7
+ data: { confirm: t(:are_you_sure) },
8
+ method: :delete)
@@ -0,0 +1,6 @@
1
+ .section
2
+ %table
3
+ %tr
4
+ %td
5
+ .label.top.req= t(:name) + ":"
6
+ = f.text_field :name
@@ -0,0 +1,7 @@
1
+ - if @research_tool.persisted?
2
+ $("#research_tools").prepend("<%= j render(@research_tool) %>");
3
+ $("#create_research_tool").slideUp(250, function() {
4
+ $(this).html("");
5
+ });
6
+ - else
7
+ $("#create_research_tool").html("<%= j render('form') %>");
@@ -0,0 +1 @@
1
+ $("#<%= dom_id(@research_tool) %>").fadeOut(250);
@@ -0,0 +1 @@
1
+ = render 'form'
@@ -0,0 +1,19 @@
1
+ .title_tools
2
+ = link_to_inline(:create_research_tool, new_admin_research_tool_path, text: t(:create_research_tool))
3
+
4
+ .title
5
+ %span#create_research_tool_title #{t :research_tools}
6
+ = image_tag("loading.gif", size: :thumb, id: "loading", style: "display: none;")
7
+ .remote#create_research_tool{ hidden }
8
+
9
+ .list#research_tools
10
+ - if @research_tools.any?
11
+ = render partial: "admin/research_tools/research_tool", collection: @research_tools
12
+ - else
13
+ = render "shared/empty"
14
+
15
+
16
+ = styles_for :research_tools
17
+
18
+
19
+ #export= render "shared/export"
@@ -0,0 +1,7 @@
1
+ crm.flip_form('create_research_tool');
2
+
3
+ - unless params[:cancel].true?
4
+ $('#create_research_tool').html('#{ j render(partial: "new") }');
5
+ crm.set_title('create_research_tool', '#{t(:create_research_tool)}');
6
+ - else
7
+ crm.set_title('create_research_tool', '#{t(:research_tools)}');
@@ -0,0 +1,4 @@
1
+ - if @research_tool.errors.empty?
2
+ $("#<%= dom_id(@research_tool) %>").replaceWith("<%= j render(@research_tool) %>");
3
+ - else
4
+ $("#edit_research_tool_<%= @research_tool.id %>").html("<%= j render('form') %>");
@@ -0,0 +1,15 @@
1
+ %fieldset.panel.panel-default
2
+ .panel-heading
3
+ %h4= t(:ai_prompts)
4
+ %p= t(:ai_prompts_description)
5
+ .panel-body
6
+ = f.fields_for :ai_prompts, Setting.ai_prompts do |ai_fields|
7
+ .row
8
+ .col-md-6
9
+ .form-group
10
+ = ai_fields.label :about_my_business, t(:about_my_business)
11
+ = ai_fields.text_area :about_my_business, class: 'form-control', placeholder: t(:about_my_business_placeholder), rows: 5
12
+ .col-md-6
13
+ .form-group
14
+ = ai_fields.label :how_i_plan_to_use_ffcrm, t(:how_i_plan_to_use_ffcrm)
15
+ = ai_fields.text_area :how_i_plan_to_use_ffcrm, class: 'form-control', placeholder: t(:how_i_plan_to_use_ffcrm_placeholder), rows: 5
@@ -0,0 +1,44 @@
1
+ %fieldset.panel.panel-default
2
+ .panel-heading
3
+ %h4= t('admin.settings.application.title', default: 'Application')
4
+ .panel-body
5
+ .row
6
+ .col-md-6
7
+ .form-group
8
+ = f.label :host
9
+ = f.text_field :host, value: Setting.host, class: 'form-control'
10
+ .form-group
11
+ = f.label :base_url, "Base URL"
12
+ = f.text_field :base_url, value: Setting.base_url, class: 'form-control', placeholder: "/crm"
13
+ .form-group
14
+ = f.label :locale, "Default Language"
15
+ = f.select :locale, options_for_select(I18n.available_locales, Setting.locale), {}, class: 'form-control'
16
+ .form-group
17
+ = f.label :default_access, "Default record permissions"
18
+ = f.select :default_access, options_for_select([['Public', 'Public'], ['Private', 'Private'], ['Shared', 'Shared']], Setting.default_access), {}, class: 'form-control'
19
+ .col-md-6
20
+ .form-group
21
+ .checkbox
22
+ = f.label :per_user_locale do
23
+ = f.check_box :per_user_locale, checked: Setting.per_user_locale
24
+ Allow users to select their own language
25
+ .form-group
26
+ .checkbox
27
+ = f.label :compound_address do
28
+ = f.check_box :compound_address, checked: Setting.compound_address
29
+ Use separate fields for addresses
30
+ .form-group
31
+ .checkbox
32
+ = f.label :task_calendar_with_time do
33
+ = f.check_box :task_calendar_with_time, checked: Setting.task_calendar_with_time
34
+ Show time in task calendar
35
+ .form-group
36
+ .checkbox
37
+ = f.label :comments_visible_on_dashboard do
38
+ = f.check_box :comments_visible_on_dashboard, checked: Setting.comments_visible_on_dashboard
39
+ Show comments on dashboard
40
+ .form-group
41
+ .checkbox
42
+ = f.label :enforce_international_phone_format do
43
+ = f.check_box :enforce_international_phone_format, checked: Setting.enforce_international_phone_format
44
+ Enforce international phone number format
@@ -0,0 +1,43 @@
1
+ %fieldset.panel.panel-default
2
+ .panel-heading
3
+ %h4= t('admin.settings.customization.title', default: 'Customization')
4
+ .panel-body
5
+ .row
6
+ .col-md-4
7
+ .form-group
8
+ = f.label :background_info
9
+ = f.select :background_info, options_for_select([:account, :campaign, :contact, :lead, :opportunity, :task], Setting.background_info), {}, { multiple: true, class: 'form-control' }
10
+ %span.help-block Select models that should have background information displayed.
11
+ .form-group
12
+ = f.label :priority_countries
13
+ = f.text_area :priority_countries, value: (Setting.priority_countries || []).join("\n"), class: 'form-control', rows: 4
14
+ %span.help-block One country code per line (e.g. US, GB).
15
+ .form-group
16
+ = f.label :opportunity_default_stage
17
+ = f.select :opportunity_default_stage, options_for_select(Setting.opportunity_stage.map{|s| [s.to_s.humanize, s]}, Setting.opportunity_default_stage), {}, class: 'form-control'
18
+ .col-md-4
19
+ .form-group
20
+ = f.label :account_category
21
+ = f.text_area :account_category, value: Setting.account_category.map { |v| v.is_a?(Symbol) ? t(v) : v }.join("\n"), class: 'form-control', rows: 5
22
+ .form-group
23
+ = f.label :campaign_status
24
+ = f.text_area :campaign_status, value: Setting.campaign_status.map { |v| v.is_a?(Symbol) ? t(v) : v }.join("\n"), class: 'form-control', rows: 5
25
+ .form-group
26
+ = f.label :lead_status
27
+ = f.text_area :lead_status, value: Setting.lead_status.map { |v| v.is_a?(Symbol) ? t(v) : v }.join("\n"), class: 'form-control', rows: 5
28
+ .form-group
29
+ = f.label :lead_source
30
+ = f.text_area :lead_source, value: Setting.lead_source.map { |v| v.is_a?(Symbol) ? t(v) : v }.join("\n"), class: 'form-control', rows: 5
31
+ .col-md-4
32
+ .form-group
33
+ = f.label :opportunity_stage
34
+ = f.text_area :opportunity_stage, value: Setting.opportunity_stage.map { |v| v.is_a?(Symbol) ? t(v) : v }.join("\n"), class: 'form-control', rows: 5
35
+ .form-group
36
+ = f.label :task_category
37
+ = f.text_area :task_category, value: Setting.task_category.map { |v| v.is_a?(Symbol) ? t(v) : v }.join("\n"), class: 'form-control', rows: 5
38
+ .form-group
39
+ = f.label :task_bucket
40
+ = f.text_area :task_bucket, value: Setting.task_bucket.map { |v| v.is_a?(Symbol) ? t(v) : v }.join("\n"), class: 'form-control', rows: 5
41
+ .form-group
42
+ = f.label :task_completed
43
+ = f.text_area :task_completed, value: Setting.task_completed.map { |v| v.is_a?(Symbol) ? t(v) : v }.join("\n"), class: 'form-control', rows: 5