diff --git a/frontend/src/components/db-table-view/body-cell.tsx b/frontend/src/components/db-table-view/body-cell.tsx
new file mode 100644
index 0000000..cbb6700
--- /dev/null
+++ b/frontend/src/components/db-table-view/body-cell.tsx
@@ -0,0 +1,26 @@
+import { cn } from "@/lib/utils";
+import type { ComponentPropsWithoutRef } from "react";
+
+export function BodyCell({
+ value,
+ dataType,
+ className,
+ ...rest
+}: { value: any; dataType: string } & Omit<
+ ComponentPropsWithoutRef<"div">,
+ "children"
+>) {
+ return (
+
+ {value}
+
+ );
+}
diff --git a/frontend/src/components/db-table-view/columns.tsx b/frontend/src/components/db-table-view/columns.tsx
index a8fd73b..498553c 100644
--- a/frontend/src/components/db-table-view/columns.tsx
+++ b/frontend/src/components/db-table-view/columns.tsx
@@ -1,18 +1,17 @@
-import { cn, isImageUrl, isUrl } from "@/lib/utils";
+import { BodyCell } from "@/components/db-table-view/body-cell";
+import { UrlWithPreview } from "@/components/db-table-view/url-with-preview";
+import { cn, isUrl } from "@/lib/utils";
import { type TableColumns, useTableColumnsQuery } from "@/services/db";
import { useSettingsStore } from "@/state";
import type { ColumnDef } from "@tanstack/react-table";
-import { useMemo } from "react";
+import { useMemo, useState } from "react";
const buildColumns = ({
columns,
formatDates,
- showImagesPreview,
}: {
columns?: TableColumns;
-
formatDates: boolean;
- showImagesPreview: boolean;
}): ColumnDef[] => {
if (!columns) return [] as ColumnDef[];
@@ -21,6 +20,7 @@ const buildColumns = ({
title: column_name,
size: 300,
id: column_name,
+ enableSorting: true,
header: () => {
return (
({
);
},
- enableSorting: true,
- cell: ({ row }) => {
- const value = row.getValue(column_name) as any;
+ cell: ({ row, table, column }) => {
+ const initialValue = row.getValue(column_name) as any;
+ const [value, setValue] = useState(initialValue);
+
+ const [isEditing, setIsEditing] = useState(false);
+ function handleDoubleClick() {
+ setIsEditing(true);
+ }
+ function handleSave() {
+ if (value === initialValue) return;
+ (table.options.meta as any)?.setEditedRows((old) => ({
+ ...old,
+ [row.index]: true,
+ }));
+ (table.options.meta as any)?.updateData(row.index, column.id, value);
+ setIsEditing(false);
+ }
+ if (isEditing) {
+ return (
+
+ autoFocus={true}
+ className={"w-full focus:ring"}
+ value={value}
+ onChange={(e) => setValue(e.target.value)}
+ onBlur={handleSave}
+ />
+ );
+ }
let finalValue = value;
if (
formatDates &&
@@ -43,43 +69,18 @@ const buildColumns = ({
) {
finalValue = new Date(value as string).toLocaleString();
}
- if (showImagesPreview && typeof value === "string" && isUrl(value)) {
- const isImage = isImageUrl(value);
- return (
-
-
- {value}
- {isImage && (
-

- )}
-
-
- );
+ if (typeof value === "string" && isUrl(value)) {
+ return ;
}
if (typeof finalValue === "boolean") {
finalValue = finalValue ? "true" : "false";
}
return (
-
- {finalValue}
-
+
);
},
})) as ColumnDef[];
@@ -91,13 +92,11 @@ export const useColumns = ({
}: { dbName: string; tableName: string }) => {
const { data: details } = useTableColumnsQuery({ dbName, tableName });
const formatDates = useSettingsStore.use.formatDates();
- const showImagesPreview = useSettingsStore.use.showImagesPreview();
return useMemo(() => {
return buildColumns({
columns: details,
formatDates,
- showImagesPreview,
});
- }, [details, formatDates, showImagesPreview]);
+ }, [details, formatDates]);
};
diff --git a/frontend/src/components/db-table-view/url-with-preview.tsx b/frontend/src/components/db-table-view/url-with-preview.tsx
new file mode 100644
index 0000000..6814ea2
--- /dev/null
+++ b/frontend/src/components/db-table-view/url-with-preview.tsx
@@ -0,0 +1,28 @@
+import { cn, isImageUrl } from "@/lib/utils";
+import { useSettingsStore } from "@/state";
+import { memo } from "react";
+
+const RawUrlWithPreview = ({ url }: { url: string }) => {
+ const showImagesPreview = useSettingsStore.use.showImagesPreview();
+ const isImage = showImagesPreview && isImageUrl(url);
+
+ return (
+
+
+ {url}
+ {isImage && (
+

+ )}
+
+
+ );
+};
+
+export const UrlWithPreview = memo(RawUrlWithPreview);
+
+UrlWithPreview.displayName = "UrlWithPreview";
diff --git a/frontend/src/routes/db/$dbName/tables/$tableName/data.tsx b/frontend/src/routes/db/$dbName/tables/$tableName/data.tsx
index 584870a..d0ece62 100644
--- a/frontend/src/routes/db/$dbName/tables/$tableName/data.tsx
+++ b/frontend/src/routes/db/$dbName/tables/$tableName/data.tsx
@@ -86,6 +86,14 @@ function Component() {
sortDesc: filters.sortDesc,
});
+ const [tableData, setTableData] = useState>>([]);
+ console.log(tableData);
+ const [editedRows, setEditedRows] = useState({});
+ console.log("editedRows", editedRows);
+ useEffect(() => {
+ setTableData(structuredClone(data?.data) ?? []);
+ }, [data]);
+
const handleWhereClauseFormSubmit = useCallback(
({ whereClause }: WhereClauseFormValues) => {
if (whereClause === whereQuery) {
@@ -120,9 +128,26 @@ function Component() {
);
const table = useReactTable({
+ meta: {
+ editedRows,
+ setEditedRows,
+ updateData: (rowIndex: number, columnId: string, value: string) => {
+ setTableData((old) =>
+ old.map((row, index) => {
+ if (index === rowIndex) {
+ return {
+ ...old[rowIndex],
+ [columnId]: value,
+ };
+ }
+ return row;
+ }),
+ );
+ },
+ },
columnResizeMode: "onChange",
columns,
- data: data?.data ?? [],
+ data: tableData,
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
manualSorting: true,
diff --git a/frontend/src/services/db/db.hooks.ts b/frontend/src/services/db/db.hooks.ts
index 86afa64..9a643ea 100644
--- a/frontend/src/services/db/db.hooks.ts
+++ b/frontend/src/services/db/db.hooks.ts
@@ -42,6 +42,12 @@ export const useTablesListQuery = (args: GetTablesListArgs) => {
export const useTableDataQuery = (args: GetTableDataArgs) => {
return useQuery({
+ refetchOnWindowFocus: false,
+ refetchOnReconnect: false,
+ refetchOnMount: false,
+ refetchInterval: false,
+ refetchIntervalInBackground: false,
+ staleTime: Number.POSITIVE_INFINITY,
queryKey: [DB_QUERY_KEYS.TABLES.DATA, args],
queryFn: () => dbService.getTableData(args),
placeholderData: (previousData, previousQuery) => {