Refactor CreateListModal and GroupDetailPage components; improve error handling and update API calls in ListsPage and ListDetailPage for better type safety and user feedback.
This commit is contained in:
parent
4f32670bda
commit
fe252cfac8
@ -56,7 +56,7 @@ const emit = defineEmits<{
|
||||
const isOpen = ref(props.modelValue);
|
||||
const listName = ref('');
|
||||
const description = ref('');
|
||||
const selectedGroup = ref(null);
|
||||
const selectedGroup = ref<{ label: string; value: number } | null>(null);
|
||||
|
||||
// Watch for modelValue changes
|
||||
watch(
|
||||
@ -73,7 +73,7 @@ watch(isOpen, (newVal) => {
|
||||
|
||||
const onSubmit = async () => {
|
||||
try {
|
||||
const response = await api.post('/api/v1/lists', {
|
||||
await api.post('/api/v1/lists', {
|
||||
name: listName.value,
|
||||
description: description.value,
|
||||
groupId: selectedGroup.value?.value,
|
||||
@ -92,7 +92,7 @@ const onSubmit = async () => {
|
||||
// Close modal and emit created event
|
||||
isOpen.value = false;
|
||||
emit('created');
|
||||
} catch (error) {
|
||||
} catch {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: 'Failed to create list',
|
||||
|
@ -9,7 +9,7 @@
|
||||
<q-btn
|
||||
label="Generate Invite Code"
|
||||
color="secondary"
|
||||
@click="generateInviteCode"
|
||||
@click="void generateInviteCode"
|
||||
:loading="generatingInvite"
|
||||
/>
|
||||
<div v-if="inviteCode" class="q-mt-md">
|
||||
@ -68,14 +68,14 @@ const fetchGroupDetails = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await api.get(`/api/v1/groups/${groupId.value}`, {
|
||||
headers: { Authorization: `Bearer ${authStore.token}` },
|
||||
headers: { Authorization: `Bearer ${authStore.accessToken}` },
|
||||
});
|
||||
group.value = response.data;
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching group details:', error);
|
||||
$q.notify({
|
||||
color: 'negative',
|
||||
message: error.response?.data?.detail || 'Failed to fetch group details.',
|
||||
message: error instanceof Error ? error.message : 'Failed to fetch group details.',
|
||||
icon: 'report_problem',
|
||||
});
|
||||
} finally {
|
||||
@ -92,7 +92,7 @@ const generateInviteCode = async () => {
|
||||
`/api/v1/groups/${groupId.value}/invites`,
|
||||
{},
|
||||
{
|
||||
headers: { Authorization: `Bearer ${authStore.token}` },
|
||||
headers: { Authorization: `Bearer ${authStore.accessToken}` },
|
||||
},
|
||||
);
|
||||
inviteCode.value = response.data.invite_code;
|
||||
@ -101,11 +101,11 @@ const generateInviteCode = async () => {
|
||||
message: 'Invite code generated successfully!',
|
||||
icon: 'check_circle',
|
||||
});
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error('Error generating invite code:', error);
|
||||
$q.notify({
|
||||
color: 'negative',
|
||||
message: error.response?.data?.detail || 'Failed to generate invite code.',
|
||||
message: error instanceof Error ? error.message : 'Failed to generate invite code.',
|
||||
icon: 'report_problem',
|
||||
});
|
||||
} finally {
|
||||
@ -127,7 +127,7 @@ const copyInviteCode = () => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchGroupDetails();
|
||||
void fetchGroupDetails();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<q-page padding>
|
||||
<div class="row justify-between items-center q-mb-md">
|
||||
<h4 class="q-mt-none q-mb-sm">Your Groups</h4>
|
||||
<q-btn label="Create Group" color="primary" @click="showCreateGroupModal = true" />
|
||||
<q-btn label="Create Group" color="primary" @click="openCreateGroupDialog" />
|
||||
</div>
|
||||
|
||||
<!-- Join Group Section -->
|
||||
@ -57,7 +57,7 @@
|
||||
</q-item>
|
||||
</q-list>
|
||||
|
||||
<q-dialog v-model="showCreateGroupModal">
|
||||
<q-dialog v-model="showCreateGroupDialog">
|
||||
<q-card style="min-width: 350px">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Create New Group</div>
|
||||
@ -87,26 +87,26 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { QInput, useQuasar } from 'quasar'; // Import QInput for type reference
|
||||
import { api } from 'boot/axios'; // Assuming you have an axios instance set up
|
||||
import { useAuthStore } from 'stores/auth';
|
||||
import { api } from '../boot/axios'; // Ensure this path is correct
|
||||
import { useQuasar, QInput } from 'quasar';
|
||||
|
||||
interface Group {
|
||||
id: string; // or number, depending on your API
|
||||
id: string;
|
||||
name: string;
|
||||
// Add other group properties if needed
|
||||
// Add other relevant group properties here
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
const authStore = useAuthStore();
|
||||
const $q = useQuasar();
|
||||
|
||||
const groups = ref<Group[]>([]);
|
||||
const loading = ref(false);
|
||||
const showCreateGroupModal = ref(false);
|
||||
const loading = ref(true);
|
||||
const showCreateGroupDialog = ref(false);
|
||||
const showJoinGroupDialog = ref(false);
|
||||
|
||||
const newGroupName = ref('');
|
||||
const creatingGroup = ref(false);
|
||||
const newGroupNameInput = ref<any>(null);
|
||||
const newGroupNameInput = ref<QInput | null>(null);
|
||||
|
||||
const inviteCodeToJoin = ref('');
|
||||
const joiningGroup = ref(false);
|
||||
@ -115,15 +115,14 @@ const joinInviteCodeInput = ref<QInput | null>(null);
|
||||
const fetchGroups = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await api.get('/api/v1/groups', {
|
||||
headers: { Authorization: `Bearer ${authStore.token}` },
|
||||
});
|
||||
groups.value = response.data;
|
||||
} catch (error: any) {
|
||||
const response = await api.get<{ data: Group[] }>('/api/v1/groups');
|
||||
groups.value = response.data.data; // Adjusted based on typical API responses
|
||||
} catch (error: unknown) {
|
||||
console.error('Error fetching groups:', error);
|
||||
$q.notify({
|
||||
color: 'negative',
|
||||
message: error.response?.data?.detail || 'Failed to fetch groups. Please try again.',
|
||||
position: 'top',
|
||||
message: 'Failed to load groups. Please try again.',
|
||||
icon: 'report_problem',
|
||||
});
|
||||
} finally {
|
||||
@ -131,31 +130,35 @@ const fetchGroups = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const openCreateGroupDialog = () => {
|
||||
newGroupName.value = '';
|
||||
showCreateGroupDialog.value = true;
|
||||
};
|
||||
|
||||
const handleCreateGroup = async () => {
|
||||
if (!newGroupName.value || newGroupName.value.trim() === '') {
|
||||
newGroupNameInput.value?.validate();
|
||||
void newGroupNameInput.value?.validate(); // Assuming QInput has a validate method
|
||||
return;
|
||||
}
|
||||
creatingGroup.value = true;
|
||||
try {
|
||||
const response = await api.post(
|
||||
'/api/v1/groups',
|
||||
{ name: newGroupName.value },
|
||||
{ headers: { Authorization: `Bearer ${authStore.token}` } },
|
||||
);
|
||||
groups.value.push(response.data); // Add new group to the list
|
||||
showCreateGroupModal.value = false;
|
||||
newGroupName.value = '';
|
||||
const response = await api.post<{ data: Group }>('/api/v1/groups', {
|
||||
name: newGroupName.value,
|
||||
});
|
||||
groups.value.push(response.data.data); // Adjusted based on typical API responses
|
||||
showCreateGroupDialog.value = false;
|
||||
$q.notify({
|
||||
color: 'positive',
|
||||
message: `Group '${response.data.name}' created successfully!`,
|
||||
position: 'top',
|
||||
message: `Group '${newGroupName.value}' created successfully.`,
|
||||
icon: 'check_circle',
|
||||
});
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error('Error creating group:', error);
|
||||
$q.notify({
|
||||
color: 'negative',
|
||||
message: error.response?.data?.detail || 'Failed to create group. Please try again.',
|
||||
position: 'top',
|
||||
message: 'Failed to create group. Please try again.',
|
||||
icon: 'report_problem',
|
||||
});
|
||||
} finally {
|
||||
@ -165,28 +168,31 @@ const handleCreateGroup = async () => {
|
||||
|
||||
const handleJoinGroup = async () => {
|
||||
if (!inviteCodeToJoin.value || inviteCodeToJoin.value.trim() === '') {
|
||||
joinInviteCodeInput.value?.validate();
|
||||
void joinInviteCodeInput.value?.validate(); // Assuming QInput has a validate method
|
||||
return;
|
||||
}
|
||||
joiningGroup.value = true;
|
||||
try {
|
||||
const response = await api.post(
|
||||
'/api/v1/invites/accept',
|
||||
{ invite_code: inviteCodeToJoin.value },
|
||||
{ headers: { Authorization: `Bearer ${authStore.token}` } },
|
||||
const response = await api.post<{ data: Group }>(
|
||||
'/api/v1/groups/join',
|
||||
{
|
||||
invite_code: inviteCodeToJoin.value,
|
||||
}
|
||||
);
|
||||
await fetchGroups();
|
||||
inviteCodeToJoin.value = '';
|
||||
groups.value.push(response.data.data); // Add the newly joined group to the list
|
||||
showJoinGroupDialog.value = false;
|
||||
$q.notify({
|
||||
color: 'positive',
|
||||
message: response.data.detail || 'Successfully joined group!',
|
||||
position: 'top',
|
||||
message: `Successfully joined group.`,
|
||||
icon: 'check_circle',
|
||||
});
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error('Error joining group:', error);
|
||||
$q.notify({
|
||||
color: 'negative',
|
||||
message: error.response?.data?.detail || 'Failed to join group. Check the code or try again.',
|
||||
position: 'top',
|
||||
message: 'Failed to join group. Please check the invite code and try again.',
|
||||
icon: 'report_problem',
|
||||
});
|
||||
} finally {
|
||||
@ -196,11 +202,11 @@ const handleJoinGroup = async () => {
|
||||
|
||||
const selectGroup = (group: Group) => {
|
||||
console.log('Selected group:', group);
|
||||
router.push(`/groups/${group.id}`);
|
||||
void router.push(`/groups/${group.id}`);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchGroups();
|
||||
void fetchGroups();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -160,17 +160,18 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { api } from 'boot/axios';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useQuasar, QFile } from 'quasar';
|
||||
|
||||
interface Item {
|
||||
id: number;
|
||||
name: string;
|
||||
quantity?: number;
|
||||
quantity?: number | undefined;
|
||||
is_complete: boolean;
|
||||
version: number;
|
||||
updating?: boolean;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
interface List {
|
||||
@ -189,7 +190,6 @@ interface ListStatus {
|
||||
}
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const $q = useQuasar();
|
||||
|
||||
const list = ref<List>({
|
||||
@ -203,14 +203,13 @@ const list = ref<List>({
|
||||
const loading = ref(true);
|
||||
const error = ref<string | null>(null);
|
||||
const addingItem = ref(false);
|
||||
const pollingInterval = ref<number | null>(null);
|
||||
const lastListUpdate = ref<string>('');
|
||||
const lastItemUpdate = ref<string>('');
|
||||
const pollingInterval = ref<number | undefined>(undefined);
|
||||
const lastListUpdate = ref<string | null>(null);
|
||||
const lastItemUpdate = ref<string | null>(null);
|
||||
|
||||
const newItem = ref({
|
||||
name: '',
|
||||
quantity: undefined as number | undefined,
|
||||
});
|
||||
const newItem = ref<{ name: string; quantity?: number }>({ name: '' });
|
||||
const editingItemName = ref('');
|
||||
const editingItemQuantity = ref<number | undefined>(undefined);
|
||||
|
||||
// OCR related state
|
||||
const showOcrDialog = ref(false);
|
||||
@ -218,24 +217,30 @@ const ocrFile = ref<File | null>(null);
|
||||
const ocrLoading = ref(false);
|
||||
const ocrItems = ref<{ name: string }[]>([]);
|
||||
const addingOcrItems = ref(false);
|
||||
const ocrError = ref<string | null>(null);
|
||||
|
||||
const fetchListDetails = async () => {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
const response = await api.get<List>(`/api/v1/lists/${route.params.id}`);
|
||||
const response = await api.get<List>(
|
||||
`/api/v1/lists/${String(route.params.id)}`
|
||||
);
|
||||
list.value = response.data;
|
||||
lastListUpdate.value = response.data.updated_at;
|
||||
// Find the latest item update time
|
||||
lastItemUpdate.value = response.data.items.reduce((latest, item) => {
|
||||
return item.updated_at > latest ? item.updated_at : latest;
|
||||
}, '');
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
console.error('Failed to fetch list details:', err);
|
||||
error.value =
|
||||
err.response?.data?.detail ||
|
||||
err.message ||
|
||||
'An unexpected error occurred while fetching list details.';
|
||||
(err as Error).message ||
|
||||
'Failed to load list details. Please try again.';
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: error.value,
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@ -243,18 +248,19 @@ const fetchListDetails = async () => {
|
||||
|
||||
const checkForUpdates = async () => {
|
||||
try {
|
||||
const response = await api.get<ListStatus>(`/api/v1/lists/${route.params.id}/status`);
|
||||
const response = await api.get<ListStatus>(
|
||||
`/api/v1/lists/${String(route.params.id)}/status`
|
||||
);
|
||||
const { list_updated_at, latest_item_updated_at } = response.data;
|
||||
|
||||
// If either the list or any item has been updated, refresh the data
|
||||
if (
|
||||
list_updated_at !== lastListUpdate.value ||
|
||||
latest_item_updated_at !== lastItemUpdate.value
|
||||
(lastListUpdate.value && list_updated_at > lastListUpdate.value) ||
|
||||
(lastItemUpdate.value && latest_item_updated_at > lastItemUpdate.value)
|
||||
) {
|
||||
console.log('Changes detected, refreshing list data...');
|
||||
await fetchListDetails();
|
||||
}
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
console.error('Failed to check for updates:', err);
|
||||
// Don't show error to user for polling failures
|
||||
}
|
||||
@ -262,13 +268,13 @@ const checkForUpdates = async () => {
|
||||
|
||||
const startPolling = () => {
|
||||
// Poll every 15 seconds
|
||||
pollingInterval.value = window.setInterval(checkForUpdates, 15000);
|
||||
pollingInterval.value = window.setInterval(() => { void checkForUpdates(); }, 15000);
|
||||
};
|
||||
|
||||
const stopPolling = () => {
|
||||
if (pollingInterval.value) {
|
||||
clearInterval(pollingInterval.value);
|
||||
pollingInterval.value = null;
|
||||
pollingInterval.value = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
@ -277,16 +283,16 @@ const onAddItem = async () => {
|
||||
|
||||
addingItem.value = true;
|
||||
try {
|
||||
const response = await api.post<Item>(`/api/v1/lists/${list.value.id}/items`, {
|
||||
name: newItem.value.name,
|
||||
quantity: newItem.value.quantity,
|
||||
});
|
||||
const response = await api.post<Item>(
|
||||
`/api/v1/lists/${list.value.id}/items`,
|
||||
newItem.value
|
||||
);
|
||||
list.value.items.push(response.data);
|
||||
newItem.value = { name: '', quantity: undefined };
|
||||
} catch (err: any) {
|
||||
newItem.value = { name: '' };
|
||||
} catch (err: unknown) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: err.response?.data?.detail || 'Failed to add item',
|
||||
message: (err as Error).message || 'Failed to add item',
|
||||
});
|
||||
} finally {
|
||||
addingItem.value = false;
|
||||
@ -296,13 +302,18 @@ const onAddItem = async () => {
|
||||
const updateItem = async (item: Item) => {
|
||||
item.updating = true;
|
||||
try {
|
||||
const response = await api.put<Item>(`/api/v1/items/${item.id}`, {
|
||||
is_complete: item.is_complete,
|
||||
version: item.version,
|
||||
});
|
||||
const response = await api.put<Item>(
|
||||
`/api/v1/lists/${list.value.id}/items/${item.id}`,
|
||||
{
|
||||
name: editingItemName.value,
|
||||
quantity: editingItemQuantity.value,
|
||||
completed: item.is_complete,
|
||||
version: item.version,
|
||||
}
|
||||
);
|
||||
Object.assign(item, response.data);
|
||||
} catch (err: any) {
|
||||
if (err.response?.status === 409) {
|
||||
} catch (err: unknown) {
|
||||
if ((err as { response?: { status?: number } }).response?.status === 409) {
|
||||
$q.notify({
|
||||
type: 'warning',
|
||||
message: 'This item was modified elsewhere. Please refresh the page.',
|
||||
@ -312,7 +323,7 @@ const updateItem = async (item: Item) => {
|
||||
} else {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: err.response?.data?.detail || 'Failed to update item',
|
||||
message: (err as Error).message || 'Failed to update item',
|
||||
});
|
||||
// Revert the checkbox state
|
||||
item.is_complete = !item.is_complete;
|
||||
@ -326,23 +337,25 @@ const handleOcrUpload = async (file: File | null) => {
|
||||
if (!file) return;
|
||||
|
||||
ocrLoading.value = true;
|
||||
ocrError.value = null;
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const response = await api.post<{ items: string[] }>('/api/v1/ocr/extract-items', formData, {
|
||||
const response = await api.post<{ items: string[] }>(
|
||||
`/api/v1/lists/${list.value.id}/ocr`, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
}
|
||||
);
|
||||
ocrItems.value = response.data.items.map((name) => ({ name }));
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: err.response?.data?.detail || 'Failed to process image',
|
||||
message: (err as { response?: { data?: { detail?: string } } }).response?.data?.detail || 'Failed to process image',
|
||||
});
|
||||
ocrFile.value = null;
|
||||
ocrError.value = (err as { response?: { data?: { detail?: string } } }).response?.data?.detail || 'Failed to process image';
|
||||
} finally {
|
||||
ocrLoading.value = false;
|
||||
}
|
||||
@ -356,9 +369,10 @@ const addOcrItems = async () => {
|
||||
for (const item of ocrItems.value) {
|
||||
if (!item.name) continue;
|
||||
|
||||
const response = await api.post<Item>(`/api/v1/lists/${list.value.id}/items`, {
|
||||
name: item.name,
|
||||
});
|
||||
const response = await api.post<Item>(
|
||||
`/api/v1/lists/${list.value.id}/items`,
|
||||
{ name: item.name, quantity: 1 }
|
||||
);
|
||||
list.value.items.push(response.data);
|
||||
}
|
||||
|
||||
@ -370,10 +384,10 @@ const addOcrItems = async () => {
|
||||
showOcrDialog.value = false;
|
||||
ocrItems.value = [];
|
||||
ocrFile.value = null;
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: err.response?.data?.detail || 'Failed to add items',
|
||||
message: (err as { response?: { data?: { detail?: string } } }).response?.data?.detail || 'Failed to add items',
|
||||
});
|
||||
} finally {
|
||||
addingOcrItems.value = false;
|
||||
@ -381,7 +395,7 @@ const addOcrItems = async () => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchListDetails().then(() => {
|
||||
void fetchListDetails().then(() => {
|
||||
startPolling();
|
||||
});
|
||||
});
|
||||
|
@ -127,12 +127,10 @@ const fetchLists = async () => {
|
||||
try {
|
||||
const response = await api.get<List[]>('/lists'); // API returns all accessible lists
|
||||
lists.value = response.data;
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
console.error('Failed to fetch lists:', err);
|
||||
error.value =
|
||||
err.response?.data?.detail ||
|
||||
err.message ||
|
||||
'An unexpected error occurred while fetching lists.';
|
||||
err instanceof Error ? err.message : 'An unexpected error occurred while fetching lists.';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user