base-project 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +37 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +196 -0
- data/README.md +43 -0
- data/Rakefile +6 -0
- data/base-project.gemspec +38 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/base/project.rb +7 -0
- data/lib/base/project/app/controllers/concerns/base_crud_controller.rb +104 -0
- data/lib/base/project/app/helpers/base_helper.rb +128 -0
- data/lib/base/project/app/helpers/button_helper.rb +101 -0
- data/lib/base/project/app/helpers/localizable_helper.rb +51 -0
- data/lib/base/project/app/helpers/panel_helper.rb +8 -0
- data/lib/base/project/app/models/concerns/addressable.rb +93 -0
- data/lib/base/project/app/models/concerns/base_user_concern.rb +39 -0
- data/lib/base/project/app/models/concerns/image_concern.rb +16 -0
- data/lib/base/project/app/models/concerns/localizable.rb +50 -0
- data/lib/base/project/app/models/concerns/scope_concern.rb +14 -0
- data/lib/base/project/app/validators/image_validator.rb +14 -0
- data/lib/base/project/app/views/_flash_messages.html.erb +12 -0
- data/lib/base/project/app/views/_menu.html.erb +16 -0
- data/lib/base/project/app/views/forms/_buttons.html.erb +4 -0
- data/lib/base/project/app/views/indexes/_buttons.html.erb +11 -0
- data/lib/base/project/app/views/indexes/_header.html.erb +10 -0
- data/lib/base/project/app/views/menu/_header_menu.html.erb +25 -0
- data/lib/base/project/app/views/menu/_sidebar.html.erb +44 -0
- data/lib/base/project/app/views/panels/_panel.html.erb +15 -0
- data/lib/base/project/app/views/versions/_model_dates.html.erb +12 -0
- data/lib/base/project/config/locales/error_pages.pt-BR.yml +9 -0
- data/lib/base/project/config/locales/pt-BR.yml +269 -0
- data/lib/base/project/config/locales/simple_form.pt-BR.yml +76 -0
- data/lib/base/project/lib/console_say.rb +17 -0
- data/lib/base/project/lib/string_sanitizer.rb +54 -0
- data/lib/base/project/version.rb +5 -0
- metadata +277 -0
data/lib/base/project.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
|
2
|
+
module Base::Project::App::Controllers::Concerns::BaseCrudController
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
@serializer_class ||= nil
|
6
|
+
|
7
|
+
included do
|
8
|
+
before_action :check_default_permission
|
9
|
+
before_action :set_model, only: %i[show update destroy]
|
10
|
+
|
11
|
+
def index
|
12
|
+
@models = apply_scopes(model_source)
|
13
|
+
|
14
|
+
respond_with(@models) do |format|
|
15
|
+
format.html { flash.now[:info] = t('simple_form.results', count: @models.size) }
|
16
|
+
format.json { render json: @models, serializer_each: @serializer_class }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def show
|
21
|
+
respond_with(@model) do |format|
|
22
|
+
format.html {}
|
23
|
+
format.json { render json: @model, serializer: @serializer_class }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def new
|
28
|
+
@model = model_source.new
|
29
|
+
|
30
|
+
respond_with(@model) do |format|
|
31
|
+
format.html {}
|
32
|
+
format.json { render json: @model, serializer: @serializer_class }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def create
|
37
|
+
@model = model_source.new(model_params)
|
38
|
+
is_save = @model.save
|
39
|
+
|
40
|
+
respond_with(@model) do |format|
|
41
|
+
format.html do
|
42
|
+
if is_save
|
43
|
+
redirect_to created_path, notice: t('simple_form.added', klass: model_name)
|
44
|
+
else
|
45
|
+
flash.now[:error] = t('simple_form.error_notification.default_message')
|
46
|
+
render action: 'new'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
format.json do
|
51
|
+
if is_save
|
52
|
+
head :created
|
53
|
+
else
|
54
|
+
render json: @model.errors, status: :unprocessable_entity
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def update
|
61
|
+
is_save = @model.update(update_model_params)
|
62
|
+
|
63
|
+
respond_with(@model) do |format|
|
64
|
+
format.html do
|
65
|
+
if is_save
|
66
|
+
flash.now[:success] = t('simple_form.updated', klass: model_name)
|
67
|
+
else
|
68
|
+
flash.now[:error] = t('simple_form.error_notification.default_message')
|
69
|
+
render action: 'show'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
format.json do
|
74
|
+
if is_save
|
75
|
+
head :no_content
|
76
|
+
else
|
77
|
+
render json: @model.errors, status: :unprocessable_entity
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def destroy
|
84
|
+
@model.destroy
|
85
|
+
|
86
|
+
respond_to do |format|
|
87
|
+
format.html { redirect_to destroyed_path, notice: t('simple_form.removed', klass: model_name) }
|
88
|
+
format.json { head :no_content }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def set_model
|
95
|
+
@model = model_source.find(params[:id])
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
module ClassMethods
|
100
|
+
def serializer(klass)
|
101
|
+
@serializer_class = klass
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
|
2
|
+
module Base::Project::App::Helpers::BaseHelper
|
3
|
+
def bootstrap_class_for(flash_type)
|
4
|
+
case flash_type.to_sym
|
5
|
+
when :success
|
6
|
+
'alert-success' # Green
|
7
|
+
when :error
|
8
|
+
'alert-danger' # Red
|
9
|
+
when :alert
|
10
|
+
'alert-warning' # Yellow
|
11
|
+
when :notice
|
12
|
+
'alert-success' # Blue
|
13
|
+
when :info
|
14
|
+
'alert-info' # Blue
|
15
|
+
else
|
16
|
+
flash_type.to_s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def show_flash(type, message)
|
21
|
+
klass = bootstrap_class_for(type)
|
22
|
+
|
23
|
+
content_tag(:div, class: "alert alert-dismissible fade in #{klass} flash-message", role: 'alert') do
|
24
|
+
concat(button_tag(type: 'button', class: 'close', data: { dismiss: 'alert', label: 'Close' }) do
|
25
|
+
content_tag(:span, 'x', aria: { hidden: true })
|
26
|
+
end)
|
27
|
+
|
28
|
+
concat(message)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def show_flashes(flash)
|
33
|
+
flash.each do |type, message|
|
34
|
+
if message.is_a?(Array)
|
35
|
+
message.each do |text|
|
36
|
+
show_flash(type, text)
|
37
|
+
end
|
38
|
+
|
39
|
+
else
|
40
|
+
show_flash(type, message)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def page_title(text)
|
46
|
+
title = t('application_name')
|
47
|
+
text.blank? ? title : "#{title} - #{text}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def nested_text_field(options = {})
|
51
|
+
text_field_tag(options[:field_name], options[:value], type: :text, class: "form-control #{options[:class]}")
|
52
|
+
end
|
53
|
+
|
54
|
+
def nested_select(options = {})
|
55
|
+
select_tag(options[:field_name], options[:value], class: "form-control tag-select #{options[:class]}")
|
56
|
+
end
|
57
|
+
|
58
|
+
def menu_item(path, icon, text, additional = {})
|
59
|
+
options = { method: :get, remote: false }.merge(additional)
|
60
|
+
|
61
|
+
content_tag(:li) do
|
62
|
+
link_to path, method: options[:method], remote: options[:remote] do
|
63
|
+
concat(content_tag(:i, '', class: "#{icon.split('-').first} #{icon}"))
|
64
|
+
concat(text)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def log_out_menu_item(path, icon, text)
|
70
|
+
menu_item(path, icon, text, method: :delete)
|
71
|
+
end
|
72
|
+
|
73
|
+
def icon_link(path, icon, text)
|
74
|
+
link_to(path) do
|
75
|
+
concat content_tag(:span, '', class: icon)
|
76
|
+
concat ' '
|
77
|
+
concat text
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def brand_link
|
82
|
+
link_to(t('application_name'), authenticated_user_path, class: 'navbar-brand')
|
83
|
+
end
|
84
|
+
|
85
|
+
def link_image(image, options = {})
|
86
|
+
link_to(image.url(:big), target: '_blank', title: options[:title]) do
|
87
|
+
image_tag(image.url(:thumb), class: 'text-center img-circle', title: options[:title])
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def edit_link(destination)
|
92
|
+
link_to(content_tag(:span, '', class: 'fa fa-pencil'), destination, class: 'pull-right')
|
93
|
+
end
|
94
|
+
|
95
|
+
def cancel_link(destination)
|
96
|
+
round_icon_link(path: destination, type: :button, icon: 'fa-close', button: 'btn-default', class: 'pull-right m-l-5', title: t('wizard.button.cancel'))
|
97
|
+
end
|
98
|
+
|
99
|
+
def submit_link
|
100
|
+
round_icon_button type: :submit, icon: 'fa-save', button: 'btn-success', class: 'pull-right', title: t('wizard.button.finish')
|
101
|
+
end
|
102
|
+
|
103
|
+
def round_icon_link(params)
|
104
|
+
params[:method] ||= :get
|
105
|
+
params[:remote] ||= false
|
106
|
+
data = {}
|
107
|
+
|
108
|
+
data[:confirm] = params[:confirm] if params[:confirm]
|
109
|
+
|
110
|
+
link_to(params[:path], method: params[:method], data: data, remote: params[:remote]) do
|
111
|
+
round_icon_button(params)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def text_icon_link(params)
|
116
|
+
params[:shape] = ''
|
117
|
+
params[:text] = params[:title]
|
118
|
+
params[:method] ||= :get
|
119
|
+
params[:remote] ||= false
|
120
|
+
data = {}
|
121
|
+
|
122
|
+
data[:confirm] = params[:confirm] if params[:confirm]
|
123
|
+
|
124
|
+
link_to(params[:path], method: params[:method], data: data, remote: params[:remote]) do
|
125
|
+
text_icon_button(params)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
|
2
|
+
module Base::Project::App::Helpers::ButtonHelper
|
3
|
+
def command_button( path, row_id, message, additional = { } )
|
4
|
+
options = { remote: false, role: :get, target: '_self', klass: '',
|
5
|
+
text: "", confirm: false, data: { } }.merge( additional )
|
6
|
+
|
7
|
+
text = options[:text]
|
8
|
+
role = options[:role]
|
9
|
+
icon = options[:icon]
|
10
|
+
klass = options[:klass]
|
11
|
+
is_remote = options[:remote]
|
12
|
+
html_data = options[:data]
|
13
|
+
|
14
|
+
data_params = html_data.merge( { toggle: 'tooltip', placement: 'top', 'row-id': row_id, 'original-title': message } )
|
15
|
+
data_params[:confirm] = I18n.t('confirmation') if options[:confirm]
|
16
|
+
|
17
|
+
button = command_button_item( klass, icon, text, data_params )
|
18
|
+
|
19
|
+
if path.present?
|
20
|
+
link_to(path, remote: is_remote, data: data_params, target: options[:target], method: role) do
|
21
|
+
concat( button )
|
22
|
+
end
|
23
|
+
|
24
|
+
else
|
25
|
+
button
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def command_button_item( klass, icon, text, data )
|
30
|
+
text = " #{text}" if text.present?
|
31
|
+
|
32
|
+
button_tag( role: 'button', type: 'button', class: "btn btn-primary #{ klass }", data: data ) do
|
33
|
+
content_tag( :span, text, class: icon )
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def edit_button( path, row_id, message, additional = { } )
|
38
|
+
options = { role: :get, icon: 'zmdi zmdi-edit' }.merge( additional )
|
39
|
+
command_button( path, row_id, I18n.t('simple_form.edit', message: message), options )
|
40
|
+
end
|
41
|
+
|
42
|
+
def new_button( path, message, additional = { } )
|
43
|
+
options = { role: :get, icon: 'zmdi zmdi-plus' }.merge( additional )
|
44
|
+
command_button( path, 0, I18n.t('simple_form.new', message: message), options )
|
45
|
+
end
|
46
|
+
|
47
|
+
def delete_button( path, row_id, message, additional = { } )
|
48
|
+
options = { role: :delete, icon: 'zmdi zmdi-delete', klass: 'command-delete', confirm: true }.merge( additional )
|
49
|
+
command_button( path, row_id, I18n.t('simple_form.delete', message: message), options )
|
50
|
+
end
|
51
|
+
|
52
|
+
def show_button( path, row_id, message, additional = { } )
|
53
|
+
options = { role: :get, icon: 'zmdi zmdi-search' }.merge( additional )
|
54
|
+
command_button( path, row_id, I18n.t('simple_form.show', message: message), options )
|
55
|
+
end
|
56
|
+
|
57
|
+
def open_filter_button
|
58
|
+
button_tag( t('simple_form.filter'), id: 'enable-filter',
|
59
|
+
class: 'btn btn-regular pull-left' )
|
60
|
+
end
|
61
|
+
|
62
|
+
def do_filter_button
|
63
|
+
button_tag( t('simple_form.search'), id: 'enable-filter',
|
64
|
+
class: 'btn btn-primary pull-right' )
|
65
|
+
end
|
66
|
+
|
67
|
+
def cancel_button
|
68
|
+
round_icon_button(type: :button, icon: 'fa-close', button: 'btn-default', class: 'pull-right', title: 'Cancelar')
|
69
|
+
end
|
70
|
+
|
71
|
+
def round_icon_button(params)
|
72
|
+
params[:shape] = 'btn-circle'
|
73
|
+
params[:text] = ''
|
74
|
+
|
75
|
+
text_icon_button(params)
|
76
|
+
end
|
77
|
+
|
78
|
+
def text_icon_button(params)
|
79
|
+
params[:size] ||= 'btn-lg'
|
80
|
+
|
81
|
+
content = content_tag(:span) do
|
82
|
+
concat(content_tag(:span, '', class: "fa #{params[:icon]}"))
|
83
|
+
concat(" #{params[:text]}") if params[:text] && params[:text].length > 0
|
84
|
+
end
|
85
|
+
|
86
|
+
button_data = { type: params[:type],
|
87
|
+
class: "btn #{params[:button]} #{params[:shape]} #{params[:size]} #{params[:class]} text-center",
|
88
|
+
title: params[:title],
|
89
|
+
data: {
|
90
|
+
toggle: 'tooltip',
|
91
|
+
placement: 'bottom'
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
|
96
|
+
button_data[:id] = params[:id] if params[:id]
|
97
|
+
button_data[:data] = button_data[:data].merge(params[:data]) if params[:data]
|
98
|
+
|
99
|
+
button_tag(content, button_data)
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
|
2
|
+
module Base::Project::App::Helpers::LocalizableHelper
|
3
|
+
def prepare_coordinates(location)
|
4
|
+
coordinate_array = location.coordinates
|
5
|
+
coordinate_array.present? ? escape_javascript(coordinate_array.to_json).to_s : ''
|
6
|
+
end
|
7
|
+
|
8
|
+
def add_localization(location)
|
9
|
+
add_localizations([location]) if location
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_localizations(locations)
|
13
|
+
return '' if locations.blank?
|
14
|
+
|
15
|
+
js_locations = []
|
16
|
+
first_location = locations.first
|
17
|
+
|
18
|
+
been_img = image_url('map/been.png')
|
19
|
+
current_img = image_url('map/employee.png')
|
20
|
+
|
21
|
+
locations.each do |location|
|
22
|
+
coordinates = prepare_coordinates(location)
|
23
|
+
next if coordinates.blank?
|
24
|
+
|
25
|
+
if location != first_location
|
26
|
+
image = been_img
|
27
|
+
current = true
|
28
|
+
else
|
29
|
+
image = current_img
|
30
|
+
current = false
|
31
|
+
end
|
32
|
+
|
33
|
+
location_id = "loc-#{location.id}"
|
34
|
+
|
35
|
+
js_locations << "window.map.addCoordinate({
|
36
|
+
coordinates: #{coordinates},
|
37
|
+
icon: '#{image}',
|
38
|
+
locationId: '#{location_id}',
|
39
|
+
visited: #{current},
|
40
|
+
period: true,
|
41
|
+
properties: { title: '#{location.name}', icon: '#{image}' }
|
42
|
+
});"
|
43
|
+
end
|
44
|
+
|
45
|
+
js_locations << "window.map.getMap().setCenter(#{prepare_coordinates(first_location)});"
|
46
|
+
js_locations << 'window.map.getMap().setZoom(15);'
|
47
|
+
|
48
|
+
result = js_locations.join("\n")
|
49
|
+
result.html_safe
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Base::Project::App::Models::Concerns::Addressable
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
before_validation :coordinates_precision
|
6
|
+
normalize_attributes :zipcode, with: :remove_punctuation
|
7
|
+
|
8
|
+
validates :default, inclusion: { in: [true, false] }
|
9
|
+
validates :name, presence: true, length: { maximum: 100 }
|
10
|
+
|
11
|
+
validates :country, presence: true, length: { maximum: 2, minimum: 2 }
|
12
|
+
validates :state, presence: true, length: { maximum: 100 }
|
13
|
+
validates :city, presence: true, length: { maximum: 100 }
|
14
|
+
validates :street, presence: true, length: { maximum: 100 }
|
15
|
+
validates :number, presence: true, numericality: { greater_than_or_equal_to: 0 }
|
16
|
+
validates :district, presence: true, length: { maximum: 100 }
|
17
|
+
validates :zipcode, presence: true, length: { maximum: 8, minimmum: 8 }, format: { with: /\A\d*\z/i }
|
18
|
+
|
19
|
+
validates :apartment, presence: false, numericality: { greater_than: 0 }, allow_blank: true
|
20
|
+
validates :block, presence: false, length: { maximum: 100 }, allow_blank: true
|
21
|
+
|
22
|
+
validates :latitude, presence: false, allow_blank: true
|
23
|
+
validates :longitude, presence: false, allow_blank: true
|
24
|
+
validate :unique_default_address
|
25
|
+
|
26
|
+
scope :by_name, ->(name) { where("#{table_name}.name ILIKE ?", "%#{name}%") }
|
27
|
+
scope :by_country, ->(country) { where("#{table_name}.country ILIKE ?", "%#{country}%") }
|
28
|
+
scope :by_city, ->(city) { where("#{table_name}.city ILIKE ?", "%#{city}%") }
|
29
|
+
scope :by_street, ->(street) { where("#{table_name}.street ILIKE ?", "%#{street}%") }
|
30
|
+
scope :by_number, ->(number) { where("#{table_name}.number = ?", number.to_i.to_s) }
|
31
|
+
scope :by_district, ->(district) { where("#{table_name}.district ILIKE ?", "%#{district}%") }
|
32
|
+
scope :by_zipcode, ->(zipcode) { where("#{table_name}.zipcode ILIKE ?", "%#{Base::Project::Lib::StringSanitizer.remove_punctuation(zipcode)}%") }
|
33
|
+
|
34
|
+
scope :regular_order, -> { order("#{table_name}.default DESC, #{table_name}.name") }
|
35
|
+
scope :the_defaults, -> { where("#{table_name}.default = ?", true) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def coordinates_precision
|
39
|
+
self.latitude = latitude.round(11) if latitude.present?
|
40
|
+
self.longitude = longitude.round(11) if longitude.present?
|
41
|
+
end
|
42
|
+
|
43
|
+
def multiple_default_error
|
44
|
+
errors.add(:default, I18n.t('activerecord.errors.messages.default_address_already_exists'))
|
45
|
+
end
|
46
|
+
|
47
|
+
def formatted_simple
|
48
|
+
text = "#{street} #{number}"
|
49
|
+
|
50
|
+
text = "#{text}, ap.#{apartment}" if apartment
|
51
|
+
text = "#{text}, bl.#{block}" if block
|
52
|
+
|
53
|
+
"#{text}, #{district}, #{city}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def formatted_zipcode
|
57
|
+
StringSanitizer.mask_cep(zipcode)
|
58
|
+
end
|
59
|
+
|
60
|
+
def formatted_country
|
61
|
+
country_model = ISO3166::Country[country]
|
62
|
+
country_model.translations[country] || country_model.name
|
63
|
+
end
|
64
|
+
|
65
|
+
def formatted_complete
|
66
|
+
"#{formatted_simple}, #{formatted_zipcode} #{formatted_country}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def coordinate_pair
|
70
|
+
[latitude, longitude]
|
71
|
+
end
|
72
|
+
|
73
|
+
def coordinates_dms
|
74
|
+
latitude_dms = coordinate_to_dms(latitude, %w[N S])
|
75
|
+
longitude_dms = coordinate_to_dms(longitude, %w[E W])
|
76
|
+
|
77
|
+
[latitude_dms, longitude_dms]
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def coordinate_to_dms(coordinate_value, directions)
|
83
|
+
value = coordinate_value.abs
|
84
|
+
|
85
|
+
degrees = value.floor
|
86
|
+
direction = (latitude > 0 ? directions.first : directions.last)
|
87
|
+
|
88
|
+
minutes = (((value - degrees) * 60)).round(3).floor
|
89
|
+
seconds = (((value - degrees) * 3600) - (minutes * 60)).round(3).floor
|
90
|
+
|
91
|
+
"#{degrees}° #{minutes}′ #{seconds}\"#{direction}"
|
92
|
+
end
|
93
|
+
end
|