Refactor ride credit sorting UI

This commit is contained in:
gpt-engineer-app[bot]
2025-10-16 15:23:09 +00:00
parent 7b0faf9bb2
commit bd44597f9a
3 changed files with 94 additions and 19 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>
);