Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions apps/client/src/components/CardLayoutBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import { CSS } from '@dnd-kit/utilities';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useI18n } from '../contexts/I18nContext';
import { apiClient } from '../lib/apiClient';
import { useRepository } from '../contexts/RepositoryContext';
import { FIELD_TYPES, type BuilderField } from '../lib/fieldTypes';
import { LoadingSpinner } from './LoadingSpinner';

Expand Down Expand Up @@ -92,6 +92,7 @@ function snapshotLayout(
export function CardLayoutBuilder({ tableId, fields, onDirtyChange }: CardLayoutBuilderProps) {
const { t } = useI18n();
const queryClient = useQueryClient();
const repository = useRepository();

const [selectedViewId, setSelectedViewId] = useState<string | null>(null);
const [newViewName, setNewViewName] = useState('');
Expand All @@ -115,7 +116,7 @@ export function CardLayoutBuilder({ tableId, fields, onDirtyChange }: CardLayout
// Fetch views for this table
const viewsQuery = useQuery<View[], Error>({
queryKey: ['views', tableId],
queryFn: () => apiClient.get(`/views?table_id=${tableId}`),
queryFn: () => repository.views.listByTable(tableId),
});

const cardViews = useMemo(() => {
Expand Down Expand Up @@ -237,7 +238,7 @@ export function CardLayoutBuilder({ tableId, fields, onDirtyChange }: CardLayout
// API mutations
const createViewMutation = useMutation({
mutationFn: (name: string) =>
apiClient.post<View>('/views', {
repository.views.create({
table_id: tableId,
name,
type: 'card',
Expand All @@ -260,9 +261,7 @@ export function CardLayoutBuilder({ tableId, fields, onDirtyChange }: CardLayout
hiddenLabels?: Record<string, boolean>;
}) => {
if (!selectedViewId) return Promise.reject(new Error('No view selected'));
return apiClient.put(`/views/${selectedViewId}`, {
config,
});
return repository.views.update(selectedViewId, { config });
},
onSuccess: (_data, config) => {
queryClient.invalidateQueries({ queryKey: ['views', tableId] });
Expand All @@ -275,15 +274,15 @@ export function CardLayoutBuilder({ tableId, fields, onDirtyChange }: CardLayout

const toggleDefaultMutation = useMutation({
mutationFn: (payload: { is_default?: boolean; is_single_default?: boolean }) =>
apiClient.put<View>(`/views/${selectedViewId}`, payload),
repository.views.update(selectedViewId!, payload),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['views', tableId] });
showToast(t('layout.defaultSetSuccess'));
},
});

const deleteViewMutation = useMutation({
mutationFn: (id: string) => apiClient.delete(`/views/${id}`),
mutationFn: (id: string) => repository.views.remove(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['views', tableId] });
setSelectedViewId(null);
Expand Down
21 changes: 5 additions & 16 deletions apps/client/src/components/CsvImportModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useMemo, useState } from 'react';
import { useMutation } from '@tanstack/react-query';
import { apiClient } from '../lib/apiClient';
import type { CsvImportResult } from '@hap/core';
import { useRepository } from '../contexts/RepositoryContext';
import type { BuilderField, FieldType } from '../lib/fieldTypes';
import { FIELD_TYPE_ORDER } from '../lib/fieldTypes';

Expand All @@ -22,17 +23,6 @@ interface ColumnMapping {
match_or_create?: boolean;
}

interface CsvImportResult {
detected_encoding: string;
delimiter: string;
row_count: number;
accepted_count: number;
rejected_count: number;
warnings: string[];
accepted_rows: Array<{ row: number; data: Record<string, unknown>; record_id?: string }>;
rejected_rows: Array<{ row: number; errors: Record<string, string[]> }>;
}

function sniffHeaders(text: string): string[] {
const firstLine = text.split(/\r?\n/).find((line) => line.trim() !== '') ?? '';
const delimiters = [',', ';', '\t'];
Expand All @@ -47,6 +37,7 @@ function sniffHeaders(text: string): string[] {
}

export function CsvImportModal({ tableId, fields, onClose, onImported }: CsvImportModalProps) {
const repository = useRepository();
const [file, setFile] = useState<File | null>(null);
const [headers, setHeaders] = useState<string[]>([]);
const [mapping, setMapping] = useState<Record<string, ColumnMapping>>({});
Expand All @@ -66,14 +57,12 @@ export function CsvImportModal({ tableId, fields, onClose, onImported }: CsvImpo
};

const dryRunMutation = useMutation({
mutationFn: () =>
apiClient.postForm<CsvImportResult>(`/tables/${tableId}/csv-import/dry-run`, buildFormData()),
mutationFn: () => repository.tables.csvImportDryRun(tableId, buildFormData()),
onSuccess: setResult,
});

const importMutation = useMutation({
mutationFn: () =>
apiClient.postForm<CsvImportResult>(`/tables/${tableId}/csv-import`, buildFormData()),
mutationFn: () => repository.tables.csvImport(tableId, buildFormData()),
onSuccess: (data) => {
setResult(data);
onImported();
Expand Down
16 changes: 8 additions & 8 deletions apps/client/src/components/DeleteReassignModal.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useState, useMemo } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useI18n } from '../contexts/I18nContext';
import { apiClient } from '../lib/apiClient';
import type { ApiErrorLike, ApiRecord, DeleteConflictData } from '../lib/apiTypes';
import { useRepository } from '../contexts/RepositoryContext';
import type { Paginated, RecordEntity } from '@hap/core';
import type { ApiErrorLike, DeleteConflictData } from '../lib/apiTypes';
import { LoadingSpinner } from './LoadingSpinner';

interface DeleteReassignModalProps {
Expand All @@ -20,15 +21,16 @@ export function DeleteReassignModal({
onClose,
onSuccess,
}: DeleteReassignModalProps) {
const repository = useRepository();
const { t } = useI18n();
const queryClient = useQueryClient();
const [targetRecordId, setTargetRecordId] = useState('');
const [errorMsg, setErrorMsg] = useState<string | null>(null);

// Fetch candidate replacement records (same table, excluding current record)
const candidatesQuery = useQuery<{ data: ApiRecord[] }, Error>({
const candidatesQuery = useQuery<Paginated<RecordEntity>, Error>({
queryKey: ['records-reassign-candidates', tableId],
queryFn: () => apiClient.get(`/records?table_id=${tableId}&per_page=100`),
queryFn: () => repository.records.list({ table_id: tableId, per_page: 100 }),
});

const candidates = useMemo(() => {
Expand All @@ -45,12 +47,10 @@ export function DeleteReassignModal({
const reassignMutation = useMutation({
mutationFn: async () => {
// 1. Reassign links to target record B
await apiClient.post(`/records/${recordId}/reassign-links`, {
to_record_id: targetRecordId,
});
await repository.records.reassignLinks(recordId, targetRecordId);

// 2. Perform deletion
await apiClient.delete(`/records/${recordId}`);
await repository.records.remove(recordId);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['records'] });
Expand Down
7 changes: 4 additions & 3 deletions apps/client/src/components/InlineRecordModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import { useI18n } from '../contexts/I18nContext';
import { apiClient } from '../lib/apiClient';
import { useRepository } from '../contexts/RepositoryContext';
import { type BuilderField } from '../lib/fieldTypes';
import { RecordForm } from './RecordForm';
import { LoadingSpinner } from './LoadingSpinner';
Expand All @@ -19,19 +19,20 @@ interface InlineRecordModalProps {
}

export function InlineRecordModal({ tableId, isOpen, onClose, onSuccess }: InlineRecordModalProps) {
const repository = useRepository();
const { t } = useI18n();

// Fetch the target table details
const tableQuery = useQuery<Table, Error>({
queryKey: ['tables', tableId],
queryFn: () => apiClient.get(`/tables/${tableId}`),
queryFn: () => repository.tables.get(tableId),
enabled: isOpen && !!tableId,
});

// Fetch the fields schema for this target table
const fieldsQuery = useQuery<BuilderField[], Error>({
queryKey: ['fields', tableId],
queryFn: () => apiClient.get(`/fields?table_id=${tableId}`),
queryFn: () => repository.fields.listByTable(tableId),
enabled: isOpen && !!tableId,
});

Expand Down
27 changes: 16 additions & 11 deletions apps/client/src/components/RecordDetailView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useMemo } from 'react';
import { useQuery } from '@tanstack/react-query';
import { apiClient } from '../lib/apiClient';
import { useRepository } from '../contexts/RepositoryContext';
import type { ApiRecord, ApiRecordData } from '../lib/apiTypes';
import { parseGpsValue } from '../lib/fieldDisplay';
import { type BuilderField } from '../lib/fieldTypes';
Expand Down Expand Up @@ -33,17 +33,18 @@ export function RecordDetailView({
recordId,
databaseId: propsDatabaseId,
}: RecordDetailViewProps) {
const repository = useRepository();
const [activeLightboxHash, setActiveLightboxHash] = useState<string | null>(null);

// Queries
const fieldsQuery = useQuery<BuilderField[], Error>({
queryKey: ['fields', tableId],
queryFn: () => apiClient.get(`/fields?table_id=${tableId}`),
queryFn: () => repository.fields.listByTable(tableId),
});

const viewsQuery = useQuery<{ data: ViewSchema[] }, Error>({
const viewsQuery = useQuery<ViewSchema[], Error>({
queryKey: ['views', tableId],
queryFn: () => apiClient.get(`/views?table_id=${tableId}`),
queryFn: () => repository.views.listByTable(tableId),
});

interface RecordData {
Expand All @@ -55,15 +56,15 @@ export function RecordDetailView({

const recordQuery = useQuery<RecordData, Error>({
queryKey: ['records', recordId],
queryFn: () => apiClient.get(`/records/${recordId}`),
queryFn: () => repository.records.get(recordId),
enabled: !!recordId,
});

const databaseId = propsDatabaseId;

const tablesQuery = useQuery<{ id: string; name: string }[], Error>({
queryKey: ['tables', databaseId],
queryFn: () => apiClient.get(`/tables?database_id=${databaseId}`),
queryFn: () => repository.tables.list(databaseId),
enabled: !!databaseId,
});

Expand All @@ -73,14 +74,16 @@ export function RecordDetailView({

const referencingQuery = useQuery<{ data: ReferencingRecordItem[] }, Error>({
queryKey: ['records', recordId, 'referencing'],
queryFn: () => apiClient.get(`/records/${recordId}/referencing-records`),
queryFn: () => repository.records.referencingRecords(recordId),
enabled: !!recordId,
});

const referencingRecords = referencingQuery.data?.data || [];

const activeView = useMemo(() => {
const list = viewsQuery.data?.data || [];
// The endpoint answers with a bare array; reading `.data` off it silently
// yielded undefined, so this list was always empty and no card view ever applied.
const list = viewsQuery.data || [];
const singleDefault = list.find((v) => v.is_single_default);
if (singleDefault) return singleDefault;
const cardView = list.find((v) => v.type === 'card');
Expand Down Expand Up @@ -317,17 +320,18 @@ function ReferenceLabel({
className?: string;
databaseId: string;
}) {
const repository = useRepository();
const recordQuery = useQuery<ApiRecord, Error>({
queryKey: ['records', targetRecordId],
queryFn: () => apiClient.get(`/records/${targetRecordId}`),
queryFn: () => repository.records.get(targetRecordId),
enabled: !!targetRecordId && targetRecordId !== '--',
});

const targetTableId = recordQuery.data?.table_id;

const fieldsQuery = useQuery<BuilderField[], Error>({
queryKey: ['fields', targetTableId],
queryFn: () => apiClient.get(`/fields?table_id=${targetTableId}`),
queryFn: () => repository.fields.listByTable(targetTableId!),
enabled: !!targetTableId,
});

Expand Down Expand Up @@ -431,9 +435,10 @@ function ReferencingRecordRow({
databaseId?: string;
tableName?: string;
}) {
const repository = useRepository();
const fieldsQuery = useQuery<BuilderField[], Error>({
queryKey: ['fields', tableId],
queryFn: () => apiClient.get(`/fields?table_id=${tableId}`),
queryFn: () => repository.fields.listByTable(tableId),
enabled: !!tableId,
});

Expand Down
12 changes: 7 additions & 5 deletions apps/client/src/components/RecordForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useState, useEffect, useRef, useMemo, type ComponentType, type ReactNod
import { useForm, useStore } from '@tanstack/react-form';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useI18n } from '../contexts/I18nContext';
import { apiClient } from '../lib/apiClient';
import { useRepository } from '../contexts/RepositoryContext';
import type { ApiRecord, ApiRecordData, ApiValue } from '../lib/apiTypes';
import { type BuilderField } from '../lib/fieldTypes';
import { GpsMapPicker } from './GpsMapPicker';
Expand Down Expand Up @@ -62,6 +62,7 @@ export function RecordForm({
}: RecordFormProps) {
const { t } = useI18n();
const queryClient = useQueryClient();
const repository = useRepository();
const formRef = useRef<HTMLFormElement>(null);

// Modal state for inline reference creation
Expand All @@ -82,7 +83,7 @@ export function RecordForm({

const recordQuery = useQuery<ApiRecord, Error>({
queryKey: ['records', recordId],
queryFn: () => apiClient.get(`/records/${recordId}`),
queryFn: () => repository.records.get(recordId!),
enabled: !!recordId,
});

Expand Down Expand Up @@ -140,7 +141,7 @@ export function RecordForm({

// Mutations
const createMutation = useMutation({
mutationFn: (payload: RecordPayload) => apiClient.post<ApiRecord>('/records', payload),
mutationFn: (payload: RecordPayload) => repository.records.create(payload),
onSuccess: (response) => {
queryClient.invalidateQueries({ queryKey: ['records'] });
queryClient.invalidateQueries({ queryKey: ['records-select'] });
Expand All @@ -150,7 +151,7 @@ export function RecordForm({

const updateMutation = useMutation({
mutationFn: (payload: RecordPayload) =>
apiClient.put<ApiRecord>(`/records/${recordId}`, {
repository.records.update(recordId!, {
data: payload.data,
version: payload.version,
}),
Expand Down Expand Up @@ -692,13 +693,14 @@ function ReferenceFieldEditor({
onOpenInlineModal,
}: ReferenceFieldEditorProps) {
const { t } = useI18n();
const repository = useRepository();
const targetTableId = String(field.options?.target_table || '');
const [searchQuery, setSearchQuery] = useState('');

// Fetch referencing records of target table
const recordsQuery = useQuery<{ data: ApiRecord[] }, Error>({
queryKey: ['records-select', targetTableId],
queryFn: () => apiClient.get(`/records?table_id=${targetTableId}&per_page=100`),
queryFn: () => repository.records.list({ table_id: targetTableId, per_page: 100 }),
enabled: !!targetTableId,
});

Expand Down
10 changes: 4 additions & 6 deletions apps/client/src/components/RecordHistoryPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useI18n } from '../contexts/I18nContext';
import { apiClient } from '../lib/apiClient';
import { useRepository } from '../contexts/RepositoryContext';
import type { ApiRecordData, ApiValue } from '../lib/apiTypes';
import { LoadingSpinner } from './LoadingSpinner';
import { formatDistanceToNow } from 'date-fns';
Expand Down Expand Up @@ -32,20 +32,18 @@ interface RecordHistoryPanelProps {
}

export function RecordHistoryPanel({ recordId, onRestoreSuccess }: RecordHistoryPanelProps) {
const repository = useRepository();
const { t } = useI18n();
const queryClient = useQueryClient();

const historyQuery = useQuery<{ data: HistoryLog[] }, Error>({
queryKey: ['records-history', recordId],
queryFn: () => apiClient.get(`/records/${recordId}/history`),
queryFn: () => repository.records.history(recordId),
enabled: !!recordId,
});

const restoreMutation = useMutation({
mutationFn: (logId: number) =>
apiClient.post(`/records/${recordId}/restore-version`, {
log_id: logId,
}),
mutationFn: (logId: number) => repository.records.restoreVersion(recordId, logId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['records'] });
onRestoreSuccess();
Expand Down
Loading
Loading