better_page 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +62 -0
- data/MIT-LICENSE +20 -0
- data/README.md +357 -0
- data/Rakefile +3 -0
- data/docs/00-README.md +17 -0
- data/docs/01-getting-started.md +137 -0
- data/docs/02-component-registry.md +192 -0
- data/docs/03-base-pages.md +238 -0
- data/docs/04-schema-validation.md +180 -0
- data/docs/05-turbo-support.md +220 -0
- data/docs/06-compliance-analyzer.md +147 -0
- data/docs/07-configuration.md +157 -0
- data/guide/00-README.md +32 -0
- data/guide/01-quick-start.md +148 -0
- data/guide/02-building-index-page.md +258 -0
- data/guide/03-building-show-page.md +266 -0
- data/guide/04-building-form-page.md +309 -0
- data/guide/05-custom-pages.md +325 -0
- data/guide/06-best-practices.md +311 -0
- data/lib/better_page/base_page.rb +161 -0
- data/lib/better_page/compliance/analyzer.rb +409 -0
- data/lib/better_page/component_registry.rb +393 -0
- data/lib/better_page/config.rb +165 -0
- data/lib/better_page/configuration.rb +153 -0
- data/lib/better_page/custom_base_page.rb +85 -0
- data/lib/better_page/default_components.rb +200 -0
- data/lib/better_page/form_base_page.rb +170 -0
- data/lib/better_page/index_base_page.rb +69 -0
- data/lib/better_page/railtie.rb +34 -0
- data/lib/better_page/show_base_page.rb +120 -0
- data/lib/better_page/validation_error.rb +7 -0
- data/lib/better_page/version.rb +3 -0
- data/lib/better_page.rb +80 -0
- data/lib/generators/better_page/component_generator.rb +131 -0
- data/lib/generators/better_page/install_generator.rb +160 -0
- data/lib/generators/better_page/page_generator.rb +101 -0
- data/lib/generators/better_page/sync_generator.rb +109 -0
- data/lib/generators/better_page/templates/application_page.rb.tt +12 -0
- data/lib/generators/better_page/templates/better_page_initializer.rb.tt +53 -0
- data/lib/generators/better_page/templates/custom_base_page.rb.tt +83 -0
- data/lib/generators/better_page/templates/custom_page.rb.tt +31 -0
- data/lib/generators/better_page/templates/edit_page.rb.tt +46 -0
- data/lib/generators/better_page/templates/form_base_page.rb.tt +126 -0
- data/lib/generators/better_page/templates/index_base_page.rb.tt +65 -0
- data/lib/generators/better_page/templates/index_page.rb.tt +56 -0
- data/lib/generators/better_page/templates/javascript/controllers/app_nav_controller.js +57 -0
- data/lib/generators/better_page/templates/javascript/controllers/drawer_controller.js +99 -0
- data/lib/generators/better_page/templates/javascript/controllers/dropdown_controller.js +60 -0
- data/lib/generators/better_page/templates/javascript/controllers/index.js +36 -0
- data/lib/generators/better_page/templates/javascript/controllers/modal_controller.js +70 -0
- data/lib/generators/better_page/templates/javascript/controllers/sidebar_controller.js +152 -0
- data/lib/generators/better_page/templates/javascript/controllers/table_controller.js +60 -0
- data/lib/generators/better_page/templates/javascript/controllers/tabs_controller.js +89 -0
- data/lib/generators/better_page/templates/new_page.rb.tt +46 -0
- data/lib/generators/better_page/templates/show_base_page.rb.tt +117 -0
- data/lib/generators/better_page/templates/show_page.rb.tt +45 -0
- data/lib/generators/better_page/templates/view_components/application_view_component.rb.tt +7 -0
- data/lib/generators/better_page/templates/view_components/custom_view_component.html.erb.tt +21 -0
- data/lib/generators/better_page/templates/view_components/custom_view_component.rb.tt +21 -0
- data/lib/generators/better_page/templates/view_components/form_view_component.html.erb.tt +25 -0
- data/lib/generators/better_page/templates/view_components/form_view_component.rb.tt +23 -0
- data/lib/generators/better_page/templates/view_components/index_view_component.html.erb.tt +33 -0
- data/lib/generators/better_page/templates/view_components/index_view_component.rb.tt +29 -0
- data/lib/generators/better_page/templates/view_components/show_view_component.html.erb.tt +29 -0
- data/lib/generators/better_page/templates/view_components/show_view_component.rb.tt +25 -0
- data/lib/generators/better_page/templates/view_components/ui/alerts_component.html.erb.tt +47 -0
- data/lib/generators/better_page/templates/view_components/ui/alerts_component.rb.tt +47 -0
- data/lib/generators/better_page/templates/view_components/ui/content_section_component.html.erb.tt +42 -0
- data/lib/generators/better_page/templates/view_components/ui/content_section_component.rb.tt +34 -0
- data/lib/generators/better_page/templates/view_components/ui/drawer_component.html.erb.tt +73 -0
- data/lib/generators/better_page/templates/view_components/ui/drawer_component.rb.tt +78 -0
- data/lib/generators/better_page/templates/view_components/ui/errors_component.html.erb.tt +23 -0
- data/lib/generators/better_page/templates/view_components/ui/errors_component.rb.tt +18 -0
- data/lib/generators/better_page/templates/view_components/ui/field_component.html.erb.tt +65 -0
- data/lib/generators/better_page/templates/view_components/ui/field_component.rb.tt +91 -0
- data/lib/generators/better_page/templates/view_components/ui/footer_component.html.erb.tt +33 -0
- data/lib/generators/better_page/templates/view_components/ui/footer_component.rb.tt +32 -0
- data/lib/generators/better_page/templates/view_components/ui/header_component.html.erb.tt +55 -0
- data/lib/generators/better_page/templates/view_components/ui/header_component.rb.tt +39 -0
- data/lib/generators/better_page/templates/view_components/ui/modal_component.html.erb.tt +70 -0
- data/lib/generators/better_page/templates/view_components/ui/modal_component.rb.tt +54 -0
- data/lib/generators/better_page/templates/view_components/ui/overview_component.html.erb.tt +22 -0
- data/lib/generators/better_page/templates/view_components/ui/overview_component.rb.tt +71 -0
- data/lib/generators/better_page/templates/view_components/ui/pagination_component.html.erb.tt +63 -0
- data/lib/generators/better_page/templates/view_components/ui/pagination_component.rb.tt +69 -0
- data/lib/generators/better_page/templates/view_components/ui/panel_component.html.erb.tt +31 -0
- data/lib/generators/better_page/templates/view_components/ui/panel_component.rb.tt +23 -0
- data/lib/generators/better_page/templates/view_components/ui/statistics_component.html.erb.tt +33 -0
- data/lib/generators/better_page/templates/view_components/ui/statistics_component.rb.tt +51 -0
- data/lib/generators/better_page/templates/view_components/ui/table_component.html.erb.tt +112 -0
- data/lib/generators/better_page/templates/view_components/ui/table_component.rb.tt +88 -0
- data/lib/generators/better_page/templates/view_components/ui/tabs_component.html.erb.tt +52 -0
- data/lib/generators/better_page/templates/view_components/ui/tabs_component.rb.tt +76 -0
- data/lib/generators/better_page/templates/view_components/ui/widget_component.html.erb.tt +72 -0
- data/lib/generators/better_page/templates/view_components/ui/widget_component.rb.tt +34 -0
- data/lib/tasks/better_page.rake +70 -0
- data/lib/tasks/better_page_tasks.rake +4 -0
- metadata +188 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# Building Form Pages
|
|
2
|
+
|
|
3
|
+
A complete guide to building new and edit form pages.
|
|
4
|
+
|
|
5
|
+
### Basic Form Page Structure
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
class Products::NewPage < FormBasePage
|
|
9
|
+
def initialize(product, metadata = {})
|
|
10
|
+
@product = product
|
|
11
|
+
@user = metadata[:user]
|
|
12
|
+
super(product, metadata)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def header
|
|
18
|
+
{ title: "New Product" }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def panels
|
|
22
|
+
[
|
|
23
|
+
panel_format(title: "Basic Info", fields: basic_fields)
|
|
24
|
+
]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
--------------------------------
|
|
30
|
+
|
|
31
|
+
### Adding Header with Description
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
def header
|
|
35
|
+
{
|
|
36
|
+
title: "New Product",
|
|
37
|
+
description: "Create a new product in your catalog",
|
|
38
|
+
breadcrumbs: [
|
|
39
|
+
{ label: "Products", path: products_path },
|
|
40
|
+
{ label: "New" }
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
--------------------------------
|
|
47
|
+
|
|
48
|
+
### Using field_format Helper
|
|
49
|
+
|
|
50
|
+
Build form fields with the helper method.
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
field_format(
|
|
54
|
+
name: :email,
|
|
55
|
+
type: :email, # :text, :email, :password, :number, :textarea, :select, :checkbox, :radio
|
|
56
|
+
label: "Email Address",
|
|
57
|
+
required: true,
|
|
58
|
+
placeholder: "Enter email",
|
|
59
|
+
hint: "We'll never share your email"
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
--------------------------------
|
|
64
|
+
|
|
65
|
+
### Field Types
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
# Text input
|
|
69
|
+
field_format(name: :name, type: :text, label: "Name", required: true)
|
|
70
|
+
|
|
71
|
+
# Email input
|
|
72
|
+
field_format(name: :email, type: :email, label: "Email")
|
|
73
|
+
|
|
74
|
+
# Password input
|
|
75
|
+
field_format(name: :password, type: :password, label: "Password")
|
|
76
|
+
|
|
77
|
+
# Number input
|
|
78
|
+
field_format(name: :price, type: :number, label: "Price", min: 0, step: 0.01)
|
|
79
|
+
|
|
80
|
+
# Textarea
|
|
81
|
+
field_format(name: :description, type: :textarea, label: "Description", rows: 5)
|
|
82
|
+
|
|
83
|
+
# Select dropdown
|
|
84
|
+
field_format(name: :category, type: :select, label: "Category", options: category_options)
|
|
85
|
+
|
|
86
|
+
# Checkbox
|
|
87
|
+
field_format(name: :active, type: :checkbox, label: "Active")
|
|
88
|
+
|
|
89
|
+
# Radio buttons
|
|
90
|
+
field_format(name: :status, type: :radio, label: "Status", options: status_options)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
--------------------------------
|
|
94
|
+
|
|
95
|
+
### Using panel_format Helper
|
|
96
|
+
|
|
97
|
+
Build form panels with the helper method.
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
panel_format(
|
|
101
|
+
title: "Basic Information",
|
|
102
|
+
description: "Enter the product details",
|
|
103
|
+
fields: [
|
|
104
|
+
field_format(name: :name, type: :text, label: "Name", required: true),
|
|
105
|
+
field_format(name: :price, type: :number, label: "Price", required: true)
|
|
106
|
+
]
|
|
107
|
+
)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
--------------------------------
|
|
111
|
+
|
|
112
|
+
### Important: Separate Checkbox Panels
|
|
113
|
+
|
|
114
|
+
Checkbox and radio fields MUST be in separate panels from text inputs.
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
def panels
|
|
118
|
+
[
|
|
119
|
+
# Text inputs panel
|
|
120
|
+
panel_format(
|
|
121
|
+
title: "Product Details",
|
|
122
|
+
fields: [
|
|
123
|
+
field_format(name: :name, type: :text, label: "Name", required: true),
|
|
124
|
+
field_format(name: :price, type: :number, label: "Price"),
|
|
125
|
+
field_format(name: :category, type: :select, label: "Category", options: categories)
|
|
126
|
+
]
|
|
127
|
+
),
|
|
128
|
+
# Checkbox panel - MUST be separate
|
|
129
|
+
panel_format(
|
|
130
|
+
title: "Settings",
|
|
131
|
+
fields: [
|
|
132
|
+
field_format(name: :active, type: :checkbox, label: "Active"),
|
|
133
|
+
field_format(name: :featured, type: :checkbox, label: "Featured on Homepage")
|
|
134
|
+
]
|
|
135
|
+
)
|
|
136
|
+
]
|
|
137
|
+
end
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
--------------------------------
|
|
141
|
+
|
|
142
|
+
### Select Field with Options
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
def panels
|
|
146
|
+
[
|
|
147
|
+
panel_format(
|
|
148
|
+
title: "Details",
|
|
149
|
+
fields: [
|
|
150
|
+
field_format(
|
|
151
|
+
name: :category_id,
|
|
152
|
+
type: :select,
|
|
153
|
+
label: "Category",
|
|
154
|
+
options: category_options,
|
|
155
|
+
include_blank: "Select a category"
|
|
156
|
+
)
|
|
157
|
+
]
|
|
158
|
+
)
|
|
159
|
+
]
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def category_options
|
|
163
|
+
[
|
|
164
|
+
{ value: 1, label: "Electronics" },
|
|
165
|
+
{ value: 2, label: "Clothing" },
|
|
166
|
+
{ value: 3, label: "Books" }
|
|
167
|
+
]
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
--------------------------------
|
|
172
|
+
|
|
173
|
+
### Configuring Footer
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
def footer
|
|
177
|
+
{
|
|
178
|
+
primary_action: {
|
|
179
|
+
label: "Save Product",
|
|
180
|
+
style: "primary"
|
|
181
|
+
},
|
|
182
|
+
secondary_actions: [
|
|
183
|
+
{ label: "Cancel", path: products_path, style: "secondary" },
|
|
184
|
+
{ label: "Save as Draft", action: :draft, style: "outline" }
|
|
185
|
+
]
|
|
186
|
+
}
|
|
187
|
+
end
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
--------------------------------
|
|
191
|
+
|
|
192
|
+
### Edit Page Example
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
class Products::EditPage < FormBasePage
|
|
196
|
+
def initialize(product, metadata = {})
|
|
197
|
+
@product = product
|
|
198
|
+
@user = metadata[:user]
|
|
199
|
+
super(product, metadata)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
private
|
|
203
|
+
|
|
204
|
+
def header
|
|
205
|
+
{
|
|
206
|
+
title: "Edit #{@product.name}",
|
|
207
|
+
breadcrumbs: [
|
|
208
|
+
{ label: "Products", path: products_path },
|
|
209
|
+
{ label: @product.name, path: product_path(@product) },
|
|
210
|
+
{ label: "Edit" }
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def panels
|
|
216
|
+
[
|
|
217
|
+
panel_format(title: "Product Details", fields: detail_fields),
|
|
218
|
+
panel_format(title: "Pricing", fields: pricing_fields),
|
|
219
|
+
panel_format(title: "Settings", fields: settings_fields)
|
|
220
|
+
]
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def detail_fields
|
|
224
|
+
[
|
|
225
|
+
field_format(name: :name, type: :text, label: "Name", required: true, value: @product.name),
|
|
226
|
+
field_format(name: :description, type: :textarea, label: "Description", value: @product.description)
|
|
227
|
+
]
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def pricing_fields
|
|
231
|
+
[
|
|
232
|
+
field_format(name: :price, type: :number, label: "Price", value: @product.price, min: 0),
|
|
233
|
+
field_format(name: :compare_price, type: :number, label: "Compare at Price", value: @product.compare_price)
|
|
234
|
+
]
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def settings_fields
|
|
238
|
+
[
|
|
239
|
+
field_format(name: :active, type: :checkbox, label: "Active", checked: @product.active?),
|
|
240
|
+
field_format(name: :featured, type: :checkbox, label: "Featured", checked: @product.featured?)
|
|
241
|
+
]
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
--------------------------------
|
|
247
|
+
|
|
248
|
+
### Complete New Page Example
|
|
249
|
+
|
|
250
|
+
```ruby
|
|
251
|
+
class Products::NewPage < FormBasePage
|
|
252
|
+
def initialize(product, metadata = {})
|
|
253
|
+
@product = product
|
|
254
|
+
@user = metadata[:user]
|
|
255
|
+
@categories = metadata[:categories] || []
|
|
256
|
+
super(product, metadata)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
private
|
|
260
|
+
|
|
261
|
+
def header
|
|
262
|
+
{
|
|
263
|
+
title: "New Product",
|
|
264
|
+
description: "Add a new product to your catalog",
|
|
265
|
+
breadcrumbs: [{ label: "Products", path: products_path }, { label: "New" }]
|
|
266
|
+
}
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def panels
|
|
270
|
+
[
|
|
271
|
+
panel_format(
|
|
272
|
+
title: "Basic Information",
|
|
273
|
+
description: "Enter the product details",
|
|
274
|
+
fields: [
|
|
275
|
+
field_format(name: :name, type: :text, label: "Product Name", required: true),
|
|
276
|
+
field_format(name: :sku, type: :text, label: "SKU"),
|
|
277
|
+
field_format(name: :category_id, type: :select, label: "Category", options: category_options),
|
|
278
|
+
field_format(name: :description, type: :textarea, label: "Description", rows: 4)
|
|
279
|
+
]
|
|
280
|
+
),
|
|
281
|
+
panel_format(
|
|
282
|
+
title: "Pricing & Inventory",
|
|
283
|
+
fields: [
|
|
284
|
+
field_format(name: :price, type: :number, label: "Price", required: true, min: 0, step: 0.01),
|
|
285
|
+
field_format(name: :stock, type: :number, label: "Stock Quantity", min: 0)
|
|
286
|
+
]
|
|
287
|
+
),
|
|
288
|
+
panel_format(
|
|
289
|
+
title: "Settings",
|
|
290
|
+
fields: [
|
|
291
|
+
field_format(name: :active, type: :checkbox, label: "Active"),
|
|
292
|
+
field_format(name: :featured, type: :checkbox, label: "Featured on Homepage")
|
|
293
|
+
]
|
|
294
|
+
)
|
|
295
|
+
]
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def footer
|
|
299
|
+
{
|
|
300
|
+
primary_action: { label: "Create Product", style: "primary" },
|
|
301
|
+
secondary_actions: [{ label: "Cancel", path: products_path, style: "secondary" }]
|
|
302
|
+
}
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def category_options
|
|
306
|
+
@categories.map { |c| { value: c.id, label: c.name } }
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
```
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# Building Custom Pages
|
|
2
|
+
|
|
3
|
+
A complete guide to building dashboards, reports, and other custom pages.
|
|
4
|
+
|
|
5
|
+
### Basic Custom Page Structure
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
class Admin::DashboardPage < CustomBasePage
|
|
9
|
+
def initialize(data, metadata = {})
|
|
10
|
+
@data = data
|
|
11
|
+
@user = metadata[:user]
|
|
12
|
+
super(data, metadata)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def header
|
|
18
|
+
{ title: "Dashboard" }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def content
|
|
22
|
+
{ widgets: [] }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
--------------------------------
|
|
28
|
+
|
|
29
|
+
### Adding Widgets
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
def content
|
|
33
|
+
{
|
|
34
|
+
widgets: [
|
|
35
|
+
widget_format(title: "Total Users", type: :counter, data: { value: @data[:users_count] }),
|
|
36
|
+
widget_format(title: "Total Orders", type: :counter, data: { value: @data[:orders_count] }),
|
|
37
|
+
widget_format(title: "Revenue", type: :counter, data: { value: format_currency(@data[:revenue]) })
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
--------------------------------
|
|
44
|
+
|
|
45
|
+
### Using widget_format Helper
|
|
46
|
+
|
|
47
|
+
Build widgets with the helper method.
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
widget_format(
|
|
51
|
+
title: "Active Users",
|
|
52
|
+
type: :counter, # :counter, :chart, :list, :table
|
|
53
|
+
data: { value: 1234 },
|
|
54
|
+
color: "blue",
|
|
55
|
+
icon: "users"
|
|
56
|
+
)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
--------------------------------
|
|
60
|
+
|
|
61
|
+
### Adding Charts
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
def content
|
|
65
|
+
{
|
|
66
|
+
widgets: [
|
|
67
|
+
chart_format(
|
|
68
|
+
title: "Revenue Over Time",
|
|
69
|
+
type: :line,
|
|
70
|
+
data: {
|
|
71
|
+
labels: @data[:months],
|
|
72
|
+
datasets: [
|
|
73
|
+
{ label: "Revenue", data: @data[:revenue_by_month], color: "blue" }
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
--------------------------------
|
|
83
|
+
|
|
84
|
+
### Using chart_format Helper
|
|
85
|
+
|
|
86
|
+
Build charts with the helper method.
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
chart_format(
|
|
90
|
+
title: "Sales Chart",
|
|
91
|
+
type: :line, # :line, :bar, :pie, :doughnut, :area
|
|
92
|
+
data: {
|
|
93
|
+
labels: ["Jan", "Feb", "Mar", "Apr"],
|
|
94
|
+
datasets: [
|
|
95
|
+
{ label: "Sales", data: [100, 200, 150, 300], color: "blue" },
|
|
96
|
+
{ label: "Returns", data: [10, 20, 15, 30], color: "red" }
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
--------------------------------
|
|
103
|
+
|
|
104
|
+
### Chart Types
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
# Line Chart
|
|
108
|
+
chart_format(title: "Trend", type: :line, data: chart_data)
|
|
109
|
+
|
|
110
|
+
# Bar Chart
|
|
111
|
+
chart_format(title: "Comparison", type: :bar, data: chart_data)
|
|
112
|
+
|
|
113
|
+
# Pie Chart
|
|
114
|
+
chart_format(title: "Distribution", type: :pie, data: pie_data)
|
|
115
|
+
|
|
116
|
+
# Doughnut Chart
|
|
117
|
+
chart_format(title: "Breakdown", type: :doughnut, data: pie_data)
|
|
118
|
+
|
|
119
|
+
# Area Chart
|
|
120
|
+
chart_format(title: "Growth", type: :area, data: chart_data)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
--------------------------------
|
|
124
|
+
|
|
125
|
+
### Adding List Widget
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
def content
|
|
129
|
+
{
|
|
130
|
+
widgets: [
|
|
131
|
+
widget_format(
|
|
132
|
+
title: "Recent Orders",
|
|
133
|
+
type: :list,
|
|
134
|
+
data: {
|
|
135
|
+
items: @data[:recent_orders].map do |order|
|
|
136
|
+
{
|
|
137
|
+
title: "Order ##{order.id}",
|
|
138
|
+
subtitle: order.customer_name,
|
|
139
|
+
value: format_currency(order.total),
|
|
140
|
+
path: order_path(order)
|
|
141
|
+
}
|
|
142
|
+
end
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
end
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
--------------------------------
|
|
151
|
+
|
|
152
|
+
### Adding Table Widget
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
def content
|
|
156
|
+
{
|
|
157
|
+
widgets: [
|
|
158
|
+
widget_format(
|
|
159
|
+
title: "Top Products",
|
|
160
|
+
type: :table,
|
|
161
|
+
data: {
|
|
162
|
+
columns: [
|
|
163
|
+
{ key: :name, label: "Product" },
|
|
164
|
+
{ key: :sales, label: "Sales" },
|
|
165
|
+
{ key: :revenue, label: "Revenue" }
|
|
166
|
+
],
|
|
167
|
+
rows: @data[:top_products]
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
end
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
--------------------------------
|
|
176
|
+
|
|
177
|
+
### Dashboard with Grid Layout
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
def content
|
|
181
|
+
{
|
|
182
|
+
layout: :grid,
|
|
183
|
+
columns: 3,
|
|
184
|
+
widgets: [
|
|
185
|
+
# Row 1: Statistics
|
|
186
|
+
widget_format(title: "Users", type: :counter, data: { value: @data[:users] }),
|
|
187
|
+
widget_format(title: "Orders", type: :counter, data: { value: @data[:orders] }),
|
|
188
|
+
widget_format(title: "Revenue", type: :counter, data: { value: @data[:revenue] }),
|
|
189
|
+
|
|
190
|
+
# Row 2: Charts (span 2 columns)
|
|
191
|
+
chart_format(title: "Sales Trend", type: :line, data: sales_chart_data, span: 2),
|
|
192
|
+
chart_format(title: "Categories", type: :pie, data: category_chart_data, span: 1),
|
|
193
|
+
|
|
194
|
+
# Row 3: Lists
|
|
195
|
+
widget_format(title: "Recent Orders", type: :list, data: orders_data, span: 2),
|
|
196
|
+
widget_format(title: "Top Products", type: :list, data: products_data, span: 1)
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
end
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
--------------------------------
|
|
203
|
+
|
|
204
|
+
### Reports Page Example
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
class Reports::SalesPage < CustomBasePage
|
|
208
|
+
def initialize(report_data, metadata = {})
|
|
209
|
+
@report_data = report_data
|
|
210
|
+
@user = metadata[:user]
|
|
211
|
+
@period = metadata[:period]
|
|
212
|
+
super(report_data, metadata)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
private
|
|
216
|
+
|
|
217
|
+
def header
|
|
218
|
+
{
|
|
219
|
+
title: "Sales Report",
|
|
220
|
+
description: "Sales performance for #{@period}",
|
|
221
|
+
actions: [
|
|
222
|
+
{ label: "Export PDF", path: export_report_path(format: :pdf), icon: "download" },
|
|
223
|
+
{ label: "Export CSV", path: export_report_path(format: :csv), icon: "file" }
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def content
|
|
229
|
+
{
|
|
230
|
+
widgets: [
|
|
231
|
+
# Summary statistics
|
|
232
|
+
widget_format(title: "Total Sales", type: :counter, data: { value: @report_data[:total_sales] }),
|
|
233
|
+
widget_format(title: "Orders", type: :counter, data: { value: @report_data[:order_count] }),
|
|
234
|
+
widget_format(title: "Avg Order Value", type: :counter, data: { value: @report_data[:avg_order] }),
|
|
235
|
+
|
|
236
|
+
# Charts
|
|
237
|
+
chart_format(title: "Daily Sales", type: :line, data: daily_sales_data),
|
|
238
|
+
chart_format(title: "Sales by Category", type: :pie, data: category_data),
|
|
239
|
+
|
|
240
|
+
# Tables
|
|
241
|
+
widget_format(title: "Top Selling Products", type: :table, data: top_products_data)
|
|
242
|
+
]
|
|
243
|
+
}
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def daily_sales_data
|
|
247
|
+
{
|
|
248
|
+
labels: @report_data[:dates],
|
|
249
|
+
datasets: [{ label: "Sales", data: @report_data[:daily_totals], color: "blue" }]
|
|
250
|
+
}
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def category_data
|
|
254
|
+
{
|
|
255
|
+
labels: @report_data[:categories].keys,
|
|
256
|
+
datasets: [{ data: @report_data[:categories].values }]
|
|
257
|
+
}
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def top_products_data
|
|
261
|
+
{
|
|
262
|
+
columns: [
|
|
263
|
+
{ key: :name, label: "Product" },
|
|
264
|
+
{ key: :quantity, label: "Qty Sold" },
|
|
265
|
+
{ key: :revenue, label: "Revenue" }
|
|
266
|
+
],
|
|
267
|
+
rows: @report_data[:top_products]
|
|
268
|
+
}
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
--------------------------------
|
|
274
|
+
|
|
275
|
+
### Complete Dashboard Example
|
|
276
|
+
|
|
277
|
+
```ruby
|
|
278
|
+
class Admin::DashboardPage < CustomBasePage
|
|
279
|
+
def initialize(stats, metadata = {})
|
|
280
|
+
@stats = stats
|
|
281
|
+
@user = metadata[:user]
|
|
282
|
+
super(stats, metadata)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
private
|
|
286
|
+
|
|
287
|
+
def header
|
|
288
|
+
{
|
|
289
|
+
title: "Dashboard",
|
|
290
|
+
description: "Welcome back, #{@user.name}"
|
|
291
|
+
}
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def content
|
|
295
|
+
{
|
|
296
|
+
layout: :grid,
|
|
297
|
+
columns: 4,
|
|
298
|
+
widgets: statistics_widgets + chart_widgets + list_widgets
|
|
299
|
+
}
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def statistics_widgets
|
|
303
|
+
[
|
|
304
|
+
widget_format(title: "Users", type: :counter, data: { value: @stats[:users], change: "+12%" }, icon: "users", color: "blue"),
|
|
305
|
+
widget_format(title: "Orders", type: :counter, data: { value: @stats[:orders], change: "+8%" }, icon: "cart", color: "green"),
|
|
306
|
+
widget_format(title: "Revenue", type: :counter, data: { value: format_currency(@stats[:revenue]), change: "+15%" }, icon: "dollar", color: "purple"),
|
|
307
|
+
widget_format(title: "Conversion", type: :counter, data: { value: "#{@stats[:conversion]}%", change: "-2%" }, icon: "chart", color: "yellow")
|
|
308
|
+
]
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def chart_widgets
|
|
312
|
+
[
|
|
313
|
+
chart_format(title: "Revenue Trend", type: :area, data: revenue_chart_data, span: 2),
|
|
314
|
+
chart_format(title: "Orders by Status", type: :doughnut, data: orders_chart_data, span: 2)
|
|
315
|
+
]
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def list_widgets
|
|
319
|
+
[
|
|
320
|
+
widget_format(title: "Recent Orders", type: :list, data: { items: recent_orders }, span: 2),
|
|
321
|
+
widget_format(title: "Low Stock", type: :list, data: { items: low_stock_items }, span: 2)
|
|
322
|
+
]
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
```
|