├── .github └── FUNDING.yml ├── Procfile ├── README.md ├── app ├── assets │ ├── images │ │ ├── .keep │ │ ├── avatar │ │ │ ├── avatar-1.png │ │ │ ├── avatar-2.png │ │ │ ├── avatar-3.png │ │ │ ├── avatar-4.png │ │ │ └── avatar-5.png │ │ ├── drawkit │ │ │ ├── drawkit-full-stack-man-colour.svg │ │ │ ├── drawkit-mobile-article-colour.svg │ │ │ ├── drawkit-nature-man-colour.svg │ │ │ └── revenue-graph-colour.svg │ │ ├── example-image-50.jpg │ │ ├── example-image.jpg │ │ ├── loader.svg │ │ ├── news │ │ │ ├── img01.jpg │ │ │ ├── img02.jpg │ │ │ ├── img03.jpg │ │ │ ├── img04.jpg │ │ │ ├── img05.jpg │ │ │ ├── img06.jpg │ │ │ ├── img07.jpg │ │ │ ├── img08.jpg │ │ │ ├── img09.jpg │ │ │ ├── img10.jpg │ │ │ ├── img11.jpg │ │ │ ├── img12.jpg │ │ │ ├── img13.jpg │ │ │ ├── img14.jpg │ │ │ ├── img15.jpg │ │ │ ├── img16.jpg │ │ │ └── img17.jpg │ │ ├── p-250.png │ │ ├── p-50.png │ │ ├── paypal.png │ │ ├── products │ │ │ ├── product-1-50.png │ │ │ ├── product-1.jpg │ │ │ ├── product-2-50.png │ │ │ ├── product-2.jpg │ │ │ ├── product-3-50.png │ │ │ ├── product-3.jpg │ │ │ ├── product-4-50.png │ │ │ ├── product-4.jpg │ │ │ ├── product-5-50.png │ │ │ └── product-5.jpg │ │ ├── stisla-fill.svg │ │ ├── stisla-light.svg │ │ ├── stisla-transparent.svg │ │ ├── stisla.svg │ │ └── unsplash │ │ │ ├── andre-benz-1214056-unsplash.jpg │ │ │ └── eberhard-grossgasteiger-1207565-unsplash.jpg │ └── stylesheets │ │ ├── application.css │ │ ├── loading.scss │ │ ├── mobile.scss │ │ └── printing.scss ├── controllers │ ├── application_controller.rb │ ├── concerns │ │ ├── forgery_protection.rb │ │ └── set_platform.rb │ └── home_controller.rb ├── helpers │ └── application_helper.rb ├── javascript │ ├── app.js │ ├── controllers │ │ ├── date_controller.js │ │ ├── datetime_controller.js │ │ ├── infinite_page_controller.js │ │ ├── select_controller.js │ │ ├── sidebar_controller.js │ │ ├── time_controller.js │ │ ├── toastr_controller.js │ │ ├── turbolinks_form_controller.js │ │ └── validation_controller.js │ ├── helpers.js │ ├── packs │ │ └── application.js │ └── vendors │ │ └── stisla │ │ ├── scripts.js │ │ └── stisla.js ├── models │ ├── application_platform.rb │ ├── application_record.rb │ └── concerns │ │ └── chronological_order.rb └── views │ ├── home │ └── index.html.erb │ ├── layouts │ └── application.html.erb │ └── shared │ ├── _alert_validation.html.erb │ ├── _card_dropdown.html.erb │ ├── _footer.html.erb │ ├── _navbar.html.erb │ ├── _sidebar.html.erb │ ├── _sidebar_dropdown.html.erb │ └── _toastr.html.erb ├── config ├── config.yml ├── environment.rb ├── initializers │ ├── assets.rb │ ├── ransack.rb │ └── sidekiq.rb ├── locales │ └── en.yml └── routes.rb ├── lib └── templates │ ├── erb │ └── scaffold │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── show.html.erb │ ├── rails │ └── scaffold_controller │ │ └── controller.rb │ └── test_unit │ └── scaffold │ └── system_test.rb ├── public ├── 404.html ├── 422.html ├── 500.html └── style.css ├── screenshot_1.jpg ├── template.rb ├── test ├── support │ └── test_capybara_helper.rb └── test_helper.rb └── vendor └── assets └── stylesheets ├── fontawesome └── rails.scss └── stisla ├── components.css └── style.css /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ["https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=8F3EJLDJVVPDL¤cy_code=BRL&source=url", "twitter.com/lazaronixon"] 2 | ko_fi: lazaronixon 3 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec puma -C config/puma.rb 2 | worker: bundle exec sidekiq 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### This library is deprecated in reason of [Administration-Zero](https://github.com/lazaronixon/administration-zero) 2 | 3 | # The Construct 4 | **This is The Construct.** It's our rails template, it has almost everything. From beautiful layout to production ready setup, javascript components and good practices to learn. 5 | 6 |  7 | 8 | ## Getting started 9 | ``` 10 | $ git clone https://github.com/lazaronixon/the_construct.git 11 | $ rails new awesome-rails -d=postgresql --webpack=stimulus -m ~/RailsProjects/the_construct/template.rb 12 | ``` 13 | 14 | ## Features 15 | * [Stisla Admin Template](https://demo.getstisla.com) 16 | * [TurboLinks](https://github.com/turbolinks/turbolinks) 17 | * [Stimulus](https://github.com/stimulusjs/stimulus) 18 | * [Scaffolds](https://guides.rubyonrails.org/command_line.html#rails-generate) 19 | * [Heroku Ready](https://heroku.com) 20 | * [Hybrid Mobile Architecture Ready](https://m.signalvnoise.com/basecamp-3-for-ios-hybrid-architecture) 21 | * [DHH development practices](https://www.youtube.com/channel/UCdx5Dk3EWTe2i8YDA7bfl6g) 22 | * [ScreenCast](https://www.youtube.com/watch?v=_p0S0Ll7o78) 23 | 24 | ### Stisla Admin Template 25 | The construct integrates [Stisla Admin Template](https://demo.getstisla.com) with modern rails ecosystem tools like Webpack, Turbolinks and Stimulus JS, bringing a sophisticated implementation and maximum performance without SPA hassle. 26 | 27 | ### Scaffolds 28 | The construct replaces default scaffold template with a new one focused on production. Some features are: 29 | * [Sortable and filterable](https://github.com/activerecord-hackery/ransack) 30 | * [Infinite scroll pagination](https://github.com/basecamp/geared_pagination) 31 | * [Exportable data](https://github.com/westonganger/spreadsheet_architect) 32 | * [Input masks](https://demo.getstisla.com/forms-advanced-form.html) 33 | * [Select with autocomplete on references fields](https://demo.getstisla.com/forms-advanced-form.html) 34 | * [Toastr plugin to show flash messages](https://demo.getstisla.com/modules-toastr.html) 35 | * [HTML5 client side validation integration](https://demo.getstisla.com/forms-validation.html) 36 | * Ajax form post with turbolinks handlers 37 | * API and WEB share controllers 38 | * Self generated system tests 39 | 40 | ### Heroku Ready 41 | The construct comes with some opinionated configurations and addons that should be on any production application like: 42 | * [Heroku Redis](https://elements.heroku.com/addons/heroku-redis) for data store and caching. 43 | * [PaperTrail](https://elements.heroku.com/addons/papertrail) or any other for logging. 44 | * [SendGrid](https://elements.heroku.com/addons/sendgrid) for email delivery. 45 | * [RackRatelimit](https://github.com/jeremy/rack-ratelimit) for blocking & throttling abusive requests. 46 | * AssetHost configured to ENV['CLOUDFRONT_URL']. 47 | * Sidekiq as queue adapter. 48 | * Force SSL. 49 | 50 | ### Hybrid Mobile Architecture Ready 51 | The construct is a [Majestic Monolith](https://m.signalvnoise.com/the-majestic-monolith) template so it is full compatible with [TurboLinks IOS](https://github.com/turbolinks/turbolinks-ios), [TurboLinks Android](https://github.com/turbolinks/turbolinks-android) or [React Native TurboLinks](https://github.com/lazaronixon/react-native-turbolinks). After create a new project you can easily port it to a IOS/Android APP. 52 | 53 | ### Requirements 54 | Rails ~> 6.0.0 55 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/images/avatar/avatar-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/avatar/avatar-1.png -------------------------------------------------------------------------------- /app/assets/images/avatar/avatar-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/avatar/avatar-2.png -------------------------------------------------------------------------------- /app/assets/images/avatar/avatar-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/avatar/avatar-3.png -------------------------------------------------------------------------------- /app/assets/images/avatar/avatar-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/avatar/avatar-4.png -------------------------------------------------------------------------------- /app/assets/images/avatar/avatar-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/avatar/avatar-5.png -------------------------------------------------------------------------------- /app/assets/images/drawkit/drawkit-full-stack-man-colour.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/assets/images/drawkit/drawkit-mobile-article-colour.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/drawkit/drawkit-nature-man-colour.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/drawkit/revenue-graph-colour.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/example-image-50.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/example-image-50.jpg -------------------------------------------------------------------------------- /app/assets/images/example-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/example-image.jpg -------------------------------------------------------------------------------- /app/assets/images/loader.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/news/img01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/news/img01.jpg -------------------------------------------------------------------------------- /app/assets/images/news/img02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/news/img02.jpg -------------------------------------------------------------------------------- /app/assets/images/news/img03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/news/img03.jpg -------------------------------------------------------------------------------- /app/assets/images/news/img04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/news/img04.jpg -------------------------------------------------------------------------------- /app/assets/images/news/img05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/news/img05.jpg -------------------------------------------------------------------------------- /app/assets/images/news/img06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/news/img06.jpg -------------------------------------------------------------------------------- /app/assets/images/news/img07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/news/img07.jpg -------------------------------------------------------------------------------- /app/assets/images/news/img08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/news/img08.jpg -------------------------------------------------------------------------------- /app/assets/images/news/img09.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/news/img09.jpg -------------------------------------------------------------------------------- /app/assets/images/news/img10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/news/img10.jpg -------------------------------------------------------------------------------- /app/assets/images/news/img11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/news/img11.jpg -------------------------------------------------------------------------------- /app/assets/images/news/img12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/news/img12.jpg -------------------------------------------------------------------------------- /app/assets/images/news/img13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/news/img13.jpg -------------------------------------------------------------------------------- /app/assets/images/news/img14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/news/img14.jpg -------------------------------------------------------------------------------- /app/assets/images/news/img15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/news/img15.jpg -------------------------------------------------------------------------------- /app/assets/images/news/img16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/news/img16.jpg -------------------------------------------------------------------------------- /app/assets/images/news/img17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/news/img17.jpg -------------------------------------------------------------------------------- /app/assets/images/p-250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/p-250.png -------------------------------------------------------------------------------- /app/assets/images/p-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/p-50.png -------------------------------------------------------------------------------- /app/assets/images/paypal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/paypal.png -------------------------------------------------------------------------------- /app/assets/images/products/product-1-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/products/product-1-50.png -------------------------------------------------------------------------------- /app/assets/images/products/product-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/products/product-1.jpg -------------------------------------------------------------------------------- /app/assets/images/products/product-2-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/products/product-2-50.png -------------------------------------------------------------------------------- /app/assets/images/products/product-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/products/product-2.jpg -------------------------------------------------------------------------------- /app/assets/images/products/product-3-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/products/product-3-50.png -------------------------------------------------------------------------------- /app/assets/images/products/product-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/products/product-3.jpg -------------------------------------------------------------------------------- /app/assets/images/products/product-4-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/products/product-4-50.png -------------------------------------------------------------------------------- /app/assets/images/products/product-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/products/product-4.jpg -------------------------------------------------------------------------------- /app/assets/images/products/product-5-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/products/product-5-50.png -------------------------------------------------------------------------------- /app/assets/images/products/product-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/products/product-5.jpg -------------------------------------------------------------------------------- /app/assets/images/stisla-fill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/stisla-light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/stisla-transparent.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/stisla.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/unsplash/andre-benz-1214056-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/unsplash/andre-benz-1214056-unsplash.jpg -------------------------------------------------------------------------------- /app/assets/images/unsplash/eberhard-grossgasteiger-1207565-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazaronixon/the_construct/33354efc4f464b36e0a4307bf51820f0ee73dd49/app/assets/images/unsplash/eberhard-grossgasteiger-1207565-unsplash.jpg -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's 6 | * vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require bootstrap/scss/bootstrap 14 | *= require fontawesome/rails 15 | *= require select2/dist/css/select2 16 | *= require izitoast/dist/css/iziToast 17 | *= require stisla/style 18 | *= require stisla/components 19 | *= require_tree . 20 | *= stub mobile 21 | *= require_self 22 | */ 23 | 24 | .turbolinks-progress-bar { 25 | background-color: #fd7e14; 26 | } 27 | 28 | .main-content { 29 | min-height: 649px; 30 | } 31 | 32 | .table .btn-icon.btn-sm { 33 | line-height: 15px; 34 | } 35 | 36 | .table thead th, .table td { 37 | white-space: nowrap; 38 | vertical-align: middle; 39 | } 40 | 41 | a[data-target='infinite-page.more'] { 42 | display: none; 43 | } 44 | 45 | [data-controller='infinite-page'] { 46 | position: relative; 47 | } 48 | 49 | [data-target='infinite-page.container'].loading:after { 50 | position: absolute; 51 | left: 50%; 52 | top: 100%; 53 | width: 3rem; 54 | height: 3rem; 55 | opacity: 0.5; 56 | border-radius: 100%; 57 | margin: -2.2rem 0 0 -1rem; 58 | content: ''; 59 | } 60 | 61 | /** HIDE-SPINNERS-BEGIN **/ 62 | input[type='number'] { 63 | -moz-appearance:textfield; 64 | } 65 | 66 | input::-webkit-outer-spin-button, 67 | input::-webkit-inner-spin-button { 68 | -webkit-appearance: none; 69 | } 70 | /** HIDE-SPINNERS-END **/ 71 | -------------------------------------------------------------------------------- /app/assets/stylesheets/loading.scss: -------------------------------------------------------------------------------- 1 | @keyframes pulse-background { 2 | 0% { 3 | -webkit-background-size: 1% 0.1em; 4 | background-size: 1% 0.1em; 5 | } 6 | 100% { 7 | -webkit-background-size: 200% 100em; 8 | background-size: 200% 100em; 9 | } 10 | } 11 | 12 | @mixin loader { 13 | animation: pulse-background 1s ease infinite; 14 | background-image: image-url("loader.svg"); 15 | background-position: center; 16 | background-repeat: no-repeat; 17 | background-size: 0.1em; 18 | } 19 | 20 | [data-disable-with]:disabled:not(.disable-loading), { 21 | @include loader; 22 | } 23 | 24 | [data-target='infinite-page.container'].loading:after { 25 | @include loader; 26 | } 27 | -------------------------------------------------------------------------------- /app/assets/stylesheets/mobile.scss: -------------------------------------------------------------------------------- 1 | .main-navbar, .navbar-bg, 2 | .main-sidebar, .main-footer { 3 | display: none; 4 | } 5 | 6 | .main-content, .card .card-header, 7 | .card .card-body, .card .card-footer { 8 | padding: 5px 10px; 9 | } 10 | 11 | .d-mobile-none { 12 | display: none !important; 13 | } 14 | 15 | .d-mobile-inline { 16 | display: inline !important; 17 | } 18 | -------------------------------------------------------------------------------- /app/assets/stylesheets/printing.scss: -------------------------------------------------------------------------------- 1 | .m-page-break { page-break-after: always; } 2 | .m-page-break:last-child { page-break-after: auto; } 3 | .m-keep-together { page-break-inside: avoid; } 4 | 5 | @media print { 6 | .main-navbar, .navbar-bg, 7 | .main-sidebar, .main-footer { 8 | display: none; 9 | } 10 | 11 | .main-content, .card .card-header, 12 | .card .card-body, .card .card-footer { 13 | padding: 5px 10px; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | include ForgeryProtection 3 | include SetPlatform 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/concerns/forgery_protection.rb: -------------------------------------------------------------------------------- 1 | module ForgeryProtection 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | protect_from_forgery unless: -> { request.format.json? } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/concerns/set_platform.rb: -------------------------------------------------------------------------------- 1 | module SetPlatform 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | helper_method :platform 6 | end 7 | 8 | private 9 | def platform 10 | @platform ||= ApplicationPlatform.new(request.user_agent) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeController < ApplicationController 2 | def index 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | def page_title 3 | content_for(:page_title) || Rails.application.class.to_s.split('::').first 4 | end 5 | 6 | def crud_actions 7 | %w( index show new edit create update ) 8 | end 9 | 10 | def active_nav_item(controller, actions) 11 | 'active' if active_actions?(controller, actions) 12 | end 13 | 14 | def sort_link_turbo(attribute, *args) 15 | sort_link(attribute, *args.push({}, { data: { turbolinks_action: 'replace' } })) 16 | end 17 | 18 | def icon(klass, text = nil) 19 | icon_tag = tag.i(class: klass) 20 | text_tag = tag.span text 21 | text ? tag.span(icon_tag + text_tag) : icon_tag 22 | end 23 | 24 | def np(number, options = {}) 25 | number_with_precision number, options 26 | end 27 | 28 | def nd(number, options = {}) 29 | number_with_delimiter number, options 30 | end 31 | 32 | def localize(object, options = {}) 33 | super(object, options) if object 34 | end 35 | alias :l :localize 36 | 37 | private 38 | def active_actions?(controller, actions) 39 | params[:controller].include?(controller) && actions.include?(params[:action]) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /app/javascript/app.js: -------------------------------------------------------------------------------- 1 | import { onDocumentReady } from "helpers" 2 | 3 | class App { 4 | 5 | bindDomContentLoaded() { 6 | onDocumentReady(() => { this.bindFormAjaxError() }) 7 | } 8 | 9 | bindTurboLinksEvents() { 10 | document.addEventListener("turbolinks:load", () => { 11 | //Initialize plugins here 12 | }) 13 | document.addEventListener("turbolinks:before-cache", () => { 14 | //Destroy plugins here 15 | }) 16 | } 17 | 18 | // Private 19 | bindFormAjaxError() { 20 | document.addEventListener("ajax:error", (e) => { 21 | if (e.target.matches("[data-remote='true']")) { 22 | iziToast.error({message: "Whoopps, something went wrong.", title: "Error", position: "topRight"}) 23 | } 24 | }) 25 | } 26 | } 27 | 28 | export function start() { 29 | const app = new App() 30 | app.bindDomContentLoaded() 31 | app.bindTurboLinksEvents() 32 | } 33 | -------------------------------------------------------------------------------- /app/javascript/controllers/date_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "stimulus" 2 | 3 | export default class extends Controller { 4 | 5 | connect() { 6 | this.element.placeholder = "YYYY/MM/DD" 7 | new Cleave(this.element, { date: true, datePattern: ["Y", "m", "d"] }) 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /app/javascript/controllers/datetime_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "stimulus" 2 | 3 | export default class extends Controller { 4 | 5 | connect() { 6 | this.element.placeholder = "YYYY/MM/DD hh:mm" 7 | new Cleave(this.element, { delimiters: ["/", "/", " ", ":"], blocks: [4, 2, 2, 2, 2] }) 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /app/javascript/controllers/infinite_page_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "stimulus" 2 | 3 | export default class extends Controller { 4 | 5 | static targets = [ "container" ] 6 | 7 | connect() { 8 | new Waypoint.Infinite({ 9 | element: this.containerTarget, 10 | items: "[data-target='infinite-page.item']", 11 | more: "a[data-target='infinite-page.more']", 12 | loadingClass: "loading", 13 | }) 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /app/javascript/controllers/select_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "stimulus" 2 | 3 | export default class extends Controller { 4 | 5 | connect() { 6 | $(this.element).select2() 7 | } 8 | 9 | disconnect() { 10 | $(this.element).select2("destroy") 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /app/javascript/controllers/sidebar_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "stimulus" 2 | 3 | export default class extends Controller { 4 | 5 | connect() { 6 | if (this.activeDropDownItem) { 7 | this.activeDropDownMenu.style.display = "block" 8 | this.activeDropDown.classList.add("active") 9 | } 10 | } 11 | 12 | // Private 13 | get activeDropDownItem() { 14 | return this.element.querySelector(".dropdown-menu > li.active") 15 | } 16 | 17 | get activeDropDownMenu() { 18 | return this.activeDropDownItem.closest(".dropdown-menu") 19 | } 20 | 21 | get activeDropDown() { 22 | return this.activeDropDownItem.closest(".dropdown") 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/javascript/controllers/time_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "stimulus" 2 | 3 | export default class extends Controller { 4 | 5 | connect() { 6 | this.element.placeholder = "hh:mm:ss" 7 | new Cleave(this.element, { time: true, timePattern: ["h", "m", "s"] }) 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /app/javascript/controllers/toastr_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "stimulus" 2 | 3 | export default class extends Controller { 4 | 5 | connect() { 6 | this.element.remove() 7 | if (this.data.get("type") === "notice") { 8 | iziToast.success({message: this.data.get("message"), title: "Success", position: "topRight"}) 9 | } else if (this.data.get("type") === "alert") { 10 | iziToast.error({message: this.data.get("message"), title: "Error", position: "topRight"}) 11 | } else { 12 | iziToast.info({message: this.data.get("message"), title: "Info", position: "topRight"}) 13 | } 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /app/javascript/controllers/turbolinks_form_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "stimulus" 2 | 3 | export default class extends Controller { 4 | 5 | connect() { 6 | this.element.addEventListener("ajax:beforeSend", function(event) { 7 | const detail = event.detail, xhr = detail[0], options = detail[1] 8 | 9 | Turbolinks.visit(options.url, { action: "replace" }) 10 | event.preventDefault() 11 | }) 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /app/javascript/controllers/validation_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "stimulus" 2 | 3 | export default class extends Controller { 4 | 5 | connect() { 6 | this.element.setAttribute("novalidate", "true") 7 | 8 | $(this.element).submit(function() { 9 | var form = $(this); 10 | 11 | if (form[0].checkValidity() === false) { 12 | event.preventDefault(); 13 | event.stopPropagation(); 14 | form.addClass("was-validated"); 15 | } else { 16 | form.addClass("was-validated"); 17 | } 18 | }) 19 | 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /app/javascript/helpers.js: -------------------------------------------------------------------------------- 1 | export function getMetaValue(name) { 2 | const element = findElement(document.head, `meta[name="${name}"]`) 3 | if (element) { 4 | return element.getAttribute("content") 5 | } 6 | } 7 | 8 | export function findElements(root, selector) { 9 | if (typeof root == "string") { 10 | selector = root 11 | root = document 12 | } 13 | const elements = root.querySelectorAll(selector) 14 | return toArray(elements) 15 | } 16 | 17 | export function findElement(root, selector) { 18 | if (typeof root == "string") { 19 | selector = root 20 | root = document 21 | } 22 | return root.querySelector(selector) 23 | } 24 | 25 | export function dispatchEvent(element, type, eventInit = {}) { 26 | const { disabled } = element 27 | const { bubbles, cancelable, detail } = eventInit 28 | const event = document.createEvent("Event") 29 | 30 | event.initEvent(type, bubbles || true, cancelable || true) 31 | event.detail = detail || {} 32 | 33 | try { 34 | element.disabled = false 35 | element.dispatchEvent(event) 36 | } finally { 37 | element.disabled = disabled 38 | } 39 | 40 | return event 41 | } 42 | 43 | export function toArray(value) { 44 | if (Array.isArray(value)) { 45 | return value 46 | } else if (Array.from) { 47 | return Array.from(value) 48 | } else { 49 | return [].slice.call(value) 50 | } 51 | } 52 | 53 | export function onDocumentReady(callback) { 54 | if (document.readyState !== "loading") { 55 | callback() 56 | } else { 57 | document.addEventListener("DOMContentLoaded", callback) 58 | } 59 | } 60 | 61 | export function removeElement(el) { 62 | if (el && el.parentNode) { 63 | el.parentNode.removeChild(el) 64 | } 65 | } 66 | 67 | export function insertAfter(el, referenceNode) { 68 | return referenceNode.parentNode.insertBefore(el, referenceNode.nextSibling) 69 | } 70 | 71 | export function getAuthenticityToken() { 72 | return getMetaValue("csrf-token") 73 | } 74 | 75 | export function rFetch(resource, _options = {}) { 76 | const options = Object.assign({}, _options) 77 | 78 | setupHeaders() 79 | setupContentType() 80 | setupAccept() 81 | setupCredentials() 82 | return fetch(resource, options).then(checkStatus).then(parseResponse) 83 | 84 | function setupHeaders() { 85 | const headers = Object.assign({}, options.headers) 86 | headers["X-CSRF-Token"] = getAuthenticityToken() 87 | headers["X-Requested-With"] = "XMLHttpRequest" 88 | options.headers = headers 89 | } 90 | 91 | function setupContentType() { 92 | if (options.contentType) options.headers["Content-Type"] = options.contentType 93 | } 94 | 95 | function setupAccept() { 96 | if (options.as == "json") { 97 | options.headers["Accept"] = "application/json" 98 | } 99 | if (options.as == "xhr") { 100 | options.headers["Accept"] = "text/javascript" 101 | } 102 | } 103 | 104 | function setupCredentials() { 105 | options.credentials = options.credentials || "same-origin" 106 | } 107 | 108 | function checkStatus(response) { 109 | if (response.status >= 200 && response.status < 300) { 110 | return response 111 | } else { 112 | const error = new Error(response.statusText) 113 | error.response = response 114 | throw error 115 | } 116 | } 117 | 118 | function parseResponse(response) { 119 | switch (options.as) { 120 | case "blob": 121 | return response.blob(); 122 | case "json": 123 | return response.json(); 124 | default: 125 | return response.text() 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /app/javascript/packs/application.js: -------------------------------------------------------------------------------- 1 | // This file is automatically compiled by Webpack, along with any other files 2 | // present in this directory. You're encouraged to place your actual application logic in 3 | // a relevant structure within app/javascript and only use these pack files to reference 4 | // that code so it'll be compiled. 5 | 6 | // Core libraries 7 | require("turbolinks").start() 8 | require("@rails/ujs").start() 9 | require("@rails/activestorage").start() 10 | require("channels") 11 | 12 | // jQuery (as a read only property so browser extensions can't clobber it) 13 | const jquery = require("jquery") 14 | const descriptor = { value: jquery, writable: false, configurable: false } 15 | Object.defineProperties(window, { $: descriptor, jQuery: descriptor }) 16 | 17 | // App libraries 18 | require("bootstrap") 19 | require("jquery.nicescroll") 20 | 21 | require("select2") 22 | require("cleave.js") 23 | require("waypoints/lib/noframework.waypoints") 24 | require("waypoints/lib/shortcuts/infinite") 25 | 26 | window.iziToast = require("izitoast") 27 | 28 | // Stisla 29 | require("vendors/stisla/stisla") 30 | require("vendors/stisla/scripts") 31 | 32 | // Application 33 | require("app").start() 34 | 35 | import "controllers" 36 | -------------------------------------------------------------------------------- /app/javascript/vendors/stisla/scripts.js: -------------------------------------------------------------------------------- 1 | $(document).on("turbolinks:load", function() { 2 | // ChartJS 3 | if(window.Chart) { 4 | Chart.defaults.global.defaultFontFamily = "'Nunito', 'Segoe UI', 'Arial'"; 5 | Chart.defaults.global.defaultFontSize = 12; 6 | Chart.defaults.global.defaultFontStyle = 500; 7 | Chart.defaults.global.defaultFontColor = "#999"; 8 | Chart.defaults.global.tooltips.backgroundColor = "#000"; 9 | Chart.defaults.global.tooltips.bodyFontColor = "rgba(255,255,255,.7)"; 10 | Chart.defaults.global.tooltips.titleMarginBottom = 10; 11 | Chart.defaults.global.tooltips.titleFontSize = 14; 12 | Chart.defaults.global.tooltips.titleFontFamily = "'Nunito', 'Segoe UI', 'Arial'"; 13 | Chart.defaults.global.tooltips.titleFontColor = '#fff'; 14 | Chart.defaults.global.tooltips.xPadding = 15; 15 | Chart.defaults.global.tooltips.yPadding = 15; 16 | Chart.defaults.global.tooltips.displayColors = false; 17 | Chart.defaults.global.tooltips.intersect = false; 18 | Chart.defaults.global.tooltips.mode = 'nearest'; 19 | } 20 | 21 | // DropzoneJS 22 | if(window.Dropzone) { 23 | Dropzone.autoDiscover = false; 24 | } 25 | 26 | // Basic confirm box 27 | // $('[data-confirm]').each(function() { 28 | // var me = $(this), 29 | // me_data = me.data('confirm'); 30 | // 31 | // me_data = me_data.split("|"); 32 | // me.fireModal({ 33 | // title: me_data[0], 34 | // body: me_data[1], 35 | // buttons: [ 36 | // { 37 | // text: me.data('confirm-text-yes') || 'Yes', 38 | // class: 'btn btn-danger btn-shadow', 39 | // handler: function() { 40 | // eval(me.data('confirm-yes')); 41 | // } 42 | // }, 43 | // { 44 | // text: me.data('confirm-text-cancel') || 'Cancel', 45 | // class: 'btn btn-secondary', 46 | // handler: function(modal) { 47 | // $.destroyModal(modal); 48 | // eval(me.data('confirm-no')); 49 | // } 50 | // } 51 | // ] 52 | // }) 53 | // }); 54 | 55 | // Global 56 | $(function() { 57 | let sidebar_nicescroll_opts = { 58 | cursoropacitymin: 0, 59 | cursoropacitymax: .8, 60 | zindex: 892 61 | }, now_layout_class = null; 62 | 63 | var sidebar_sticky = function() { 64 | if($("body").hasClass('layout-2')) { 65 | $("body.layout-2 #sidebar-wrapper").stick_in_parent({ 66 | parent: $('body') 67 | }); 68 | $("body.layout-2 #sidebar-wrapper").stick_in_parent({recalc_every: 1}); 69 | } 70 | } 71 | sidebar_sticky(); 72 | 73 | var sidebar_nicescroll; 74 | var update_sidebar_nicescroll = function() { 75 | let a = setInterval(function() { 76 | if(sidebar_nicescroll != null) 77 | sidebar_nicescroll.resize(); 78 | }, 10); 79 | 80 | setTimeout(function() { 81 | clearInterval(a); 82 | }, 600); 83 | } 84 | 85 | var sidebar_dropdown = function() { 86 | if($(".main-sidebar").length) { 87 | $(".main-sidebar").niceScroll(sidebar_nicescroll_opts); 88 | sidebar_nicescroll = $(".main-sidebar").getNiceScroll(); 89 | 90 | $(".main-sidebar .sidebar-menu li a.has-dropdown").off('click').on('click', function() { 91 | var me = $(this); 92 | var active = false; 93 | if(me.parent().hasClass("active")){ 94 | active = true; 95 | } 96 | 97 | $('.main-sidebar .sidebar-menu li.active > .dropdown-menu').slideUp(500, function() { 98 | update_sidebar_nicescroll(); 99 | return false; 100 | }); 101 | 102 | $('.main-sidebar .sidebar-menu li.active').removeClass('active'); 103 | 104 | if(active==true) { 105 | me.parent().removeClass('active'); 106 | me.parent().find('> .dropdown-menu').slideUp(500, function() { 107 | update_sidebar_nicescroll(); 108 | return false; 109 | }); 110 | }else{ 111 | me.parent().addClass('active'); 112 | me.parent().find('> .dropdown-menu').slideDown(500, function() { 113 | update_sidebar_nicescroll(); 114 | return false; 115 | }); 116 | } 117 | 118 | return false; 119 | }); 120 | 121 | $('.main-sidebar .sidebar-menu li.active > .dropdown-menu').slideDown(500, function() { 122 | update_sidebar_nicescroll(); 123 | return false; 124 | }); 125 | } 126 | } 127 | sidebar_dropdown(); 128 | 129 | if($("#top-5-scroll").length) { 130 | $("#top-5-scroll").css({ 131 | height: 315 132 | }).niceScroll(); 133 | } 134 | 135 | $(".main-content").css({ 136 | minHeight: $(window).outerHeight() - 108 137 | }) 138 | 139 | $(".nav-collapse-toggle").click(function() { 140 | $(this).parent().find('.navbar-nav').toggleClass('show'); 141 | return false; 142 | }); 143 | 144 | $(document).on('click', function(e) { 145 | $(".nav-collapse .navbar-nav").removeClass('show'); 146 | }); 147 | 148 | var toggle_sidebar_mini = function(mini) { 149 | let body = $('body'); 150 | 151 | if(!mini) { 152 | body.removeClass('sidebar-mini'); 153 | $(".main-sidebar").css({ 154 | overflow: 'hidden' 155 | }); 156 | setTimeout(function() { 157 | $(".main-sidebar").niceScroll(sidebar_nicescroll_opts); 158 | sidebar_nicescroll = $(".main-sidebar").getNiceScroll(); 159 | }, 500); 160 | $(".main-sidebar .sidebar-menu > li > ul .dropdown-title").remove(); 161 | $(".main-sidebar .sidebar-menu > li > a").removeAttr('data-toggle'); 162 | $(".main-sidebar .sidebar-menu > li > a").removeAttr('data-original-title'); 163 | $(".main-sidebar .sidebar-menu > li > a").removeAttr('title'); 164 | }else{ 165 | body.addClass('sidebar-mini'); 166 | body.removeClass('sidebar-show'); 167 | sidebar_nicescroll.remove(); 168 | sidebar_nicescroll = null; 169 | $(".main-sidebar .sidebar-menu > li").each(function() { 170 | let me = $(this); 171 | 172 | if(me.find('> .dropdown-menu').length) { 173 | me.find('> .dropdown-menu').hide(); 174 | me.find('> .dropdown-menu').prepend('
Find me in app/views/home/index.html.erb
7 |<%%= sort_link_turbo(@search, :<%= attribute.name %>) %> | 39 | <% end -%> 40 |Actions | 41 |||||
---|---|---|---|---|---|
<%%= l(<%= singular_table_name %>.<%= attribute.name %>) %> | 49 | <% elsif [:time].include?(attribute.type) -%> 50 |<%%= l(<%= singular_table_name %>.<%= attribute.name %>, format: :time) %> | 51 | <% elsif [:float, :decimal].include?(attribute.type) -%> 52 |<%%= np(<%= singular_table_name %>.<%= attribute.name %>) %> | 53 | <% elsif attribute.type == :boolean -%> 54 |<%%= t(<%= singular_table_name %>.<%= attribute.name %>) %> | 55 | <% else -%> 56 |<%%= <%= singular_table_name %>.<%= attribute.name %> %> | 57 | <% end -%> 58 | <% end -%> 59 |60 | <%%= link_to icon('fas fa-eye'), <%= model_resource_name %>, title: 'Show it', class: 'btn btn-icon btn-sm btn-info mr-1' %> 61 | <%%= link_to icon('fas fa-edit'), edit_<%= singular_route_name %>_path(<%= singular_table_name %>), title: 'Edit it', class: 'btn btn-icon btn-sm btn-warning mr-1' %> 62 | <%%= link_to icon('fas fa-trash'), <%= model_resource_name %>, title: 'Destroy it', class: 'btn btn-icon btn-sm btn-danger', method: :delete, data: { confirm: 'Are you sure?', remote: true } %> 63 | | 64 |