Skip to content

Commit 08545ea

Browse files
committed
Add price revert button
1 parent 633b67c commit 08545ea

3 files changed

Lines changed: 127 additions & 2 deletions

File tree

app/controllers/items_controller.rb

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
class ItemsController < ApplicationController
22
before_action :set_item, only: [ :buy, :claim_referral_item, :edit, :update, :destroy ]
3-
before_action :signed_in_admin, only: [ :create, :edit, :update, :destroy, :bulk_adjust_price, :bulk_set_category ]
4-
skip_after_action :verify_authorized, only: [ :index, :buy, :claim_referral_item, :create, :edit, :update, :destroy, :bulk_adjust_price, :bulk_set_category ]
3+
before_action :signed_in_admin, only: [ :create, :edit, :update, :destroy, :bulk_adjust_price, :bulk_set_category, :revert_price_changes, :preview_price_revert ]
4+
skip_after_action :verify_authorized, only: [ :index, :buy, :claim_referral_item, :create, :edit, :update, :destroy, :bulk_adjust_price, :bulk_set_category, :revert_price_changes, :preview_price_revert ]
55

66
def index
77
program = ReferralProgram.instance
@@ -102,6 +102,43 @@ def bulk_adjust_price
102102
inertia_location admin_items_path
103103
end
104104

105+
def preview_price_revert
106+
preview = []
107+
Item.find_each do |item|
108+
version = item.versions.reverse_each.find do |v|
109+
v.object_changes.is_a?(Hash) && v.object_changes.key?("price")
110+
end
111+
next unless version
112+
113+
preview << {
114+
id: item.id,
115+
name: item.name,
116+
current_price: item.price,
117+
revert_to: version.object_changes["price"][0],
118+
changed_at: version.created_at
119+
}
120+
end
121+
122+
render json: preview
123+
end
124+
125+
def revert_price_changes
126+
reverted = 0
127+
Item.find_each do |item|
128+
price_change_version = item.versions.reverse_each.find do |v|
129+
v.object_changes.is_a?(Hash) && v.object_changes.key?("price")
130+
end
131+
next unless price_change_version
132+
133+
old_price = price_change_version.object_changes["price"][0]
134+
item.update!(price: old_price)
135+
reverted += 1
136+
end
137+
138+
flash[:notice] = "Reverted prices for #{reverted} item#{"s" if reverted != 1}"
139+
inertia_location admin_items_path
140+
end
141+
105142
def bulk_set_category
106143
item_ids = Array(params[:item_ids]).map(&:to_i)
107144
category = params[:category].presence

app/frontend/pages/admin/items.tsx

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ import { useRef, useState } from "react";
66
import ItemForm from "@/components/admin/items/ItemForm";
77
import { router } from "@inertiajs/react";
88

9+
interface PriceRevertPreviewItem {
10+
id: number;
11+
name: string;
12+
current_price: number;
13+
revert_to: number;
14+
changed_at: string;
15+
}
16+
917
ModuleRegistry.registerModules([AllCommunityModule]);
1018

1119
interface Props {
@@ -19,6 +27,8 @@ export default function Items({ items, categories }: Props) {
1927
const [selectedIds, setSelectedIds] = useState<number[]>([]);
2028
const [percentage, setPercentage] = useState<number>(0);
2129
const [bulkCategory, setBulkCategory] = useState<string>("");
30+
const [revertPreview, setRevertPreview] = useState<PriceRevertPreviewItem[] | null>(null);
31+
const [previewLoading, setPreviewLoading] = useState(false);
2232

2333
const [colDefs] = useState([
2434
{
@@ -67,6 +77,19 @@ export default function Items({ items, categories }: Props) {
6777
});
6878
};
6979

80+
const openRevertPreview = async () => {
81+
setPreviewLoading(true);
82+
const res = await fetch("/admin/items/preview_price_revert");
83+
const data: PriceRevertPreviewItem[] = await res.json();
84+
setRevertPreview(data);
85+
setPreviewLoading(false);
86+
};
87+
88+
const confirmRevert = () => {
89+
router.post("/admin/items/revert_price_changes");
90+
setRevertPreview(null);
91+
};
92+
7093
const selectionLabel = `${selectedIds.length} item${selectedIds.length !== 1 ? "s" : ""} selected`;
7194

7295
return (
@@ -131,8 +154,71 @@ export default function Items({ items, categories }: Props) {
131154
Apply
132155
</button>
133156
</div>
157+
158+
<div className="ml-auto">
159+
<button
160+
onClick={openRevertPreview}
161+
disabled={previewLoading}
162+
className="cursor-pointer rounded-lg bg-red-600 px-4 py-1.5 text-sm font-semibold text-white hover:bg-red-700 disabled:cursor-not-allowed disabled:opacity-40"
163+
>
164+
{previewLoading ? "Loading…" : "Revert All Price Changes"}
165+
</button>
166+
</div>
134167
</div>
135168

169+
{revertPreview !== null && (
170+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
171+
<div className="w-full max-w-lg rounded-xl bg-white p-6 shadow-xl">
172+
<h2 className="mb-1 text-lg font-bold">Revert Price Changes</h2>
173+
{revertPreview.length === 0 ? (
174+
<p className="py-4 text-sm text-gray-500">No price changes found in version history.</p>
175+
) : (
176+
<>
177+
<p className="mb-4 text-sm text-gray-500">
178+
{revertPreview.length} item{revertPreview.length !== 1 ? "s" : ""} will be reverted:
179+
</p>
180+
<div className="max-h-72 overflow-y-auto rounded-lg border border-gray-200">
181+
<table className="w-full text-sm">
182+
<thead className="sticky top-0 bg-gray-50 text-xs font-semibold uppercase text-gray-500">
183+
<tr>
184+
<th className="px-3 py-2 text-left">Item</th>
185+
<th className="px-3 py-2 text-right">Current</th>
186+
<th className="px-3 py-2 text-right">Revert to</th>
187+
</tr>
188+
</thead>
189+
<tbody className="divide-y divide-gray-100">
190+
{revertPreview.map((row) => (
191+
<tr key={row.id}>
192+
<td className="px-3 py-2">{row.name}</td>
193+
<td className="px-3 py-2 text-right text-gray-400">{row.current_price}</td>
194+
<td className="px-3 py-2 text-right font-medium text-green-700">{row.revert_to}</td>
195+
</tr>
196+
))}
197+
</tbody>
198+
</table>
199+
</div>
200+
</>
201+
)}
202+
<div className="mt-5 flex justify-end gap-3">
203+
<button
204+
onClick={() => setRevertPreview(null)}
205+
className="cursor-pointer rounded-lg border border-gray-200 px-4 py-1.5 text-sm font-semibold text-gray-700 hover:bg-gray-50"
206+
>
207+
Cancel
208+
</button>
209+
{revertPreview.length > 0 && (
210+
<button
211+
onClick={confirmRevert}
212+
className="cursor-pointer rounded-lg bg-red-600 px-4 py-1.5 text-sm font-semibold text-white hover:bg-red-700"
213+
>
214+
Confirm Revert
215+
</button>
216+
)}
217+
</div>
218+
</div>
219+
</div>
220+
)}
221+
136222
<div style={{ height: 500 }}>
137223
<AgGridReact
138224
ref={gridRef}

config/routes.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@
109109
get "/items", to: "admin#items"
110110
post "/items/bulk_adjust_price", to: "items#bulk_adjust_price"
111111
post "/items/bulk_set_category", to: "items#bulk_set_category"
112+
get "/items/preview_price_revert", to: "items#preview_price_revert"
113+
post "/items/revert_price_changes", to: "items#revert_price_changes"
112114
get "/stats", to: "admin#stats"
113115
get "/orders", to: "admin#orders"
114116
get "/audit-log", to: "admin#audit_log"

0 commit comments

Comments
 (0)