fat_free_crm 0.22.0 → 0.22.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of fat_free_crm might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/app/assets/javascripts/application.js.erb +1 -0
- data/app/assets/javascripts/crm.js.coffee +6 -3
- data/app/assets/javascripts/crm_select2.js.coffee +4 -1
- data/app/assets/javascripts/crm_tags.js.coffee +4 -4
- data/app/assets/javascripts/crm_validations.js.coffee +12 -0
- data/app/assets/stylesheets/bootstrap-custom.scss +3 -3
- data/app/assets/stylesheets/common.scss +9 -0
- data/app/assets/stylesheets/rails.scss +1 -1
- data/app/controllers/admin/fields_controller.rb +16 -13
- data/app/controllers/entities_controller.rb +1 -1
- data/app/controllers/lists_controller.rb +5 -4
- data/app/helpers/application_helper.rb +4 -4
- data/app/helpers/opportunities_helper.rb +4 -2
- data/app/helpers/users_helper.rb +1 -1
- data/app/models/entities/campaign.rb +2 -2
- data/app/models/fields/custom_field_pair.rb +6 -7
- data/app/models/fields/field.rb +1 -3
- data/app/models/list.rb +1 -1
- data/app/views/accounts/_contact_info.html.haml +1 -1
- data/app/views/accounts/_top_section.html.haml +3 -3
- data/app/views/accounts/show.js.haml +1 -1
- data/app/views/accounts/update.js.haml +1 -0
- data/app/views/admin/custom_fields/_check_boxes_field.html.haml +4 -1
- data/app/views/admin/custom_fields/_date_pair_field.html.haml +1 -1
- data/app/views/campaigns/_top_section.html.haml +2 -2
- data/app/views/campaigns/show.js.haml +1 -1
- data/app/views/campaigns/update.js.haml +1 -0
- data/app/views/contacts/_top_section.html.haml +5 -5
- data/app/views/contacts/show.js.haml +2 -3
- data/app/views/contacts/update.js.haml +1 -0
- data/app/views/devise/sessions/new.html.haml +4 -5
- data/app/views/fields/_group.html.haml +5 -2
- data/app/views/fields/_group_table.html.haml +4 -5
- data/app/views/fields/_group_view.html.haml +4 -1
- data/app/views/fields/_sidebar_show.html.haml +5 -8
- data/app/views/fields/group.js.erb +3 -1
- data/app/views/layouts/application.html.haml +1 -1
- data/app/views/leads/_top_section.html.haml +4 -4
- data/app/views/leads/show.js.haml +1 -1
- data/app/views/leads/update.js.haml +1 -0
- data/app/views/opportunities/_top_section.html.haml +2 -2
- data/app/views/opportunities/show.js.haml +1 -1
- data/app/views/opportunities/update.js.haml +1 -0
- data/app/views/shared/_add_comment.html.haml +1 -1
- data/app/views/shared/_address.html.haml +1 -1
- data/app/views/tasks/_top_section.html.haml +4 -4
- data/config/database.yml +1 -2
- data/config/initializers/action_mailer.rb +1 -1
- data/config/initializers/custom_field_ransack_translations.rb +2 -2
- data/config/settings.default.yml +2 -1
- data/db/migrate/20230526212613_convert_to_active_storage.rb +1 -1
- data/lib/fat_free_crm/custom_fields.rb +1 -0
- data/lib/fat_free_crm/version.rb +1 -1
- metadata +7 -31
- data/public/avatars/User/2/large_rails.png +0 -0
- data/public/avatars/User/2/medium_rails.png +0 -0
- data/public/avatars/User/2/original_rails.png +0 -0
- data/public/avatars/User/2/small_rails.png +0 -0
- data/public/avatars/User/2/thumb_rails.png +0 -0
- data/public/avatars/User/3/large_rails.png +0 -0
- data/public/avatars/User/3/medium_rails.png +0 -0
- data/public/avatars/User/3/original_rails.png +0 -0
- data/public/avatars/User/3/small_rails.png +0 -0
- data/public/avatars/User/3/thumb_rails.png +0 -0
- data/public/avatars/User/4/large_rails.png +0 -0
- data/public/avatars/User/4/medium_rails.png +0 -0
- data/public/avatars/User/4/original_rails.png +0 -0
- data/public/avatars/User/4/small_rails.png +0 -0
- data/public/avatars/User/4/thumb_rails.png +0 -0
- data/public/avatars/User/6/large_rails.png +0 -0
- data/public/avatars/User/6/medium_rails.png +0 -0
- data/public/avatars/User/6/original_rails.png +0 -0
- data/public/avatars/User/6/small_rails.png +0 -0
- data/public/avatars/User/6/thumb_rails.png +0 -0
- data/public/avatars/User/7/large_rails.png +0 -0
- data/public/avatars/User/7/medium_rails.png +0 -0
- data/public/avatars/User/7/original_rails.png +0 -0
- data/public/avatars/User/7/small_rails.png +0 -0
- data/public/avatars/User/7/thumb_rails.png +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ed29c049db6b4f6c31a7c1f5a3a083c1ab92a87d1475ae0808777a8d5c87c9d
|
4
|
+
data.tar.gz: ea1c36a463eafd3c50db26e072a447e74a8d07095a9bd19d0149096f681dc36b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 31bf4871e24767881af295b9b686a62b5c7de6d56894d67f13af38d6721f8728b36d3a0535b05a9b7f14ebbc65d5648ee4dd9924ded07fcf3308fe04835ea428
|
7
|
+
data.tar.gz: 71240615f55f2e0ac3527223d240470d28c3ada768a5f7240c4aea3a1b976ced11e10a2d4335cd733971ba526719f06ff44b7a15ca41127bb63aea7dbc5dd4ab
|
@@ -9,8 +9,8 @@
|
|
9
9
|
@[0].toUpperCase() + @.substring(1)
|
10
10
|
|
11
11
|
window.crm =
|
12
|
-
EXPANDED: "&#
|
13
|
-
COLLAPSED: "&#
|
12
|
+
EXPANDED: "▽"
|
13
|
+
COLLAPSED: "▷"
|
14
14
|
searchRequest: null
|
15
15
|
autocompleter: null
|
16
16
|
base_url: ""
|
@@ -336,7 +336,10 @@
|
|
336
336
|
|
337
337
|
# Country dropdown needs special treatment ;-)
|
338
338
|
country = $("#" + from + "_attributes_country").select2("data")
|
339
|
-
|
339
|
+
if country.length == 1
|
340
|
+
country_dropdown = $("#" + to + "_attributes_country")
|
341
|
+
country_dropdown.val(country[0].id)
|
342
|
+
country_dropdown.trigger('change')
|
340
343
|
|
341
344
|
|
342
345
|
#----------------------------------------------------------------------------
|
@@ -15,6 +15,7 @@
|
|
15
15
|
$(this).select2
|
16
16
|
'width':'resolve'
|
17
17
|
placeholder: $(this).attr("placeholder")
|
18
|
+
allowClear: true
|
18
19
|
ajax:
|
19
20
|
url: $(this).data("url")
|
20
21
|
dataType: 'json'
|
@@ -22,16 +23,18 @@
|
|
22
23
|
$(this).select2
|
23
24
|
'width':'resolve'
|
24
25
|
placeholder: $(this).attr("placeholder")
|
26
|
+
allowClear: true
|
25
27
|
|
26
28
|
if $(this).prop("disabled") == true
|
27
29
|
$(this).next('.select2-container').disable()
|
28
|
-
$(this).next('.select2-container').hide()
|
30
|
+
$(this).next('.select2-container').hide()
|
29
31
|
|
30
32
|
$(".select2_tag").not(".select2-container, .select2-offscreen").each ->
|
31
33
|
$(this).select2
|
32
34
|
'width':'resolve'
|
33
35
|
placeholder: $(this).data("placeholder")
|
34
36
|
multiple: $(this).data("multiple")
|
37
|
+
allowClear: true
|
35
38
|
|
36
39
|
$(document).ready ->
|
37
40
|
crm.make_select2()
|
@@ -7,16 +7,16 @@
|
|
7
7
|
|
8
8
|
# The multiselect tag list has listeners to load/remove fieldsets related to tags
|
9
9
|
#----------------------------------------------------------------------------
|
10
|
-
$(document).on 'select2
|
10
|
+
$(document).on 'select2:select', "[name*='tag_list']", (event) ->
|
11
11
|
url = $(this).data('url')
|
12
12
|
asset_id = $(this).data('asset-id')
|
13
13
|
$.get(url, {
|
14
|
-
tag: event.
|
14
|
+
tag: event.params.data.text
|
15
15
|
asset_id: asset_id
|
16
16
|
collapsed: "no"
|
17
17
|
})
|
18
18
|
|
19
|
-
$(document).on 'select2
|
20
|
-
$("#field_groups div[data-tag='" + event.
|
19
|
+
$(document).on 'select2:unselect', "[name*='tag_list']", (event) ->
|
20
|
+
$("#field_groups div[data-tag='" + event.params.data.text + "']").remove()
|
21
21
|
|
22
22
|
) jQuery
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#------------------------------------------------------------------------------
|
2
|
+
(($) ->
|
3
|
+
|
4
|
+
# Ensure that any html5 required fields are unhidden when invalid
|
5
|
+
#----------------------------------------------------------------------------
|
6
|
+
$(document).on 'click', 'form.simple_form input:submit', (event) ->
|
7
|
+
form = this.closest('form')
|
8
|
+
invalidInputs = form.querySelectorAll(':invalid')
|
9
|
+
$(invalidInputs).each ->
|
10
|
+
$(this).closest('.field_group').show()
|
11
|
+
|
12
|
+
) jQuery
|
@@ -165,12 +165,12 @@ button.navbar-toggler:focus {
|
|
165
165
|
dl {
|
166
166
|
li {
|
167
167
|
width: 23%;
|
168
|
-
font-size:
|
168
|
+
font-size: 1rem;
|
169
169
|
dt {
|
170
|
-
font-size:
|
170
|
+
font-size: 0.75rem;
|
171
171
|
}
|
172
172
|
tt {
|
173
|
-
font-size:
|
173
|
+
font-size: 0.75rem;
|
174
174
|
}
|
175
175
|
}
|
176
176
|
}
|
@@ -33,7 +33,7 @@ class Admin::FieldsController < Admin::ApplicationController
|
|
33
33
|
# GET /fields/1/edit AJAX
|
34
34
|
#----------------------------------------------------------------------------
|
35
35
|
def edit
|
36
|
-
@field = Field.find(params[
|
36
|
+
@field = Field.find(params["id"])
|
37
37
|
respond_with(@field)
|
38
38
|
end
|
39
39
|
|
@@ -41,12 +41,12 @@ class Admin::FieldsController < Admin::ApplicationController
|
|
41
41
|
# POST /fields.xml AJAX
|
42
42
|
#----------------------------------------------------------------------------
|
43
43
|
def create
|
44
|
-
as = field_params[
|
44
|
+
as = field_params["as"]
|
45
|
+
klass= Field.lookup_class(as).safe_constantize
|
45
46
|
@field =
|
46
47
|
if as.match?(/pair/)
|
47
|
-
|
48
|
+
klass.create_pair("pair" => pair_params, "field" => field_params).first
|
48
49
|
elsif as.present?
|
49
|
-
klass = find_class(Field.lookup_class(as))
|
50
50
|
klass.create(field_params)
|
51
51
|
else
|
52
52
|
Field.new(field_params).tap(&:valid?)
|
@@ -59,10 +59,10 @@ class Admin::FieldsController < Admin::ApplicationController
|
|
59
59
|
# PUT /fields/1.xml AJAX
|
60
60
|
#----------------------------------------------------------------------------
|
61
61
|
def update
|
62
|
-
if field_params[
|
63
|
-
@field = CustomFieldPair.update_pair(
|
62
|
+
if field_params["as"].match?(/pair/)
|
63
|
+
@field = CustomFieldPair.update_pair("pair" => pair_params, "field" => field_params).first
|
64
64
|
else
|
65
|
-
@field = Field.find(params[
|
65
|
+
@field = Field.find(params["id"])
|
66
66
|
@field.update(field_params)
|
67
67
|
end
|
68
68
|
|
@@ -73,7 +73,7 @@ class Admin::FieldsController < Admin::ApplicationController
|
|
73
73
|
# DELETE /fields/1.xml HTML and AJAX
|
74
74
|
#----------------------------------------------------------------------------
|
75
75
|
def destroy
|
76
|
-
@field = Field.find(params[
|
76
|
+
@field = Field.find(params["id"])
|
77
77
|
@field.destroy
|
78
78
|
|
79
79
|
respond_with(@field)
|
@@ -82,7 +82,7 @@ class Admin::FieldsController < Admin::ApplicationController
|
|
82
82
|
# POST /fields/sort
|
83
83
|
#----------------------------------------------------------------------------
|
84
84
|
def sort
|
85
|
-
field_group_id = params[
|
85
|
+
field_group_id = params["field_group_id"].to_i
|
86
86
|
field_ids = params["fields_field_group_#{field_group_id}"] || []
|
87
87
|
|
88
88
|
field_ids.each_with_index do |id, index|
|
@@ -96,13 +96,12 @@ class Admin::FieldsController < Admin::ApplicationController
|
|
96
96
|
#----------------------------------------------------------------------------
|
97
97
|
def subform
|
98
98
|
field = field_params
|
99
|
-
as =
|
100
|
-
|
99
|
+
as = field_params["as"]
|
101
100
|
@field = if (id = field[:id]).present?
|
102
101
|
Field.find(id).tap { |f| f.as = as }
|
103
102
|
else
|
104
103
|
field_group_id = field[:field_group_id]
|
105
|
-
klass =
|
104
|
+
klass = Field.lookup_class(as).safe_constantize
|
106
105
|
klass.new(field_group_id: field_group_id, as: as)
|
107
106
|
end
|
108
107
|
|
@@ -114,7 +113,11 @@ class Admin::FieldsController < Admin::ApplicationController
|
|
114
113
|
protected
|
115
114
|
|
116
115
|
def field_params
|
117
|
-
params
|
116
|
+
params.require(:field).permit(:as, :collection_string, :disabled, :field_group_id, :hint, :label, :maxlength, :minlength, :name, :pair_id, :placeholder, :position, :required, :type, settings: {})
|
117
|
+
end
|
118
|
+
|
119
|
+
def pair_params
|
120
|
+
params.require(:pair).permit("0": [:hint, :required, :disabled, :id], "1": [:hint, :required, :disabled, :id])
|
118
121
|
end
|
119
122
|
|
120
123
|
def setup_current_tab
|
@@ -83,7 +83,7 @@ class EntitiesController < ApplicationController
|
|
83
83
|
#----------------------------------------------------------------------------
|
84
84
|
def field_group
|
85
85
|
if @tag = Tag.find_by_name(params[:tag].strip)
|
86
|
-
if @
|
86
|
+
if @field_groups = FieldGroup.where(tag_id: @tag.id, klass_name: klass.to_s).order(:label, :created_at)
|
87
87
|
@asset = klass.find_by_id(params[:asset_id]) || klass.new
|
88
88
|
render('fields/group') && return
|
89
89
|
end
|
@@ -9,13 +9,14 @@ class ListsController < ApplicationController
|
|
9
9
|
# POST /lists
|
10
10
|
#----------------------------------------------------------------------------
|
11
11
|
def create
|
12
|
-
|
12
|
+
list_attr = list_params.to_h
|
13
|
+
list_attr["user_id"] = current_user.id if params["is_global"] != "1"
|
13
14
|
|
14
15
|
# Find any existing list with the same name (case insensitive)
|
15
|
-
if @list = List.where("lower(name) = ?",
|
16
|
-
@list.update(
|
16
|
+
if @list = List.where("lower(name) = ?", list_attr[:name].downcase).where(user_id: list_attr[:user_id]).first
|
17
|
+
@list.update(list_attr)
|
17
18
|
else
|
18
|
-
@list = List.create(
|
19
|
+
@list = List.create(list_attr)
|
19
20
|
end
|
20
21
|
|
21
22
|
respond_with(@list)
|
@@ -36,7 +36,7 @@ module ApplicationHelper
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def subtitle_link(id, text, hidden)
|
39
|
-
link_to("<small>#{hidden ? '&#
|
39
|
+
link_to("<small>#{hidden ? '▷' : '▽'}</small> #{sanitize text}".html_safe,
|
40
40
|
url_for(controller: :home, action: :toggle, id: id),
|
41
41
|
remote: true,
|
42
42
|
onclick: "crm.flip_subtitle(this)")
|
@@ -99,7 +99,7 @@ module ApplicationHelper
|
|
99
99
|
|
100
100
|
#----------------------------------------------------------------------------
|
101
101
|
def arrow_for(id)
|
102
|
-
content_tag(:span, "&#
|
102
|
+
content_tag(:span, "▷".html_safe, id: "#{id}_arrow", class: :arrow)
|
103
103
|
end
|
104
104
|
|
105
105
|
#----------------------------------------------------------------------------
|
@@ -115,13 +115,13 @@ module ApplicationHelper
|
|
115
115
|
|
116
116
|
#----------------------------------------------------------------------------
|
117
117
|
def link_to_delete(record, options = {})
|
118
|
-
confirm = options[:confirm] ||
|
118
|
+
confirm = options[:confirm] || t(:confirm_delete, record.class.to_s.downcase)
|
119
119
|
|
120
120
|
link_to(t(:delete) + "!",
|
121
121
|
options[:url] || url_for(record),
|
122
122
|
method: :delete,
|
123
123
|
remote: true,
|
124
|
-
confirm: confirm)
|
124
|
+
data: { confirm: confirm })
|
125
125
|
end
|
126
126
|
|
127
127
|
#----------------------------------------------------------------------------
|
@@ -41,8 +41,10 @@ module OpportunitiesHelper
|
|
41
41
|
selected_campaign = Campaign.find_by_id(options[:selected])
|
42
42
|
campaigns = ([selected_campaign] + Campaign.my(current_user).order(:name).limit(25)).compact.uniq
|
43
43
|
collection_select :opportunity, :campaign_id, campaigns, :id, :name,
|
44
|
-
{ selected: options[:selected], prompt: t(:select_a_campaign) },
|
45
|
-
style: 'width:330px;', class: 'select2'
|
44
|
+
{ selected: options[:selected], prompt: t(:select_a_campaign), include_blank: true },
|
45
|
+
style: 'width:330px;', class: 'select2',
|
46
|
+
placeholder: t(:select_a_campaign),
|
47
|
+
"data-url": auto_complete_campaigns_path(format: 'json')
|
46
48
|
end
|
47
49
|
|
48
50
|
# Generates the inline revenue message for the opportunity list table.
|
data/app/helpers/users_helper.rb
CHANGED
@@ -25,7 +25,7 @@ module UsersHelper
|
|
25
25
|
user_options = user_options_for_select(users, myself)
|
26
26
|
select(asset, :assigned_to, user_options,
|
27
27
|
{ include_blank: t(:unassigned) },
|
28
|
-
style: 'width: 160px;',
|
28
|
+
style: 'width: 160px;', "data-allow-clear" => false,
|
29
29
|
class: 'select2')
|
30
30
|
end
|
31
31
|
|
@@ -35,8 +35,8 @@ class Campaign < ActiveRecord::Base
|
|
35
35
|
belongs_to :user, optional: true # TODO: Is this really optional?
|
36
36
|
belongs_to :assignee, class_name: "User", foreign_key: :assigned_to, optional: true # TODO: Is this really optional?
|
37
37
|
has_many :tasks, as: :asset, dependent: :destroy # , :order => 'created_at DESC'
|
38
|
-
has_many :leads, -> { order "id DESC" }
|
39
|
-
has_many :opportunities, -> { order "id DESC" }
|
38
|
+
has_many :leads, -> { order "id DESC" }
|
39
|
+
has_many :opportunities, -> { order "id DESC" }
|
40
40
|
has_many :emails, as: :mediator
|
41
41
|
|
42
42
|
serialize :subscribed_users, Array
|
@@ -12,10 +12,9 @@ class CustomFieldPair < CustomField
|
|
12
12
|
#------------------------------------------------------------------------------
|
13
13
|
def self.create_pair(params)
|
14
14
|
fields = params['field']
|
15
|
-
|
16
|
-
pair = params.delete('pair')
|
15
|
+
pair = params['pair']
|
17
16
|
base_params = fields.delete_if { |k, _v| !%w[field_group_id label as].include?(k) }
|
18
|
-
klass =
|
17
|
+
klass = Field.lookup_class(fields['as']).safe_constantize
|
19
18
|
field1 = klass.create(base_params.merge(pair['0']))
|
20
19
|
field2 = klass.create(base_params.merge(pair['1']).merge('pair_id' => field1.id, 'required' => field1.required, 'disabled' => field1.disabled))
|
21
20
|
[field1, field2]
|
@@ -25,19 +24,19 @@ class CustomFieldPair < CustomField
|
|
25
24
|
#------------------------------------------------------------------------------
|
26
25
|
def self.update_pair(params)
|
27
26
|
fields = params['field']
|
28
|
-
pair = params
|
27
|
+
pair = params['pair']
|
29
28
|
base_params = fields.delete_if { |k, _v| !%w[field_group_id label as].include?(k) }
|
30
|
-
field1 = CustomFieldPair.find(
|
29
|
+
field1 = CustomFieldPair.find(pair['0']['id'])
|
31
30
|
field1.update(base_params.merge(pair['0']))
|
32
31
|
field2 = field1.paired_with
|
33
32
|
field2.update(base_params.merge(pair['1']).merge('required' => field1.required, 'disabled' => field1.disabled))
|
34
33
|
[field1, field2]
|
35
34
|
end
|
36
35
|
|
37
|
-
# Returns the field that this field is paired with
|
36
|
+
# Returns the field that this field is paired with (bi-directional)
|
38
37
|
#------------------------------------------------------------------------------
|
39
38
|
def paired_with
|
40
|
-
pair ||
|
39
|
+
pair || self.class.find_by_id(pair_id)
|
41
40
|
end
|
42
41
|
|
43
42
|
ActiveSupport.run_load_hooks(:fat_free_crm_custom_field_pair, self)
|
data/app/models/fields/field.rb
CHANGED
@@ -34,7 +34,7 @@ class Field < ActiveRecord::Base
|
|
34
34
|
serialize :collection, Array
|
35
35
|
serialize :settings, HashWithIndifferentAccess
|
36
36
|
|
37
|
-
belongs_to :field_group, optional: true
|
37
|
+
belongs_to :field_group, optional: true
|
38
38
|
|
39
39
|
scope :core_fields, -> { where(type: 'CoreField') }
|
40
40
|
scope :custom_fields, -> { where("type != 'CoreField'") }
|
@@ -92,8 +92,6 @@ class Field < ActiveRecord::Base
|
|
92
92
|
|
93
93
|
def render(value)
|
94
94
|
case as
|
95
|
-
when 'checkbox'
|
96
|
-
value.to_s == '0' ? "no" : "yes"
|
97
95
|
when 'date'
|
98
96
|
value&.strftime(I18n.t("date.formats.mmddyy"))
|
99
97
|
when 'datetime'
|
data/app/models/list.rb
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
.section
|
3
3
|
%table
|
4
4
|
%tr
|
5
|
-
%td(colspan="5")
|
5
|
+
%td{class: (@account.errors['name'].present? ? 'fieldWithErrors' : nil)}(colspan="5")
|
6
6
|
.label.top.req #{t :name}:
|
7
|
-
= f.text_field :name, autofocus: true, style: "width:500px"
|
7
|
+
= f.text_field :name, autofocus: true, style: "width:500px", required: "required"
|
8
8
|
%tr
|
9
9
|
%td
|
10
10
|
.label #{t :assigned_to}:
|
@@ -12,7 +12,7 @@
|
|
12
12
|
%td= spacer
|
13
13
|
%td
|
14
14
|
.label #{t :category}:
|
15
|
-
= f.select :category, Setting.unroll(:account_category), { selected: (@account.category || "other").to_sym, include_blank: t(:other) }, { style: "width:160px", class: 'select2' }
|
15
|
+
= f.select :category, Setting.unroll(:account_category), { selected: (@account.category || "other").to_sym, include_blank: t(:other) }, { style: "width:160px", class: 'select2', placeholder: t(:other) }
|
16
16
|
%td= spacer
|
17
17
|
%td
|
18
18
|
.label #{t :rating}:
|
@@ -1,5 +1,5 @@
|
|
1
1
|
- entity_name = controller.controller_name.singularize.underscore #account
|
2
2
|
- @entity = instance_variable_get("@#{entity_name}")
|
3
3
|
|
4
|
-
$('#main').html('#{ j (render template: "#{entity_name.pluralize}/show
|
4
|
+
$('#main').html('#{ j (render template: "#{entity_name.pluralize}/show", formats: [:html], entity_name => @entity) }');
|
5
5
|
= raw generate_js_for_popups(@entity, :tasks, :contacts, :opportunities)
|
@@ -7,6 +7,7 @@
|
|
7
7
|
crm.flip_form('edit_#{entity_name}');
|
8
8
|
crm.set_title('edit_#{entity_name}', '#{j @entity.name}');
|
9
9
|
= refresh_sidebar(:show)
|
10
|
+
$('#summary').html('#{ j (render partial: "#{entity_name.pluralize}/sidebar_show", entity_name => @entity) }');
|
10
11
|
- else
|
11
12
|
$('##{id}').replaceWith('#{ j render(partial: entity_name, collection: [ @entity ]) }');
|
12
13
|
$('##{id}').effect("highlight", { duration:1500 });
|
@@ -1,6 +1,9 @@
|
|
1
1
|
%div
|
2
2
|
.label.top.req
|
3
3
|
= "Select Options (pipe separated):"
|
4
|
-
= f.text_field :collection_string, class: 'field_collection_string', size: 78
|
4
|
+
= f.text_field :collection_string, class: 'field_collection_string', size: 78, placeholder: "Option 1|Option 2|Option 3"
|
5
5
|
|
6
6
|
= render partial: 'admin/custom_fields/base_field', locals: {f: f}
|
7
|
+
|
8
|
+
- if f.object.new_record?
|
9
|
+
.info2 After saving, you must restart all instances of the Rails server to apply column serialization.
|
@@ -2,9 +2,9 @@
|
|
2
2
|
.section
|
3
3
|
%table
|
4
4
|
%tr
|
5
|
-
%td(colspan="5")
|
5
|
+
%td{class: (@campaign.errors['name'].present? ? 'fieldWithErrors' : nil)}(colspan="5")
|
6
6
|
.label.top.req #{t :name}:
|
7
|
-
= f.text_field :name, autofocus: true, style: "width:500px"
|
7
|
+
= f.text_field :name, autofocus: true, style: "width:500px", required: "required"
|
8
8
|
%tr
|
9
9
|
%td
|
10
10
|
.label #{t :start_date}:
|
@@ -1,5 +1,5 @@
|
|
1
1
|
- entity_name = controller.controller_name.singularize.underscore #account
|
2
2
|
- @entity = instance_variable_get("@#{entity_name}")
|
3
3
|
|
4
|
-
$('#main').html('#{ j (render template: "#{entity_name.pluralize}/show
|
4
|
+
$('#main').html('#{ j (render template: "#{entity_name.pluralize}/show", formats: [:html], entity_name => @entity) }');
|
5
5
|
= raw generate_js_for_popups(@entity, :tasks, :leads, :opportunities)
|
@@ -7,6 +7,7 @@
|
|
7
7
|
crm.flip_form('edit_#{entity_name}');
|
8
8
|
crm.set_title('edit_#{entity_name}', '#{h @entity.name}');
|
9
9
|
= refresh_sidebar(:show)
|
10
|
+
$('#summary').html('#{ j (render partial: "#{entity_name.pluralize}/sidebar_show", entity_name => @entity) }');
|
10
11
|
- else
|
11
12
|
$('##{id}').replaceWith('#{ j render(partial: entity_name, collection: [ @entity ]) }');
|
12
13
|
$('##{id}').effect("highlight", { duration:1500 });
|
@@ -3,13 +3,13 @@
|
|
3
3
|
.section
|
4
4
|
%table
|
5
5
|
%tr
|
6
|
-
%td
|
7
|
-
.label.top
|
8
|
-
= f.text_field :first_name, autofocus: true
|
6
|
+
%td{class: (@contact.errors['first_name'].present? ? 'fieldWithErrors' : nil)}
|
7
|
+
.label.top{ class: "#{Setting.require_first_names ? 'req' : nil}" } #{t :first_name}:
|
8
|
+
= f.text_field :first_name, autofocus: true, required: (Setting.require_first_names ? "required" : nil)
|
9
9
|
%td= spacer
|
10
|
-
%td
|
10
|
+
%td{class: (@contact.errors['last_name'].present? ? 'fieldWithErrors' : nil)}
|
11
11
|
.label.top{ class: "#{Setting.require_last_names ? 'req' : nil}" } #{t :last_name}:
|
12
|
-
= f.text_field :last_name
|
12
|
+
= f.text_field :last_name, required: (Setting.require_last_names ? "required" : nil)
|
13
13
|
%tr
|
14
14
|
%td
|
15
15
|
.label #{t :email}:
|
@@ -1,5 +1,4 @@
|
|
1
|
-
- entity_name = controller.controller_name.singularize.underscore
|
1
|
+
- entity_name = controller.controller_name.singularize.underscore
|
2
2
|
- @entity = instance_variable_get("@#{entity_name}")
|
3
|
-
|
4
|
-
$('#main').html('#{ j (render template: "#{entity_name.pluralize}/show.html", entity_name => @entity) }');
|
3
|
+
$('#main').html('#{ j (render template: "#{entity_name.pluralize}/show", formats: [:html], entity_name => @entity) }');
|
5
4
|
= raw generate_js_for_popups(@entity, :tasks, :opportunities)
|
@@ -8,6 +8,7 @@
|
|
8
8
|
crm.flip_form('edit_#{entity_name}');
|
9
9
|
crm.set_title('edit_#{entity_name}', '#{h @entity.full_name}');
|
10
10
|
= refresh_sidebar(:show)
|
11
|
+
$('#summary').html('#{ j (render partial: "#{entity_name.pluralize}/sidebar_show", entity_name => @entity) }');
|
11
12
|
- else
|
12
13
|
$('##{id}').replaceWith('#{ j render(partial: entity_name, collection: [ @entity ]) }');
|
13
14
|
$('##{id}').effect("highlight", { duration:1500 });
|
@@ -23,10 +23,9 @@
|
|
23
23
|
.label= t(:password)
|
24
24
|
= f.input_field :password
|
25
25
|
|
26
|
-
|
26
|
+
.section
|
27
27
|
= f.input :remember_me, as: :boolean, inline_label: t('remember_me')
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
= t(:or)
|
28
|
+
= f.submit t(:login), class: 'btn btn-primary'
|
29
|
+
|
30
|
+
.section
|
32
31
|
= link_to t(:forgot_password) + '?', new_password_path(resource_name)
|
@@ -1,6 +1,9 @@
|
|
1
1
|
- if field_group.name != 'custom_fields'
|
2
|
-
|
3
|
-
-
|
2
|
+
- # Ensure field groups containing validation errors are expanded
|
3
|
+
- required_field_names = field_group.fields.select(&:required?).map(&:name)
|
4
|
+
- fields_with_errors = f.object.errors.map{|e| e.attribute.to_s}
|
5
|
+
- force_open = (required_field_names & fields_with_errors).any?
|
6
|
+
- collapsed = session[field_group.key].nil? && !force_open
|
4
7
|
%div{ id: "#{field_group.key}_container", :"data-tag" => field_group.tag.try(:name) }
|
5
8
|
= subtitle field_group.key, collapsed, t(field_group.name, default: field_group.label)
|
6
9
|
.section
|
@@ -2,12 +2,11 @@
|
|
2
2
|
- field_group.fields.without_pairs.in_groups_of(2, false) do |group|
|
3
3
|
%tr
|
4
4
|
- group.each_with_index do |field, i|
|
5
|
-
%td
|
5
|
+
%td{class: (f.object.errors[field.name].present? ? 'fieldWithErrors' : nil)}
|
6
6
|
- if field.hint.present?
|
7
7
|
= image_tag "info_tiny.png", title: field.hint, class: "tooltip-icon"
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
= f.input field.name, field.input_options.merge(checked: checked)
|
8
|
+
.label.top{class: (field.required? ? 'req': nil)}
|
9
|
+
= "#{field.label}:"
|
10
|
+
= f.input_field field.name, field.input_options
|
12
11
|
- if i == 0
|
13
12
|
%td= spacer
|
@@ -4,7 +4,10 @@
|
|
4
4
|
%tr
|
5
5
|
- group.each do |field|
|
6
6
|
= col(field.label, (i == groups.size - 1) ? :last : nil) do
|
7
|
-
|
7
|
+
- if field.as == "text"
|
8
|
+
= simple_format(field.render_value(entity))
|
9
|
+
- else
|
10
|
+
= field.render_value(entity)
|
8
11
|
- if group.size == 1
|
9
12
|
%th.last
|
10
13
|
%td.last
|
@@ -1,9 +1,6 @@
|
|
1
1
|
- asset.field_groups.each do |field_group|
|
2
|
-
-
|
3
|
-
- if
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
- fg.each do |field|
|
8
|
-
- if (value = field.render_value(asset)).present?
|
9
|
-
== #{field.label}:<br /> <b>#{truncate(value, length: 35)}</b><br />
|
2
|
+
- fields = field_group.fields.without_pairs
|
3
|
+
- if fields.select{|f| asset.send(f.name).present? }.any?
|
4
|
+
- unless field_group.name == 'custom_fields'
|
5
|
+
.caption #{field_group.label_i18n}
|
6
|
+
= render("fields/group_view", fields: fields, entity: asset) unless fields.nil?
|
@@ -1,3 +1,5 @@
|
|
1
1
|
<%= simple_fields_for(@asset) do |f| %>
|
2
|
-
|
2
|
+
<% @field_groups.each do |field_group| %>
|
3
|
+
$('#field_groups').append('<%= j render(partial: 'fields/group', locals: {f: f, field_group: field_group, fields: field_group.fields}) %>')
|
4
|
+
<% end %>
|
3
5
|
<% end %>
|
@@ -42,7 +42,7 @@
|
|
42
42
|
= render "layouts/footer"
|
43
43
|
|
44
44
|
%script{type: "text/javascript"}
|
45
|
-
= "crm.base_url = '#{Setting.base_url}';" unless Setting.base_url.blank?
|
45
|
+
= "crm.base_url = '#{h Setting.base_url}';".html_safe unless Setting.base_url.blank?
|
46
46
|
= get_browser_timezone_offset
|
47
47
|
= content_for :javascript_epilogue
|
48
48
|
= hook(:javascript_epilogue, self)
|
@@ -3,13 +3,13 @@
|
|
3
3
|
.section
|
4
4
|
%table
|
5
5
|
%tr
|
6
|
-
%td
|
6
|
+
%td{ class: (@lead.errors['first_name'].present? ? 'fieldWithErrors' : nil)}
|
7
7
|
.label.top{ class: "#{Setting.require_first_names ? 'req' : nil}" } #{t :first_name}:
|
8
|
-
= f.text_field :first_name, autofocus: true
|
8
|
+
= f.text_field :first_name, autofocus: true, required: (Setting.require_first_names ? "required" : nil)
|
9
9
|
%td= spacer
|
10
|
-
%td
|
10
|
+
%td{ class: (@lead.errors['last_name'].present? ? 'fieldWithErrors' : nil)}
|
11
11
|
.label.top{ class: "#{Setting.require_last_names ? 'req' : nil}" } #{t :last_name}:
|
12
|
-
= f.text_field :last_name
|
12
|
+
= f.text_field :last_name, required: (Setting.require_last_names ? "required" : nil)
|
13
13
|
%tr
|
14
14
|
%td
|
15
15
|
.label #{t :email}:
|
@@ -1,5 +1,5 @@
|
|
1
1
|
- entity_name = controller.controller_name.singularize.underscore #account
|
2
2
|
- @entity = instance_variable_get("@#{entity_name}")
|
3
3
|
|
4
|
-
$('#main').html('#{ j (render template: "#{entity_name.pluralize}/show
|
4
|
+
$('#main').html('#{ j (render template: "#{entity_name.pluralize}/show", formats: [:html], entity_name => @entity) }');
|
5
5
|
= raw generate_js_for_popups(@entity, :tasks)
|
@@ -7,6 +7,7 @@
|
|
7
7
|
crm.flip_form('edit_#{entity_name}');
|
8
8
|
crm.set_title('edit_#{entity_name}', '#{h @entity.full_name}');
|
9
9
|
= refresh_sidebar(:show)
|
10
|
+
$('#summary').html('#{ j (render partial: "#{entity_name.pluralize}/sidebar_show", entity_name => @entity) }');
|
10
11
|
- else
|
11
12
|
$('##{id}').replaceWith('#{ j render(partial: entity_name, collection: [ @entity ]) }');
|
12
13
|
$('##{id}').effect("highlight", { duration:1500 });
|
@@ -2,9 +2,9 @@
|
|
2
2
|
.section
|
3
3
|
%table
|
4
4
|
%tr
|
5
|
-
%td
|
5
|
+
%td{class: (@opportunity.errors['name'].present? ? 'fieldWithErrors' : nil)}
|
6
6
|
.label.req.top #{t :name}:
|
7
|
-
= f.text_field :name, autofocus: true, style: "width:325px"
|
7
|
+
= f.text_field :name, autofocus: true, style: "width:325px", required: "required"
|
8
8
|
%td= spacer
|
9
9
|
%td
|
10
10
|
.label.req.top #{t :stage}:
|
@@ -1,5 +1,5 @@
|
|
1
1
|
- entity_name = controller.controller_name.singularize.underscore #account
|
2
2
|
- @entity = instance_variable_get("@#{entity_name}")
|
3
3
|
|
4
|
-
$('#main').html('#{ j (render template: "#{entity_name.pluralize}/show
|
4
|
+
$('#main').html('#{ j (render template: "#{entity_name.pluralize}/show", formats: [:html], entity_name => @entity) }');
|
5
5
|
= raw generate_js_for_popups(@entity, :tasks, :contacts)
|
@@ -7,6 +7,7 @@
|
|
7
7
|
crm.flip_form('edit_#{entity_name}');
|
8
8
|
crm.set_title('edit_#{entity_name}', '#{h @entity.name}');
|
9
9
|
= refresh_sidebar(:show)
|
10
|
+
$('#summary').html('#{ j (render partial: "#{entity_name.pluralize}/sidebar_show", entity_name => @entity) }');
|
10
11
|
- else
|
11
12
|
$('##{id}').replaceWith('#{ j render(partial: entity_name, collection: [ @entity ]) }');
|
12
13
|
$('##{id}').effect("highlight", { duration:1500 });
|
@@ -41,4 +41,4 @@
|
|
41
41
|
= address_field(a, :zipcode, "width:80px;")
|
42
42
|
%td= spacer
|
43
43
|
%td
|
44
|
-
= a.country_select(:country, priority_countries: priority_countries, include_blank:
|
44
|
+
= a.country_select(:country, {priority_countries: priority_countries, include_blank: true}, {data: { placeholder: t(:select_a_country)}, class: 'select2'})
|
@@ -3,10 +3,10 @@
|
|
3
3
|
%tr
|
4
4
|
%td(colspan="5")
|
5
5
|
.label.top.req #{t :name}:
|
6
|
-
= f.text_field :name, autofocus: true, style: "width:500px"
|
6
|
+
= f.text_field :name, autofocus: true, style: "width:500px", required: "required"
|
7
7
|
%tr
|
8
8
|
%td
|
9
|
-
.label
|
9
|
+
.label #{t :due}:
|
10
10
|
- bucket = (params[:bucket].blank? ? @task.bucket : params[:bucket]) || "due_asap"
|
11
11
|
- with_time = Setting.task_calendar_with_time
|
12
12
|
- if @task.bucket != "specific_time"
|
@@ -18,11 +18,11 @@
|
|
18
18
|
= f.text_field :calendar, value: f.object.due_at.strftime(fmt), style: "width:160px;", autocomplete: :off, class: (with_time ? 'datetime' : 'date')
|
19
19
|
%td= spacer
|
20
20
|
%td
|
21
|
-
.label
|
21
|
+
.label #{t :assign_to}:
|
22
22
|
= user_select(:task, all_users, current_user)
|
23
23
|
%td= spacer
|
24
24
|
%td
|
25
|
-
.label
|
25
|
+
.label #{t :category}:
|
26
26
|
= f.select :category, @category, { selected: @task.category.blank? ? nil : @task.category.to_sym, include_blank: t(:select_blank) }, { style: "width:160px", class: 'select2' }
|
27
27
|
|
28
28
|
- if Setting.background_info && Setting.background_info.include?(:task)
|
data/config/database.yml
CHANGED
@@ -3,7 +3,7 @@ development: &development
|
|
3
3
|
adapter: postgresql
|
4
4
|
database: fat_free_crm_development
|
5
5
|
username: postgres
|
6
|
-
password:
|
6
|
+
password: postgres
|
7
7
|
host: localhost
|
8
8
|
port: 5432
|
9
9
|
schema_search_path: public
|
@@ -23,4 +23,3 @@ production:
|
|
23
23
|
staging:
|
24
24
|
<<: *development
|
25
25
|
database: fat_free_crm_staging
|
26
|
-
|
@@ -6,8 +6,8 @@
|
|
6
6
|
# See MIT-LICENSE file or http://www.opensource.org/licenses/mit-license.php
|
7
7
|
#------------------------------------------------------------------------------
|
8
8
|
# Load field names for custom fields, for Ransack search
|
9
|
-
require '
|
10
|
-
if Setting.database_and_table_exists?
|
9
|
+
require 'setting'
|
10
|
+
if Setting.database_and_table_exists? && ActiveRecord::Base.connection.table_exists?(:custom_fields)
|
11
11
|
Rails.application.config.after_initialize do
|
12
12
|
I18n.backend.load_translations
|
13
13
|
|
data/config/settings.default.yml
CHANGED
@@ -192,7 +192,8 @@
|
|
192
192
|
#------------------------------------------------------------------------------
|
193
193
|
# Specify which countries (if any) should appear at the top of country pickers
|
194
194
|
# priority_countries:
|
195
|
-
# -
|
195
|
+
# - AU
|
196
|
+
# - BF
|
196
197
|
|
197
198
|
# Main and Admin Tabs
|
198
199
|
#------------------------------------------------------------------------------
|
@@ -16,7 +16,7 @@ class ConvertToActiveStorage < ActiveRecord::Migration[5.2]
|
|
16
16
|
end
|
17
17
|
|
18
18
|
ActiveRecord::Base.connection.raw_connection.then do |conn|
|
19
|
-
if conn.is_a?(PG::Connection)
|
19
|
+
if conn.is_a?(::PG::Connection)
|
20
20
|
conn.prepare('active_storage_blobs', <<-SQL)
|
21
21
|
INSERT INTO active_storage_blobs (
|
22
22
|
key, filename, content_type, metadata, byte_size, checksum, created_at
|
@@ -8,6 +8,7 @@
|
|
8
8
|
|
9
9
|
#
|
10
10
|
# Register CustomFields when Field class is loaded
|
11
|
+
|
11
12
|
ActiveSupport.on_load(:fat_free_crm_field) do # self == Field
|
12
13
|
register(as: 'date_pair', klass: 'CustomFieldDatePair', type: 'date')
|
13
14
|
register(as: 'datetime_pair', klass: 'CustomFieldDatetimePair', type: 'timestamp')
|
data/lib/fat_free_crm/version.rb
CHANGED
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fat_free_crm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.22.
|
4
|
+
version: 0.22.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Dvorkin
|
8
8
|
- Stephen Kenworthy
|
9
9
|
- Daniel O'Connor
|
10
|
-
autorequire:
|
10
|
+
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2024-
|
13
|
+
date: 2024-08-28 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rails
|
@@ -553,6 +553,7 @@ files:
|
|
553
553
|
- app/assets/javascripts/crm_sortable.js.coffee
|
554
554
|
- app/assets/javascripts/crm_tags.js.coffee
|
555
555
|
- app/assets/javascripts/crm_textarea_autocomplete.js.coffee
|
556
|
+
- app/assets/javascripts/crm_validations.js.coffee
|
556
557
|
- app/assets/javascripts/datepicker.js.coffee
|
557
558
|
- app/assets/javascripts/format_buttons.js.coffee
|
558
559
|
- app/assets/javascripts/lists.js.coffee
|
@@ -1217,31 +1218,6 @@ files:
|
|
1217
1218
|
- public/404.html
|
1218
1219
|
- public/422.html
|
1219
1220
|
- public/500.html
|
1220
|
-
- public/avatars/User/2/large_rails.png
|
1221
|
-
- public/avatars/User/2/medium_rails.png
|
1222
|
-
- public/avatars/User/2/original_rails.png
|
1223
|
-
- public/avatars/User/2/small_rails.png
|
1224
|
-
- public/avatars/User/2/thumb_rails.png
|
1225
|
-
- public/avatars/User/3/large_rails.png
|
1226
|
-
- public/avatars/User/3/medium_rails.png
|
1227
|
-
- public/avatars/User/3/original_rails.png
|
1228
|
-
- public/avatars/User/3/small_rails.png
|
1229
|
-
- public/avatars/User/3/thumb_rails.png
|
1230
|
-
- public/avatars/User/4/large_rails.png
|
1231
|
-
- public/avatars/User/4/medium_rails.png
|
1232
|
-
- public/avatars/User/4/original_rails.png
|
1233
|
-
- public/avatars/User/4/small_rails.png
|
1234
|
-
- public/avatars/User/4/thumb_rails.png
|
1235
|
-
- public/avatars/User/6/large_rails.png
|
1236
|
-
- public/avatars/User/6/medium_rails.png
|
1237
|
-
- public/avatars/User/6/original_rails.png
|
1238
|
-
- public/avatars/User/6/small_rails.png
|
1239
|
-
- public/avatars/User/6/thumb_rails.png
|
1240
|
-
- public/avatars/User/7/large_rails.png
|
1241
|
-
- public/avatars/User/7/medium_rails.png
|
1242
|
-
- public/avatars/User/7/original_rails.png
|
1243
|
-
- public/avatars/User/7/small_rails.png
|
1244
|
-
- public/avatars/User/7/thumb_rails.png
|
1245
1221
|
- public/favicon.ico
|
1246
1222
|
- public/robots.txt
|
1247
1223
|
- vendor/assets/images/calendar_date_select/calendar.gif
|
@@ -1338,7 +1314,7 @@ homepage: http://fatfreecrm.com
|
|
1338
1314
|
licenses:
|
1339
1315
|
- MIT
|
1340
1316
|
metadata: {}
|
1341
|
-
post_install_message:
|
1317
|
+
post_install_message:
|
1342
1318
|
rdoc_options: []
|
1343
1319
|
require_paths:
|
1344
1320
|
- lib
|
@@ -1353,8 +1329,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
1353
1329
|
- !ruby/object:Gem::Version
|
1354
1330
|
version: '0'
|
1355
1331
|
requirements: []
|
1356
|
-
rubygems_version: 3.
|
1357
|
-
signing_key:
|
1332
|
+
rubygems_version: 3.5.9
|
1333
|
+
signing_key:
|
1358
1334
|
specification_version: 4
|
1359
1335
|
summary: Fat Free CRM
|
1360
1336
|
test_files: []
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|