44 lines
1.7 KiB
TypeScript
44 lines
1.7 KiB
TypeScript
import { computed, ref, type ComputedRef, type Ref } from "vue";
|
|
import { apiErrorContextFromUnknown } from "@/api/client";
|
|
import type { ApiError, ApiResult, ErrorDiagnostic } from "@/types";
|
|
|
|
type TableLoaderResult<T> = T[] | ApiResult<T[]>;
|
|
|
|
export function useTableLoader<T>(loader: () => Promise<TableLoaderResult<T>>): { rows: Ref<T[]>; loading: Ref<boolean>; error: Ref<string | null>; apiError: Ref<ApiError | null>; diagnostic: Ref<ErrorDiagnostic | null>; empty: ComputedRef<boolean>; reload: () => Promise<void> } {
|
|
const rows = ref([]) as Ref<T[]>;
|
|
const loading = ref(false);
|
|
const error = ref<string | null>(null);
|
|
const apiError = ref<ApiError | null>(null);
|
|
const diagnostic = ref<ErrorDiagnostic | null>(null);
|
|
const empty = computed(() => !loading.value && rows.value.length === 0);
|
|
|
|
async function reload(): Promise<void> {
|
|
loading.value = true;
|
|
error.value = null;
|
|
apiError.value = null;
|
|
diagnostic.value = null;
|
|
try {
|
|
const result = await loader();
|
|
if (isApiResult(result)) {
|
|
if (!result.ok) throw result;
|
|
rows.value = result.data ?? [];
|
|
} else {
|
|
rows.value = result;
|
|
}
|
|
} catch (err) {
|
|
const context = apiErrorContextFromUnknown(err);
|
|
error.value = context.error;
|
|
apiError.value = context.apiError;
|
|
diagnostic.value = context.diagnostic;
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
|
|
return { rows, loading, error, apiError, diagnostic, empty, reload };
|
|
}
|
|
|
|
function isApiResult<T>(value: TableLoaderResult<T>): value is ApiResult<T[]> {
|
|
return Boolean(value && !Array.isArray(value) && typeof value === "object" && "ok" in value && "status" in value);
|
|
}
|