ahoy_panel 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +63 -0
- data/Rakefile +8 -0
- data/app/assets/builds/ahoy_panel.css +1 -0
- data/app/assets/config/ahoy_panel_manifest.js +3 -0
- data/app/assets/stylesheets/ahoy_panel/application.tailwind.css +13 -0
- data/app/components/ahoy_panel/date_arrow_navigator_component.html.erb +38 -0
- data/app/components/ahoy_panel/date_arrow_navigator_component.rb +12 -0
- data/app/components/ahoy_panel/dropdown_component.html.erb +53 -0
- data/app/components/ahoy_panel/dropdown_component.rb +35 -0
- data/app/components/ahoy_panel/stat_box_component.html.erb +22 -0
- data/app/components/ahoy_panel/stat_box_component.rb +13 -0
- data/app/controllers/ahoy_panel/application_controller.rb +4 -0
- data/app/controllers/ahoy_panel/dashboard_controller.rb +69 -0
- data/app/helpers/ahoy_panel/application_helper.rb +4 -0
- data/app/helpers/ahoy_panel/country_helper.rb +7 -0
- data/app/helpers/ahoy_panel/heroicon_helper.rb +7 -0
- data/app/helpers/ahoy_panel/nav_helper.rb +13 -0
- data/app/javascript/ahoy_panel/application.js +4 -0
- data/app/javascript/ahoy_panel/controllers/application.js +9 -0
- data/app/javascript/ahoy_panel/controllers/date_arrow_navigator_controller.js +11 -0
- data/app/javascript/ahoy_panel/controllers/dropdown_controller.js +18 -0
- data/app/javascript/ahoy_panel/controllers/index.js +11 -0
- data/app/javascript/ahoy_panel/controllers/mobile_header_controller.js +20 -0
- data/app/jobs/ahoy_panel/application_job.rb +4 -0
- data/app/mailers/ahoy_panel/application_mailer.rb +6 -0
- data/app/models/ahoy_panel/application_record.rb +5 -0
- data/app/models/ahoy_panel/bounce_rate_data.rb +4 -0
- data/app/models/ahoy_panel/stat_box_data.rb +26 -0
- data/app/models/ahoy_panel/total_pageviews_data.rb +31 -0
- data/app/models/ahoy_panel/total_visits_data.rb +29 -0
- data/app/models/ahoy_panel/unique_visitors_data.rb +29 -0
- data/app/models/ahoy_panel/view_per_visit_data.rb +4 -0
- data/app/models/ahoy_panel/visit_duration_data.rb +4 -0
- data/app/views/ahoy_panel/dashboard/index.html.erb +77 -0
- data/app/views/layouts/ahoy_panel/_header.html.erb +82 -0
- data/app/views/layouts/ahoy_panel/application.html.erb +19 -0
- data/config/importmap.rb +8 -0
- data/config/initializers/heroicon.rb +10 -0
- data/config/initializers/inline_svg.rb +5 -0
- data/config/routes.rb +3 -0
- data/config/tailwind.config.js +23 -0
- data/lib/ahoy_panel/engine.rb +19 -0
- data/lib/ahoy_panel/version.rb +3 -0
- data/lib/ahoy_panel.rb +6 -0
- data/lib/tasks/ahoy_panel_tasks.rake +19 -0
- metadata +243 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 562fa091377a556ab2e45ec0847c1c3ad52fb49f3f9b0832a21e4a383880ea07
|
4
|
+
data.tar.gz: 6ea33e3d37c79b04fb04904db43a091562f9fbfcd9e86a3cb1b5294cee687537
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b7c510c0e9cae0dc41bfee48b0dc6641ae43fea7652d98dac3c5fa9afd87a0d06840d1c4a71e1b77ffbedb08e04b86a741214f650a280205dbf70ee30dd9f075
|
7
|
+
data.tar.gz: 199d94c1f6eaf6ab2d1c9ebc97eaaf3ac4aaefd081011715e4b25eebcfda4de8a12347f40acee80c37b7b69e92b107c06997d270fd34fe5a58612f3dbc20a1f2
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright Chris Jeon
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# AhoyPanel
|
2
|
+
AhoyPanel is a rails engine that will give detailed analytics on Rails apps that use ahoy to collect analytics data.
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
Install the gem in your Gemfile.
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem "ahoy_panel"
|
9
|
+
```
|
10
|
+
|
11
|
+
And in `config/routes.rb`
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
mount AhoyPanel::Engine, at: "/ahoy_panel"
|
15
|
+
```
|
16
|
+
|
17
|
+
And then navigate to `/ahoy_panel` to see the dashboard.
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
Add this line to your application's Gemfile:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
gem "ahoy_panel"
|
24
|
+
```
|
25
|
+
|
26
|
+
And then execute:
|
27
|
+
```bash
|
28
|
+
$ bundle
|
29
|
+
```
|
30
|
+
|
31
|
+
Or install it yourself as:
|
32
|
+
```bash
|
33
|
+
$ gem install ahoy_panel
|
34
|
+
```
|
35
|
+
|
36
|
+
## Contributing
|
37
|
+
Contribution directions go here.
|
38
|
+
|
39
|
+
## Running the gem locally
|
40
|
+
|
41
|
+
1. Install the gem in your locally run app
|
42
|
+
2. In routes.rb, mount the engine
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
mount AhoyPanel::Engine, at: "/ahoy_panel"
|
46
|
+
```
|
47
|
+
|
48
|
+
3. Run the tailwindcss watch command in the root of the gem
|
49
|
+
|
50
|
+
```bash
|
51
|
+
$ bundle exec rails app:tailwindcss:watch
|
52
|
+
```
|
53
|
+
|
54
|
+
4. Run the rails server in your Rails application
|
55
|
+
|
56
|
+
```bash
|
57
|
+
$ bundle exec rails s
|
58
|
+
```
|
59
|
+
|
60
|
+
5. Navigate to `/ahoy_panel` to see the dashboard
|
61
|
+
|
62
|
+
## License
|
63
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:Inter var,ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],input:where(:not([type])),select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-radius:0;border-width:1px;font-size:1rem;line-height:1.5rem;padding:.5rem .75rem;--tw-shadow:0 0 #0000}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,input:where(:not([type])):focus,select:focus,textarea:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);border-color:#2563eb;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-bottom:0;padding-top:0}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple],[size]:where(select:not([size="1"])){background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;background-origin:border-box;border-color:#6b7280;border-width:1px;color:#2563eb;display:inline-block;flex-shrink:0;height:1rem;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle;width:1rem;--tw-shadow:0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{background-color:currentColor;background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:transparent}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0'/%3E%3C/svg%3E")}@media (forced-colors:active) {[type=checkbox]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}@media (forced-colors:active) {[type=radio]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=radio]:checked:focus,[type=radio]:checked:hover{background-color:currentColor;border-color:transparent}[type=checkbox]:indeterminate{background-color:currentColor;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:transparent}@media (forced-colors:active) {[type=checkbox]:indeterminate{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{background-color:currentColor;border-color:transparent}[type=file]{background:unset;border-color:inherit;border-radius:0;border-width:0;font-size:unset;line-height:inherit;padding:0}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}.sr-only{height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border-width:0;white-space:nowrap}.static{position:static}.absolute{position:absolute}.relative{position:relative}.-inset-0\.5{inset:-.125rem}.right-0{right:0}.isolate{isolation:isolate}.z-10{z-index:10}.mx-auto{margin-left:auto;margin-right:auto}.-ml-px{margin-left:-1px}.-mr-1{margin-right:-.25rem}.mb-2{margin-bottom:.5rem}.mb-5{margin-bottom:1.25rem}.mb-8{margin-bottom:2rem}.mt-2{margin-top:.5rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.size-5{height:1.25rem;width:1.25rem}.size-6{height:1.5rem;width:1.5rem}.h-16{height:4rem}.h-4{height:1rem}.h-8{height:2rem}.h-full{height:100%}.w-4{width:1rem}.w-48{width:12rem}.w-56{width:14rem}.w-auto{width:auto}.w-full{width:100%}.max-w-7xl{max-width:80rem}.flex-none{flex:none}.shrink-0{flex-shrink:0}.origin-top-right{transform-origin:top right}.scale-100{--tw-scale-x:1;--tw-scale-y:1}.scale-100,.scale-95{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-95{--tw-scale-x:.95;--tw-scale-y:.95}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-px{gap:1px}.gap-x-1{-moz-column-gap:.25rem;column-gap:.25rem}.gap-x-1\.5{-moz-column-gap:.375rem;column-gap:.375rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-2{row-gap:.5rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.25rem*var(--tw-space-y-reverse));margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.divide-gray-100>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(243 244 246/var(--tw-divide-opacity,1))}.overflow-hidden{overflow:hidden}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-l-md{border-bottom-left-radius:.375rem;border-top-left-radius:.375rem}.rounded-r-md{border-bottom-right-radius:.375rem;border-top-right-radius:.375rem}.border-b-2{border-bottom-width:2px}.border-l-4{border-left-width:4px}.border-blue-500{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.border-sky-500{--tw-border-opacity:1;border-color:rgb(14 165 233/var(--tw-border-opacity,1))}.border-transparent{border-color:transparent}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.bg-gray-900\/5{background-color:rgba(17,24,39,.05)}.bg-sky-50{--tw-bg-opacity:1;background-color:rgb(240 249 255/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.p-2{padding:.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-10{padding-bottom:2.5rem;padding-top:2.5rem}.py-12{padding-bottom:3rem;padding-top:3rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.py-5{padding-bottom:1.25rem;padding-top:1.25rem}.pb-3{padding-bottom:.75rem}.pl-3{padding-left:.75rem}.pr-4{padding-right:1rem}.pt-1{padding-top:.25rem}.pt-2{padding-top:.5rem}.text-left{text-align:left}.text-base{font-size:1rem;line-height:1.5rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl\/3{font-size:1.25rem;line-height:.75rem}.text-xs{font-size:.75rem;line-height:1rem}.text-xs\/6{font-size:.75rem;line-height:1.5rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-tight{letter-spacing:-.025em}.tracking-wide{letter-spacing:.025em}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity,1))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity,1))}.text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity,1))}.text-red-600{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity,1))}.text-sky-700{--tw-text-opacity:1;color:rgb(3 105 161/var(--tw-text-opacity,1))}.opacity-0{opacity:0}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-inset{--tw-ring-inset:inset}.ring-black\/5{--tw-ring-color:rgba(0,0,0,.05)}.ring-gray-300{--tw-ring-opacity:1;--tw-ring-color:rgb(209 213 219/var(--tw-ring-opacity,1))}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-100{transition-duration:.1s}.duration-75{transition-duration:75ms}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity,1))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.hover\:text-gray-500:hover{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.hover\:text-gray-700:hover{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.hover\:text-gray-800:hover{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity,1))}.focus\:z-10:focus{z-index:10}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-inset:focus{--tw-ring-inset:inset}.focus\:ring-sky-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(14 165 233/var(--tw-ring-opacity,1))}@media (min-width:640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:p-6{padding:1.5rem}.sm\:px-4{padding-left:1rem;padding-right:1rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}}@media (min-width:1024px){.lg\:ml-6{margin-left:1.5rem}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(2rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(2rem*var(--tw-space-x-reverse))}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-8{padding-left:2rem;padding-right:2rem}}@media (min-width:1280px){.xl\:px-8{padding-left:2rem;padding-right:2rem}}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
<span
|
2
|
+
data-controller="date-arrow-navigator"
|
3
|
+
class="isolate inline-flex rounded-md shadow-sm"
|
4
|
+
>
|
5
|
+
<%= button_tag(
|
6
|
+
data: { action: "click->date-arrow-navigator#left" },
|
7
|
+
class: "relative inline-flex items-center rounded-l-md bg-white px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
|
8
|
+
) do %>
|
9
|
+
<span class="sr-only">Previous</span>
|
10
|
+
<svg
|
11
|
+
data-url="<%= @left_url %>"
|
12
|
+
class="size-5"
|
13
|
+
viewBox="0 0 20 20"
|
14
|
+
fill="currentColor"
|
15
|
+
aria-hidden="true"
|
16
|
+
data-slot="icon"
|
17
|
+
>
|
18
|
+
<path fill-rule="evenodd" d="M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z" clip-rule="evenodd" />
|
19
|
+
</svg>
|
20
|
+
<% end %>
|
21
|
+
<%= button_tag(
|
22
|
+
data: { action: "click->date-arrow-navigator#right" },
|
23
|
+
disabled: @right_disabled,
|
24
|
+
class: "relative -ml-px inline-flex items-center rounded-r-md bg-white px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
|
25
|
+
) do %>
|
26
|
+
<span class="sr-only">Next</span>
|
27
|
+
<svg
|
28
|
+
data-url="<%= @right_url %>"
|
29
|
+
class="size-5"
|
30
|
+
viewBox="0 0 20 20"
|
31
|
+
fill="currentColor"
|
32
|
+
aria-hidden="true"
|
33
|
+
data-slot="icon"
|
34
|
+
>
|
35
|
+
<path fill-rule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
|
36
|
+
</svg>
|
37
|
+
<% end %>
|
38
|
+
</span>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AhoyPanel
|
4
|
+
class DateArrowNavigatorComponent < ViewComponent::Base
|
5
|
+
def initialize(date: Time.zone.now.to_date)
|
6
|
+
@right_disabled = date.today?
|
7
|
+
|
8
|
+
@left_url = "/ahoy_panel?selected_date=#{date - 1.day}"
|
9
|
+
@right_url = "/ahoy_panel?selected_date=#{date + 1.day}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
<div
|
2
|
+
data-controller="dropdown"
|
3
|
+
class="relative inline-block text-left"
|
4
|
+
>
|
5
|
+
<div class="w-48">
|
6
|
+
<button
|
7
|
+
data-action="click->dropdown#open"
|
8
|
+
type="button"
|
9
|
+
class="inline-flex w-full justify-between gap-x-1.5 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50" id="menu-button" aria-expanded="true" aria-haspopup="true"
|
10
|
+
>
|
11
|
+
<%= @label %>
|
12
|
+
<svg class="-mr-1 size-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon">
|
13
|
+
<path fill-rule="evenodd" d="M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
|
14
|
+
</svg>
|
15
|
+
</button>
|
16
|
+
</div>
|
17
|
+
|
18
|
+
<!--
|
19
|
+
Dropdown menu, show/hide based on menu state.
|
20
|
+
|
21
|
+
Entering: "transition ease-out duration-100"
|
22
|
+
From: "transform opacity-0 scale-95"
|
23
|
+
To: "transform opacity-100 scale-100"
|
24
|
+
Leaving: "transition ease-in duration-75"
|
25
|
+
From: "transform opacity-100 scale-100"
|
26
|
+
To: "transform opacity-0 scale-95"
|
27
|
+
-->
|
28
|
+
<div
|
29
|
+
data-dropdown-target="items"
|
30
|
+
data-transition-enter="transition ease-out duration-100"
|
31
|
+
data-transition-enter-start="transform opacity-0 scale-95"
|
32
|
+
data-transition-enter-end="transform opacity-100 scale-100"
|
33
|
+
data-transition-leave="transition ease-in duration-75"
|
34
|
+
data-transition-leave-start="transform opacity-100 scale-100"
|
35
|
+
data-transition-leave-end="transform opacity-0 scale-95"
|
36
|
+
class="hidden absolute right-0 z-10 mt-2 w-56 origin-top-right divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black/5 focus:outline-none"
|
37
|
+
role="menu"
|
38
|
+
aria-orientation="vertical"
|
39
|
+
aria-labelledby="menu-button"
|
40
|
+
tabindex="-1"
|
41
|
+
>
|
42
|
+
<!-- Active: "bg-gray-100 text-gray-900 outline-none", Not Active: "text-gray-700" -->
|
43
|
+
<% @item_groups.each do |items| %>
|
44
|
+
<div class="py-1" role="none">
|
45
|
+
<% items.each do |item| %>
|
46
|
+
<%= link_to item.label, item.url, class: item.class, role: "menuitem", tabindex: "-1" %>
|
47
|
+
<% end %>
|
48
|
+
</div>
|
49
|
+
<% end %>
|
50
|
+
</div>
|
51
|
+
</div>
|
52
|
+
|
53
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AhoyPanel
|
4
|
+
class DropdownComponent < ViewComponent::Base
|
5
|
+
def initialize(item_groups:, date: Time.zone.now.to_date)
|
6
|
+
@item_groups = build_item_groups(item_groups)
|
7
|
+
@label = humanize_date(date)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
Item = Struct.new(:label, :url, :class)
|
13
|
+
|
14
|
+
CLASSES = {
|
15
|
+
active: "block px-4 py-2 text-sm bg-gray-100 text-gray-900 outline-none hover:bg-gray-100",
|
16
|
+
inactive: "block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
17
|
+
}
|
18
|
+
|
19
|
+
def build_item_groups(item_groups)
|
20
|
+
item_groups.map do |items|
|
21
|
+
items.map do |item|
|
22
|
+
Item.new(item[:label], item[:url], CLASSES[:inactive])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def humanize_date(date)
|
28
|
+
return "Today" if date.today?
|
29
|
+
|
30
|
+
return "Yesterday" if date.yesterday?
|
31
|
+
|
32
|
+
date
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<div class="justify-between gap-x-4 gap-y-2 bg-white px-4 py-10 sm:px-6 xl:px-8">
|
2
|
+
<dt class="text-xs/6 font-semibold text-gray-500 uppercase tracking-wide mb-2">
|
3
|
+
<%= @title %>
|
4
|
+
</dt>
|
5
|
+
<div class="flex items-center gap-x-4">
|
6
|
+
<dd class="flex-none text-xl/3 font-medium tracking-tight text-gray-900">
|
7
|
+
<%= @data %>
|
8
|
+
</dd>
|
9
|
+
<% if @change.nonzero? %>
|
10
|
+
<dd class="text-xs font-medium text-gray-700">
|
11
|
+
<div class="flex gap-x-1">
|
12
|
+
<% if @change.positive? %>
|
13
|
+
<%= heroicon "arrow-up-right", options: { class: "h-4 w-4 text-green-600" } %>
|
14
|
+
<% else %>
|
15
|
+
<%= heroicon "arrow-down-right", options: { class: "h-4 w-4 text-red-600" } %>
|
16
|
+
<% end %>
|
17
|
+
<%= @change %>%
|
18
|
+
</div>
|
19
|
+
</dd>
|
20
|
+
<% end %>
|
21
|
+
</div>
|
22
|
+
</div>
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module AhoyPanel
|
2
|
+
class DashboardController < ApplicationController
|
3
|
+
def index
|
4
|
+
@dropdown_item_groups = [
|
5
|
+
[{ label: "Today", url: "" }, { label: "Yesterday", url: "" }],
|
6
|
+
[{ label: "Last 7 Days", url: "" }, { label: "Last 30 Days", url: "" }],
|
7
|
+
[{ label: "Month to Date", url: "" }, { label: "Last Month", url: "" }],
|
8
|
+
[{ label: "Year to Date", url: "" }, { label: "Last 12 Months", url: "" }],
|
9
|
+
[{ label: "All Time", url: "" }, { label: "Custom Range", url: "" }],
|
10
|
+
[{ label: "Compare", url: "" }],
|
11
|
+
]
|
12
|
+
|
13
|
+
@selected_date = params[:selected_date]&.to_time&.to_date || Time.zone.now.to_date
|
14
|
+
@date_range = date_range
|
15
|
+
|
16
|
+
@visits = Ahoy::Visit.all
|
17
|
+
@events = Ahoy::Event.all
|
18
|
+
@visits_count = Ahoy::Visit.where("started_at > ?", 30.days.ago).size
|
19
|
+
@events_count = Ahoy::Event.where("time > ?", 30.days.ago).size
|
20
|
+
|
21
|
+
|
22
|
+
@unique_visitors_current = Ahoy::Visit.where(started_at: current_time_range)
|
23
|
+
@unique_visitors_before = Ahoy::Visit.where(started_at: before_time_range)
|
24
|
+
|
25
|
+
@total_visits_current = Ahoy::Visit.where(started_at: current_time_range)
|
26
|
+
@total_visits_before = Ahoy::Visit.where(started_at: before_time_range)
|
27
|
+
|
28
|
+
@unique_visitors_data = AhoyPanel::UniqueVisitorsData.new(
|
29
|
+
start_at: date_range.start_at, end_at: date_range.end_at
|
30
|
+
)
|
31
|
+
@total_visits_data = AhoyPanel::TotalVisitsData.new(
|
32
|
+
start_at: date_range.start_at, end_at: date_range.end_at
|
33
|
+
)
|
34
|
+
@total_pageviews_data = AhoyPanel::TotalPageviewsData.new(
|
35
|
+
start_at: date_range.start_at, end_at: date_range.end_at
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
DateRange = Struct.new(:start_at, :end_at)
|
42
|
+
|
43
|
+
def date_range
|
44
|
+
return @date_range if defined?(@date_range)
|
45
|
+
|
46
|
+
case params[:selected_date]
|
47
|
+
when "blah"
|
48
|
+
else
|
49
|
+
@date_range = DateRange.new(Time.zone.now.beginning_of_day, Time.zone.now.end_of_day)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def filter_date
|
54
|
+
Time.zone.today
|
55
|
+
end
|
56
|
+
|
57
|
+
def before_date
|
58
|
+
filter_date - 1.day
|
59
|
+
end
|
60
|
+
|
61
|
+
def current_time_range
|
62
|
+
filter_date.beginning_of_day..filter_date.end_of_day
|
63
|
+
end
|
64
|
+
|
65
|
+
def before_time_range
|
66
|
+
before_date.beginning_of_day..before_date.end_of_day
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AhoyPanel
|
4
|
+
module CountryHelper
|
5
|
+
EMOJI_MAP = { "AD": "🇦🇩", "AE": "🇦🇪", "AF": "🇦🇫", "AG": "🇦🇬", "AI": "🇦🇮", "AL": "🇦🇱", "AM": "🇦🇲", "AO": "🇦🇴", "AQ": "🇦🇶", "AR": "🇦🇷", "AS": "🇦🇸", "AT": "🇦🇹", "AU": "🇦🇺", "AW": "🇦🇼", "AX": "🇦🇽", "AZ": "🇦🇿", "BA": "🇧🇦", "BB": "🇧🇧", "BD": "🇧🇩", "BE": "🇧🇪", "BF": "🇧🇫", "BG": "🇧🇬", "BH": "🇧🇭", "BI": "🇧🇮", "BJ": "🇧🇯", "BL": "🇧🇱", "BM": "🇧🇲", "BN": "🇧🇳", "BO": "🇧🇴", "BQ": "🇧🇶", "BR": "🇧🇷", "BS": "🇧🇸", "BT": "🇧🇹", "BV": "🇧🇻", "BW": "🇧🇼", "BY": "🇧🇾", "BZ": "🇧🇿", "CA": "🇨🇦", "CC": "🇨🇨", "CD": "🇨🇩", "CF": "🇨🇫", "CG": "🇨🇬", "CH": "🇨🇭", "CI": "🇨🇮", "CK": "🇨🇰", "CL": "🇨🇱", "CM": "🇨🇲", "CN": "🇨🇳", "CO": "🇨🇴", "CR": "🇨🇷", "CU": "🇨🇺", "CV": "🇨🇻", "CW": "🇨🇼", "CX": "🇨🇽", "CY": "🇨🇾", "CZ": "🇨🇿", "DE": "🇩🇪", "DJ": "🇩🇯", "DK": "🇩🇰", "DM": "🇩🇲", "DO": "🇩🇴", "DZ": "🇩🇿", "EC": "🇪🇨", "EE": "🇪🇪", "EG": "🇪🇬", "EH": "🇪🇭", "ER": "🇪🇷", "ES": "🇪🇸", "ET": "🇪🇹", "FI": "🇫🇮", "FJ": "🇫🇯", "FK": "🇫🇰", "FM": "🇫🇲", "FO": "🇫🇴", "FR": "🇫🇷", "GA": "🇬🇦", "GB": "🇬🇧", "GD": "🇬🇩", "GE": "🇬🇪", "GF": "🇬🇫", "GG": "🇬🇬", "GH": "🇬🇭", "GI": "🇬🇮", "GL": "🇬🇱", "GM": "🇬🇲", "GN": "🇬🇳", "GP": "🇬🇵", "GQ": "🇬🇶", "GR": "🇬🇷", "GS": "🇬🇸", "GT": "🇬🇹", "GU": "🇬🇺", "GW": "🇬🇼", "GY": "🇬🇾", "HK": "🇭🇰", "HM": "🇭🇲", "HN": "🇭🇳", "HR": "🇭🇷", "HT": "🇭🇹", "HU": "🇭🇺", "ID": "🇮🇩", "IE": "🇮🇪", "IL": "🇮🇱", "IM": "🇮🇲", "IN": "🇮🇳", "IO": "🇮🇴", "IQ": "🇮🇶", "IR": "🇮🇷", "IS": "🇮🇸", "IT": "🇮🇹", "JE": "🇯🇪", "JM": "🇯🇲", "JO": "🇯🇴", "JP": "🇯🇵", "KE": "🇰🇪", "KG": "🇰🇬", "KH": "🇰🇭", "KI": "🇰🇮", "KM": "🇰🇲", "KN": "🇰🇳", "KP": "🇰🇵", "KR": "🇰🇷", "KW": "🇰🇼", "KY": "🇰🇾", "KZ": "🇰🇿", "LA": "🇱🇦", "LB": "🇱🇧", "LC": "🇱🇨", "LI": "🇱🇮", "LK": "🇱🇰", "LR": "🇱🇷", "LS": "🇱🇸", "LT": "🇱🇹", "LU": "🇱🇺", "LV": "🇱🇻", "LY": "🇱🇾", "MA": "🇲🇦", "MC": "🇲🇨", "MD": "🇲🇩", "ME": "🇲🇪", "MF": "🇲🇫", "MG": "🇲🇬", "MH": "🇲🇭", "MK": "🇲🇰", "ML": "🇲🇱", "MM": "🇲🇲", "MN": "🇲🇳", "MO": "🇲🇴", "MP": "🇲🇵", "MQ": "🇲🇶", "MR": "🇲🇷", "MS": "🇲🇸", "MT": "🇲🇹", "MU": "🇲🇺", "MV": "🇲🇻", "MW": "🇲🇼", "MX": "🇲🇽", "MY": "🇲🇾", "MZ": "🇲🇿", "NA": "🇳🇦", "NC": "🇳🇨", "NE": "🇳🇪", "NF": "🇳🇫", "NG": "🇳🇬", "NI": "🇳🇮", "NL": "🇳🇱", "NO": "🇳🇴", "NP": "🇳🇵", "NR": "🇳🇷", "NU": "🇳🇺", "NZ": "🇳🇿", "OM": "🇴🇲", "PA": "🇵🇦", "PE": "🇵🇪", "PF": "🇵🇫", "PG": "🇵🇬", "PH": "🇵🇭", "PK": "🇵🇰", "PL": "🇵🇱", "PM": "🇵🇲", "PN": "🇵🇳", "PR": "🇵🇷", "PS": "🇵🇸", "PT": "🇵🇹", "PW": "🇵🇼", "PY": "🇵🇾", "QA": "🇶🇦", "RE": "🇷🇪", "RO": "🇷🇴", "RS": "🇷🇸", "RU": "🇷🇺", "RW": "🇷🇼", "SA": "🇸🇦", "SB": "🇸🇧", "SC": "🇸🇨", "SD": "🇸🇩", "SE": "🇸🇪", "SG": "🇸🇬", "SH": "🇸🇭", "SI": "🇸🇮", "SJ": "🇸🇯", "SK": "🇸🇰", "SL": "🇸🇱", "SM": "🇸🇲", "SN": "🇸🇳", "SO": "🇸🇴", "SR": "🇸🇷", "SS": "🇸🇸", "ST": "🇸🇹", "SV": "🇸🇻", "SX": "🇸🇽", "SY": "🇸🇾", "SZ": "🇸🇿", "TC": "🇹🇨", "TD": "🇹🇩", "TF": "🇹🇫", "TG": "🇹🇬", "TH": "🇹🇭", "TJ": "🇹🇯", "TK": "🇹🇰", "TL": "🇹🇱", "TM": "🇹🇲", "TN": "🇹🇳", "TO": "🇹🇴", "TR": "🇹🇷", "TT": "🇹🇹", "TV": "🇹🇻", "TW": "🇹🇼", "TZ": "🇹🇿", "UA": "🇺🇦", "UG": "🇺🇬", "UM": "🇺🇲", "US": "🇺🇸", "UY": "🇺🇾", "UZ": "🇺🇿", "VA": "🇻🇦", "VC": "🇻🇨", "VE": "🇻🇪", "VG": "🇻🇬", "VI": "🇻🇮", "VN": "🇻🇳", "VU": "🇻🇺", "WF": "🇼🇫", "WS": "🇼🇸", "YE": "🇾🇪", "YT": "🇾🇹", "ZA": "🇿🇦", "ZM": "🇿🇲", "ZW": "🇿🇼" }
|
6
|
+
end
|
7
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AhoyPanel
|
4
|
+
module NavHelper
|
5
|
+
def active_nav_link?(page_name)
|
6
|
+
if controller_name == page_name
|
7
|
+
"border-blue-500 text-gray-900"
|
8
|
+
else
|
9
|
+
"border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
2
|
+
import { leave, toggle } from 'el-transition'
|
3
|
+
|
4
|
+
export default class extends Controller {
|
5
|
+
static targets = ['items']
|
6
|
+
|
7
|
+
connect() {
|
8
|
+
document.body.addEventListener('click', (e) => {
|
9
|
+
if (!this.element.contains(e.target)) {
|
10
|
+
leave(this.itemsTarget)
|
11
|
+
}
|
12
|
+
})
|
13
|
+
}
|
14
|
+
|
15
|
+
open() {
|
16
|
+
toggle(this.itemsTarget)
|
17
|
+
}
|
18
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
// Import and register all your controllers from the importmap under controllers/*
|
2
|
+
|
3
|
+
import { application } from "controllers/application"
|
4
|
+
|
5
|
+
// Eager load all controllers defined in the import map under controllers/**/*_controller
|
6
|
+
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
|
7
|
+
eagerLoadControllersFrom("controllers", application)
|
8
|
+
|
9
|
+
// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
|
10
|
+
// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
|
11
|
+
// lazyLoadControllersFrom("controllers", application)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
2
|
+
import { enter, leave, toggle } from 'el-transition'
|
3
|
+
|
4
|
+
export default class extends Controller {
|
5
|
+
static targets = ['button', 'mobileMenu', 'closedIcon', 'openedIcon']
|
6
|
+
|
7
|
+
toggle(e) {
|
8
|
+
toggle(this.mobileMenuTarget);
|
9
|
+
|
10
|
+
if (this.buttonTarget.dataset.open === 'false') {
|
11
|
+
this.closedIconTarget.classList.add('hidden')
|
12
|
+
this.openedIconTarget.classList.remove('hidden')
|
13
|
+
this.buttonTarget.dataset.open = 'true'
|
14
|
+
} else {
|
15
|
+
this.closedIconTarget.classList.remove('hidden')
|
16
|
+
this.openedIconTarget.classList.add('hidden')
|
17
|
+
this.buttonTarget.dataset.open = 'false'
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module AhoyPanel
|
2
|
+
class StatBoxData
|
3
|
+
def initialize(start_at:, end_at:)
|
4
|
+
@start_at = start_at
|
5
|
+
@end_at = end_at
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :start_at, :end_at
|
9
|
+
|
10
|
+
def to_params
|
11
|
+
{ title:, data:, change: }
|
12
|
+
end
|
13
|
+
|
14
|
+
def title
|
15
|
+
raise "Must implement"
|
16
|
+
end
|
17
|
+
|
18
|
+
def data
|
19
|
+
raise "Must implement"
|
20
|
+
end
|
21
|
+
|
22
|
+
def change
|
23
|
+
raise "Must implement"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module AhoyPanel
|
2
|
+
class TotalPageviewsData < StatBoxData
|
3
|
+
def title
|
4
|
+
"Total Pageviews"
|
5
|
+
end
|
6
|
+
|
7
|
+
def data
|
8
|
+
return @data if defined?(@data)
|
9
|
+
|
10
|
+
@data ||= Ahoy::Visit.where(started_at: start_at..end_at).size
|
11
|
+
end
|
12
|
+
|
13
|
+
def change
|
14
|
+
return @change if defined?(@change)
|
15
|
+
|
16
|
+
start_back_at = if start_at.to_date == end_at.to_date
|
17
|
+
start_back_days = 1
|
18
|
+
else
|
19
|
+
(end_at.to_date - start_at.to_date).to_i
|
20
|
+
end
|
21
|
+
|
22
|
+
start_range = (start_at - start_back_at.days)..(end_at - start_back_at.days)
|
23
|
+
end_range = start_at..end_at
|
24
|
+
|
25
|
+
start_count = Ahoy::Visit.where(started_at: start_range).size
|
26
|
+
end_count = Ahoy::Visit.where(started_at: end_range).size
|
27
|
+
|
28
|
+
@change = ((end_count - start_count).to_f / (start_count.zero? ? 1 : start_count) * 100).to_i
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|