mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-24 15:11:12 -05:00
Refactor ride credit sorting UI
This commit is contained in:
@@ -24,14 +24,18 @@ import {
|
||||
interface RideCreditCardProps {
|
||||
credit: UserRideCredit;
|
||||
position: number;
|
||||
maxPosition?: number;
|
||||
viewMode: 'grid' | 'list';
|
||||
isEditMode?: boolean;
|
||||
onUpdate: () => void;
|
||||
onDelete: () => void;
|
||||
onReorder?: (creditId: string, newPosition: number) => Promise<void>;
|
||||
}
|
||||
|
||||
export function RideCreditCard({ credit, position, viewMode, onUpdate, onDelete }: RideCreditCardProps) {
|
||||
export function RideCreditCard({ credit, position, maxPosition, viewMode, isEditMode, onUpdate, onDelete, onReorder }: RideCreditCardProps) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [editCount, setEditCount] = useState(credit.ride_count);
|
||||
const [editPosition, setEditPosition] = useState(position);
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
const [updating, setUpdating] = useState(false);
|
||||
|
||||
@@ -81,6 +85,25 @@ export function RideCreditCard({ credit, position, viewMode, onUpdate, onDelete
|
||||
}
|
||||
};
|
||||
|
||||
const handlePositionChange = async () => {
|
||||
if (editPosition === position || !onReorder || !maxPosition) return;
|
||||
|
||||
if (editPosition < 1 || editPosition > maxPosition) {
|
||||
toast.error(`Position must be between 1 and ${maxPosition}`);
|
||||
setEditPosition(position);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await onReorder(credit.id, editPosition);
|
||||
toast.success('Position updated');
|
||||
} catch (error) {
|
||||
console.error('Error changing position:', error);
|
||||
toast.error(getErrorMessage(error));
|
||||
setEditPosition(position);
|
||||
}
|
||||
};
|
||||
|
||||
const getCategoryBadge = (category: string) => {
|
||||
const categoryMap: Record<string, { label: string; variant: 'default' | 'secondary' | 'outline' }> = {
|
||||
roller_coaster: { label: 'Coaster', variant: 'default' },
|
||||
@@ -117,9 +140,25 @@ export function RideCreditCard({ credit, position, viewMode, onUpdate, onDelete
|
||||
>
|
||||
{rideName}
|
||||
</Link>
|
||||
<Badge variant="secondary" className="text-xs font-semibold">
|
||||
#{position}
|
||||
</Badge>
|
||||
{isEditMode && maxPosition ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-xs text-muted-foreground">#</span>
|
||||
<Input
|
||||
type="number"
|
||||
value={editPosition}
|
||||
onChange={(e) => setEditPosition(parseInt(e.target.value) || 1)}
|
||||
onBlur={handlePositionChange}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handlePositionChange()}
|
||||
className="w-14 h-6 text-xs p-1"
|
||||
min="1"
|
||||
max={maxPosition}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Badge variant="secondary" className="text-xs font-semibold">
|
||||
#{position}
|
||||
</Badge>
|
||||
)}
|
||||
{getCategoryBadge(category)}
|
||||
</div>
|
||||
|
||||
@@ -245,9 +284,25 @@ export function RideCreditCard({ credit, position, viewMode, onUpdate, onDelete
|
||||
{rideName}
|
||||
</Link>
|
||||
<div className="flex gap-1 flex-shrink-0">
|
||||
<Badge variant="secondary" className="text-xs font-semibold">
|
||||
#{position}
|
||||
</Badge>
|
||||
{isEditMode && maxPosition ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-xs text-muted-foreground">#</span>
|
||||
<Input
|
||||
type="number"
|
||||
value={editPosition}
|
||||
onChange={(e) => setEditPosition(parseInt(e.target.value) || 1)}
|
||||
onBlur={handlePositionChange}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handlePositionChange()}
|
||||
className="w-14 h-6 text-xs p-1"
|
||||
min="1"
|
||||
max={maxPosition}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Badge variant="secondary" className="text-xs font-semibold">
|
||||
#{position}
|
||||
</Badge>
|
||||
)}
|
||||
{getCategoryBadge(category)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -158,6 +158,23 @@ export function RideCreditsManager({ userId }: RideCreditsManagerProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleReorder = async (creditId: string, newPosition: number) => {
|
||||
try {
|
||||
const { error } = await supabase.rpc('reorder_ride_credit', {
|
||||
p_credit_id: creditId,
|
||||
p_new_position: newPosition
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
// Refetch to get accurate sort_order values
|
||||
await fetchCredits();
|
||||
} catch (error) {
|
||||
console.error('Error reordering credit:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragEnd = async (event: DragEndEvent) => {
|
||||
const { active, over } = event;
|
||||
|
||||
@@ -173,19 +190,9 @@ export function RideCreditsManager({ userId }: RideCreditsManagerProps) {
|
||||
setCredits(newCredits);
|
||||
|
||||
try {
|
||||
// Call RPC to persist the change
|
||||
const { error } = await supabase.rpc('reorder_ride_credit', {
|
||||
p_credit_id: String(active.id),
|
||||
p_new_position: newIndex + 1
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
// Refetch to get accurate sort_order values
|
||||
fetchCredits();
|
||||
await handleReorder(String(active.id), newIndex + 1);
|
||||
toast.success('Order updated');
|
||||
} catch (error) {
|
||||
console.error('Error reordering credit:', error);
|
||||
toast.error(getErrorMessage(error));
|
||||
// Revert on error
|
||||
fetchCredits();
|
||||
@@ -340,9 +347,11 @@ export function RideCreditsManager({ userId }: RideCreditsManagerProps) {
|
||||
key={credit.id}
|
||||
credit={credit}
|
||||
position={index + 1}
|
||||
maxPosition={credits.length}
|
||||
viewMode={viewMode}
|
||||
onUpdate={handleCreditUpdated}
|
||||
onDelete={() => handleCreditDeleted(credit.id)}
|
||||
onReorder={handleReorder}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -7,17 +7,21 @@ import { UserRideCredit } from '@/types/database';
|
||||
interface SortableRideCreditCardProps {
|
||||
credit: UserRideCredit;
|
||||
position: number;
|
||||
maxPosition: number;
|
||||
viewMode: 'grid' | 'list';
|
||||
onUpdate: () => void;
|
||||
onDelete: () => void;
|
||||
onReorder: (creditId: string, newPosition: number) => Promise<void>;
|
||||
}
|
||||
|
||||
export function SortableRideCreditCard({
|
||||
credit,
|
||||
position,
|
||||
maxPosition,
|
||||
viewMode,
|
||||
onUpdate,
|
||||
onDelete,
|
||||
onReorder,
|
||||
}: SortableRideCreditCardProps) {
|
||||
const {
|
||||
attributes,
|
||||
@@ -39,7 +43,11 @@ export function SortableRideCreditCard({
|
||||
<div
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className="absolute top-2 left-2 z-10 cursor-grab active:cursor-grabbing p-2 rounded bg-background/80 backdrop-blur-sm hover:bg-accent transition-colors"
|
||||
className={
|
||||
viewMode === 'grid'
|
||||
? "absolute top-2 right-2 z-10 cursor-grab active:cursor-grabbing p-2 rounded-full bg-background/90 backdrop-blur-sm hover:bg-accent transition-colors shadow-md"
|
||||
: "absolute left-0 top-1/2 -translate-y-1/2 z-10 cursor-grab active:cursor-grabbing p-2 rounded-r-md bg-background/90 backdrop-blur-sm hover:bg-accent transition-colors"
|
||||
}
|
||||
>
|
||||
<GripVertical className="w-4 h-4 text-muted-foreground" />
|
||||
</div>
|
||||
@@ -47,9 +55,12 @@ export function SortableRideCreditCard({
|
||||
<RideCreditCard
|
||||
credit={credit}
|
||||
position={position}
|
||||
maxPosition={maxPosition}
|
||||
viewMode={viewMode}
|
||||
isEditMode={true}
|
||||
onUpdate={onUpdate}
|
||||
onDelete={onDelete}
|
||||
onReorder={onReorder}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user