Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7775c9b
first shot
lodewiges Jan 21, 2026
a884ff7
run yarn install
lodewiges Jan 21, 2026
cb7b4ed
run the migrations
lodewiges Jan 21, 2026
b010cf4
fix edit mode button
lodewiges Jan 21, 2026
b0924b3
Merge branch 'staging' into feature/redesign-orderscreen
lodewiges Feb 1, 2026
d236a84
puntjes op de i
lodewiges Feb 3, 2026
b2974c7
Merge branch 'feature/redesign-orderscreen' of https://github.com/csv…
lodewiges Feb 3, 2026
dcdba74
fix some lint
lodewiges Feb 4, 2026
4b8babb
remove comments
lodewiges Feb 4, 2026
53c7df1
remove active session logic
lodewiges Feb 4, 2026
a5dedaa
re add a couple comments
lodewiges Feb 4, 2026
b7a3961
run yarn install
lodewiges Feb 4, 2026
4369fc5
lint fixes
lodewiges Feb 4, 2026
5329711
Update app/javascript/order_screen.js
lodewiges Feb 4, 2026
51ecf4d
Update app/controllers/product_prices_controller.rb
lodewiges Feb 4, 2026
99955cf
Update app/javascript/order_screen.js
lodewiges Feb 4, 2026
c5bd856
Update app/javascript/order_screen.js
lodewiges Feb 4, 2026
08d6ae5
implement copilot changes
lodewiges Feb 4, 2026
ab71c13
Apply suggestions from code review
lodewiges Feb 4, 2026
4f2c9a5
some more suggestions
lodewiges Feb 4, 2026
20bd86c
some more improvements
lodewiges Feb 4, 2026
adf39c0
Merge branch 'feature/redesign-orderscreen' of https://github.com/csv…
lodewiges Feb 4, 2026
acfa7a6
Update app/models/price_list.rb
lodewiges Feb 4, 2026
a0091fb
Update app/javascript/order_screen.js
lodewiges Feb 4, 2026
778d290
Update app/models/product_price_folder.rb
lodewiges Feb 6, 2026
c606335
Merge branch 'staging' into feature/redesign-orderscreen
lodewiges Sep 3, 2026
edb96b5
fix ai suggestions
lodewiges Sep 3, 2026
7f16574
rename product price
lodewiges Sep 3, 2026
572f1f1
Apply suggestion from @Copilot
lodewiges Sep 3, 2026
3564f1e
Apply suggestion from @Copilot
lodewiges Sep 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 191 additions & 3 deletions app/assets/stylesheets/order_screen.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,27 @@

.flash {
display: flex;
align-self: flex-end;
height: 100%;
position: fixed;
bottom: 1.5rem;
right: 1.5rem;
z-index: 1000;
height: auto;
min-width: 250px;
max-width: 450px;
font-size: $font-size-lg;
vertical-align: middle;
color: $font-color-light;
text-align: center;
background-color: $gray-800;
border-radius: 0.5rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.flash-text {
display: flex;
align-items: center;
justify-content: center;
padding: 0 1rem;
padding: 1rem 1.5rem;

a {
&:hover {
Expand Down Expand Up @@ -112,6 +119,11 @@
}
}

.header-actions {
display: flex;
flex-direction: row-reverse;
}

.side-panel {
z-index: 4;
display: flex;
Expand All @@ -124,6 +136,12 @@
background-color: $gray-200;
border-right: 2px solid $gray-400;
box-shadow: 2px 0 4px -2px $transparent-200;

&.edit-mode-disabled {
opacity: 0.5;
pointer-events: none;
user-select: none;
}
}

.user-details {
Expand Down Expand Up @@ -241,6 +259,11 @@
gap: 2px;
justify-content: flex-start;
background-color: $gray-400;
grid-auto-flow: dense;
}

.products-container {
display: contents;
}

.product-grid-product {
Expand Down Expand Up @@ -279,4 +302,169 @@
grid-area: order-grid;
overflow-y: auto;
}

.folder-container {
display: contents;
}

.folder-tile {
position: relative;
background-color: $gray-600;
color: $white;

.folder-icon {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 0.5rem;
font-size: 3.5rem;
position: relative;
}

.folder-back-arrow {
position: absolute;
font-size: 1rem;
bottom: 0;
right: -0.5rem;
color: $white;
background: rgba(0, 0, 0, 0.5);
border-radius: 50%;
padding: 0.2rem;
}

.product-grid-product-name {
color: $font-color-dark;
font-size: $font-size-lg;
text-shadow: none;
}

&.edit-mode {
cursor: grab;
}
}

.folder-edit-btn {
position: absolute;
top: 8px;
right: 8px;
width: 56px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(255, 255, 255, 0.9);
border-radius: 50%;
cursor: pointer;
font-size: 1.6rem;
color: $gray-700;
transition: all 0.2s ease;

&:hover {
background-color: $white;
transform: scale(1.1);
}
}

.add-folder-tile {
background-color: $gray-400;
border: 2px dashed $gray-600;

.folder-icon {
color: $gray-600;
}

.product-grid-product-name {
color: $gray-600;
}

&:hover {
background-color: $gray-300;
}
}

.drop-home-tile {
background-color: $gray-500;
border: 2px dashed $gray-700;
}

.back-button-tile {
pointer-events: auto;

.folder-icon {
font-size: 2rem;
}
}

.draggable {
cursor: grab;

&:active {
cursor: grabbing;
}
}

.drag-handle {
position: absolute;
top: 8px;
left: 8px;
color: rgba(0, 0, 0, 0.3);
font-size: 1rem;
}

.sortable-ghost {
opacity: 0.4;
}

.sortable-chosen {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}

.sortable-drag {
background-color: $white;
}

.folder-modal-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1050;
}

.folder-modal {
background-color: $white;
border-radius: 8px;
width: 90%;
max-width: 400px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}

.folder-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
border-bottom: 1px solid $gray-300;

h5 {
margin: 0;
}
}

.folder-modal-body {
padding: 1rem;
}

.folder-modal-footer {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
padding: 1rem;
border-top: 1px solid $gray-300;
}
}
12 changes: 10 additions & 2 deletions app/controllers/activities_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,25 @@ def destroy
def order_screen # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
authorize Activity

@activity = Activity.includes([:price_list, { price_list: { product_price: :product } }])
@activity = Activity.includes([:price_list, { price_list: { product_prices: :product } }])
.find(params[:id])

@product_prices_json = sorted_product_price(@activity).to_json(
only: %i[id price position product_price_folder_id],
include: { product: { only: %i[id name category color], methods: %i[requires_age] } }
)

@folders_json = @activity.price_list.product_price_folders.order(:position).to_json(
only: %i[id name position color]
)

@users_json = users_hash.to_json

@activity_json = @activity.to_json(only: %i[id title start_time end_time])

@is_treasurer = current_user.treasurer?
@price_list_id = @activity.price_list_id

@sumup_key = Rails.application.config.x.sumup_key
@sumup_enabled = @sumup_key.present?

Expand Down Expand Up @@ -174,7 +182,7 @@ def users_hash
end

def sorted_product_price(activity)
activity.price_list.product_price.sort_by { |p| p.product.id }
activity.price_list.product_prices.includes(:product).order(:position)
end

def activity_params
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/errors_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ def unacceptable
render_error_page('errors/unacceptable', :not_acceptable, '406 Not Acceptable')
end

def unprocessable_entity
render_error_page('errors/unprocessable_entity', :unprocessable_entity, '422 Unprocessable Entity')
def unprocessable_content
render_error_page('errors/unprocessable_content', :unprocessable_content, '422 Unprocessable Entity')
end

def internal_server_error
Expand Down
78 changes: 78 additions & 0 deletions app/controllers/product_price_folders_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
class ProductPriceFoldersController < ApplicationController
before_action :authenticate_user!
before_action :set_price_list, only: %i[index create reorder]
before_action :set_folder, only: %i[update destroy]

def index
authorize ProductPriceFolder, :index?
@folders = @price_list.product_price_folders.order(:position)
render json: @folders
end

def create
@folder = @price_list.product_price_folders.new(folder_params)
authorize @folder

if @folder.save
render json: @folder, status: :created
else
render json: { errors: @folder.errors.full_messages }, status: :unprocessable_content
end
end

def update
authorize @folder

if @folder.update(folder_params)
render json: @folder
else
render json: { errors: @folder.errors.full_messages }, status: :unprocessable_content
end
end

def destroy
authorize @folder

orphaned_products = @folder.product_prices
max_position = @folder.price_list.product_prices.without_folder.maximum(:position) || -1

orphaned_products.each_with_index do |product_prices, index|
product_prices.update(product_price_folder_id: nil, position: max_position + index + 1)
end

@folder.destroy

head :no_content
end
Comment thread
lodewiges marked this conversation as resolved.

def reorder # rubocop:disable Metrics/MethodLength
authorize ProductPriceFolder, :reorder?

folder_positions = params.require(:folder_positions)

ActiveRecord::Base.transaction do
folder_positions.each do |folder_data|
folder = @price_list.product_price_folders.find(folder_data[:id])

Copilot AI Feb 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reorder action only authorizes at the class level (ProductPriceFolder) but doesn't authorize each individual folder being reordered. While this might be acceptable since all folders belong to the same price list, consider adding authorization for each folder to ensure consistency with the ProductPricesController pattern (line 32 in product_prices_controller.rb), which authorizes each product_price individually in its reorder action.

Suggested change
folder = @price_list.product_price_folders.find(folder_data[:id])
folder = @price_list.product_price_folders.find(folder_data[:id])
authorize folder, :reorder?

Copilot uses AI. Check for mistakes.
folder.update!(position: folder_data[:position])
end
end

render json: { success: true }
rescue ActiveRecord::RecordInvalid => e
render json: { errors: [e.message] }, status: :unprocessable_content
end

private

def set_price_list
@price_list = PriceList.find(params[:price_list_id])
end

def set_folder
@folder = ProductPriceFolder.find(params[:id])
end

def folder_params
params.require(:product_price_folder).permit(:name, :color, :position)
end
end
Comment thread
lodewiges marked this conversation as resolved.
Comment on lines +1 to +78

Copilot AI Feb 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new ProductPriceFoldersController lacks test coverage. The codebase has comprehensive test coverage for other controllers (as evidenced by the spec/controllers directory), but there are no tests for this new controller. Tests should be added to cover the index, create, update, destroy, and reorder actions, including authorization checks for different user roles.

Copilot uses AI. Check for mistakes.
Loading
Loading