Skip to content

Commit 8bf15e4

Browse files
committed
Preferences editor: use optimistic updates
1 parent 39f3bfc commit 8bf15e4

4 files changed

Lines changed: 70 additions & 18 deletions

File tree

app/Http/Controllers/LotteryController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public function preferences(UpdateLotteryPreferencesRequest $request)
7777
return back()->with('error', $e->getUserMessage());
7878
}
7979

80-
return back()->with('success', __('lottery.lottery_preferences_updated'));
80+
return back();
8181
}
8282

8383
/**

resources/js/components/lottery/composables/useLotteryAudits.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copilot - Pending review
2-
import type { ComputedRef, Ref } from 'vue';
2+
import { ComputedRef } from 'vue';
33

44
export interface LotteryAuditsComposable {
55
hasAudits: ComputedRef<boolean>;
@@ -13,9 +13,9 @@ export interface LotteryAuditsComposable {
1313
initAuditTimestamp: ComputedRef<number | null>;
1414
}
1515

16-
export function useLotteryAudits(
16+
export const useLotteryAudits = (
1717
lottery: ComputedRef<Lottery> | Ref<Lottery>,
18-
): LotteryAuditsComposable {
18+
): LotteryAuditsComposable => {
1919
const lotteryValue = isRef(lottery) ? lottery : computed(() => lottery.value);
2020

2121
// Check if there are any audits
@@ -25,17 +25,17 @@ export function useLotteryAudits(
2525

2626
// Check if there's a failure audit
2727
const hasFailure = computed(() => {
28-
return !!lotteryValue.value?.audits?.some((audit) => audit.type === 'failure');
28+
return !!lotteryValue.value?.audits?.some((audit: LotteryAudit) => audit.type === 'failure');
2929
});
3030

3131
// Get the failure audit (most recent one if multiple)
3232
const failureAudit = computed(() => {
33-
return lotteryValue.value?.audits?.find((audit) => audit.type === 'failure');
33+
return lotteryValue.value?.audits?.find((audit: LotteryAudit) => audit.type === 'failure');
3434
});
3535

3636
// Get init audit for timestamp
3737
const initAudit = computed(() => {
38-
return lotteryValue.value?.audits?.find((audit) => audit.type === 'init');
38+
return lotteryValue.value?.audits?.find((audit: LotteryAudit) => audit.type === 'init');
3939
});
4040

4141
// Get timestamp from init audit (in milliseconds)
@@ -79,7 +79,7 @@ export function useLotteryAudits(
7979
if (!lotteryValue.value?.audits) return 0;
8080

8181
const groupExecutions = lotteryValue.value.audits.filter(
82-
(audit) => audit.type === 'group_execution',
82+
(audit: LotteryAudit) => audit.type === 'group_execution',
8383
);
8484

8585
return groupExecutions.length;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copilot - Pending review
2+
import { router } from '@inertiajs/vue3';
3+
4+
export const usePreferences = (preferences: Ref<ApiResource<Unit>[]>) => {
5+
// Reactive copy for optimistic updates
6+
const localPreferences = ref<ApiResource<Unit>[]>([...preferences.value]);
7+
const form = useForm({ preferences: localPreferences.value });
8+
const disabled = ref(false);
9+
10+
// Track latest request to handle out-of-order responses
11+
let requestCounter = 0;
12+
13+
// Watch for external prop changes (e.g., after reload)
14+
watch(
15+
preferences,
16+
(newPreferences: ApiResource<Unit>[]) => {
17+
localPreferences.value = [...newPreferences];
18+
},
19+
{ deep: true },
20+
);
21+
22+
const submit = (updatedPreferences: ApiResource<Unit>[]) => {
23+
// Optimistically update local state immediately
24+
localPreferences.value = [...updatedPreferences];
25+
26+
// Increment request counter to track this request
27+
const currentRequest = ++requestCounter;
28+
29+
form.preferences = updatedPreferences;
30+
form.post(route('lottery.preferences'), {
31+
preserveScroll: true,
32+
onError: () => {
33+
// Only process if this is still the latest request
34+
if (currentRequest === requestCounter) {
35+
// Disable editing on failure
36+
disabled.value = true;
37+
38+
// Do a partial reload to get current persisted state
39+
router.reload({
40+
only: ['preferences'],
41+
onSuccess: () => {
42+
// Re-enable after reload completes
43+
disabled.value = false;
44+
},
45+
});
46+
}
47+
},
48+
});
49+
};
50+
51+
return {
52+
localPreferences,
53+
disabled,
54+
submit,
55+
};
56+
}

resources/js/components/lottery/member/PreferencesManager.vue

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
// Copilot - Pending review
12
<script setup lang="ts">
23
import { Card, CardContent, CardHeader } from '@/components/card';
34
import { _ } from '@/composables/useTranslations';
45
import { HeartIcon } from 'lucide-vue-next';
6+
import { usePreferences } from '../composables/usePreferences';
57
import PreferencesEmptyState from './PreferencesEmptyState.vue';
68
import PreferencesGridDesktop from './PreferencesGridDesktop.vue';
79
import PreferencesGridMobile from './PreferencesGridMobile.vue';
@@ -11,13 +13,7 @@ const props = defineProps<{
1113
preferences: ApiResource<Unit>[];
1214
}>();
1315
14-
const preferences = reactive(props.preferences);
15-
const form = useForm({ preferences });
16-
17-
const submit = (updatedPreferences: Unit[]) => {
18-
form.preferences = updatedPreferences;
19-
form.post(route('lottery.preferences'), { preserveScroll: true });
20-
};
16+
const { localPreferences, disabled, submit } = usePreferences(toRef(props, 'preferences'));
2117
</script>
2218

2319
<template>
@@ -32,17 +28,17 @@ const submit = (updatedPreferences: Unit[]) => {
3228
</CardHeader>
3329

3430
<CardContent class="w-full flex-1 overflow-y-auto">
35-
<PreferencesEmptyState v-if="preferences.length === 0" />
31+
<PreferencesEmptyState v-if="localPreferences.length === 0" />
3632

3733
<!-- Mobile/Tablet: Vertical List -->
38-
<PreferencesGridMobile :preferences :disabled="form.processing" @change="submit">
34+
<PreferencesGridMobile :preferences="localPreferences" :disabled @change="submit">
3935
<template #unit-details="{ unit, index }">
4036
<PreferencesGridMobileSlot :unit :index />
4137
</template>
4238
</PreferencesGridMobile>
4339

4440
<!-- Desktop: Grid Layout with Fixed Numbered Slots -->
45-
<PreferencesGridDesktop :preferences :disabled="form.processing" @change="submit">
41+
<PreferencesGridDesktop :preferences="localPreferences" :disabled @change="submit">
4642
<template #priority-badge="{ index }">
4743
<div
4844
v-if="index < 3"

0 commit comments

Comments
 (0)