Skip to content

Commit c0b5c0b

Browse files
ClemsazertSimonClo
authored andcommitted
✨ connection to existing table
1 parent 2e24657 commit c0b5c0b

File tree

7 files changed

+258
-24
lines changed

7 files changed

+258
-24
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { useState } from 'react';
2+
3+
import { Box, DropdownMenu, Text } from '@/components';
4+
import { useListGristTables } from '@/features/grist';
5+
import { Doc, useListGristDocs } from '@/features/grist/useListGristDocs';
6+
7+
type DatabaseSourceSelectorProps = {
8+
onSourceSelected: (args: { documentId: string; tableId: string }) => void;
9+
};
10+
11+
export const DatabaseSourceSelector = ({
12+
onSourceSelected,
13+
}: DatabaseSourceSelectorProps) => {
14+
const [selectedDoc, setSelectedDoc] = useState<Doc>();
15+
const { docs } = useListGristDocs();
16+
const { tables } = useListGristTables(selectedDoc?.id);
17+
18+
return (
19+
<Box>
20+
<DropdownMenu
21+
options={docs.map((doc) => ({
22+
label: doc.name,
23+
value: doc.id,
24+
callback: () => setSelectedDoc(doc),
25+
}))}
26+
showArrow
27+
>
28+
<Text>{selectedDoc?.name ?? 'Sélectionner un document Grist'}</Text>
29+
</DropdownMenu>
30+
{selectedDoc && tables && (
31+
<DropdownMenu
32+
options={tables.map(({ id }) => ({
33+
label: id,
34+
value: id,
35+
callback: () =>
36+
onSourceSelected({ documentId: selectedDoc.id, tableId: id }),
37+
}))}
38+
showArrow
39+
>
40+
<Text>Sélectionner une table Grist existante</Text>
41+
</DropdownMenu>
42+
)}
43+
</Box>
44+
);
45+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { useGristTableData } from '@/features/grist/useGristTableData';
2+
3+
export const DatabaseTableDisplay = ({
4+
documentId,
5+
tableId,
6+
}: {
7+
documentId: string;
8+
tableId: string;
9+
}) => {
10+
const { tableData } = useGristTableData({
11+
documentId,
12+
tableId,
13+
});
14+
15+
return JSON.stringify(tableData, null, 2);
16+
};

src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/DatabaseBlock.tsx

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
/* eslint-disable react-hooks/rules-of-hooks */
2-
import { defaultProps, insertOrUpdateBlock } from '@blocknote/core';
2+
import { insertOrUpdateBlock } from '@blocknote/core';
33
import { createReactBlockSpec } from '@blocknote/react';
4+
import { Button } from '@openfun/cunningham-react';
45
import { TFunction } from 'i18next';
5-
import React, { useEffect } from 'react';
66

7-
import { Box, Icon } from '@/components';
8-
import { useGristTable } from '@/features/grist';
7+
import { Box, Icon, Text } from '@/components';
98

109
import { DocsBlockNoteEditor } from '../../types';
10+
import { DatabaseSourceSelector } from '../DatabaseSourceSelector';
11+
import { DatabaseTableDisplay } from '../DatabaseTableDisplay';
1112

1213
export const DatabaseBlock = createReactBlockSpec(
1314
{
1415
type: 'database',
1516
propSchema: {
16-
textAlignment: defaultProps.textAlignment,
17-
backgroundColor: defaultProps.backgroundColor,
1817
documentId: {
1918
type: 'string',
2019
default: '',
@@ -27,23 +26,7 @@ export const DatabaseBlock = createReactBlockSpec(
2726
content: 'inline',
2827
},
2928
{
30-
render: ({ block, editor, contentRef }) => {
31-
const { tableData } = useGristTable({
32-
tableId: block.props.tableId,
33-
documentId: block.props.documentId,
34-
});
35-
36-
useEffect(() => {
37-
if (
38-
!block.content.length &&
39-
block.props.backgroundColor === 'default'
40-
) {
41-
editor.updateBlock(block, { props: { backgroundColor: 'orange' } });
42-
} else {
43-
editor.updateBlock(block, { content: JSON.stringify(tableData) });
44-
}
45-
}, [block, editor, tableData]);
46-
29+
render: ({ block, editor }) => {
4730
return (
4831
<Box
4932
$padding="1rem"
@@ -53,7 +36,35 @@ export const DatabaseBlock = createReactBlockSpec(
5336
flexDirection: 'row',
5437
}}
5538
>
56-
<Box as="p" className="inline-content" ref={contentRef} />
39+
<Box as="div" />
40+
{block.props.documentId && block.props.tableId ? (
41+
<Box>
42+
<DatabaseTableDisplay
43+
documentId={block.props.documentId}
44+
tableId={block.props.tableId}
45+
/>
46+
</Box>
47+
) : (
48+
<Box
49+
style={{
50+
flexDirection: 'column',
51+
gap: 10,
52+
alignItems: 'center',
53+
justifyContent: 'center',
54+
width: '100%',
55+
}}
56+
>
57+
<Button>Créer une nouvelle base de données vide</Button>
58+
<Text>ou</Text>
59+
<DatabaseSourceSelector
60+
onSourceSelected={({ documentId, tableId }) => {
61+
editor.updateBlock(block, {
62+
props: { documentId: documentId.toString(), tableId },
63+
});
64+
}}
65+
/>
66+
</Box>
67+
)}
5768
</Box>
5869
);
5970
},
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
export * from './useGristTable';
2+
export * from './useListGristDocs';
3+
export * from './useListGristTables';
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { useEffect, useState } from 'react';
2+
3+
import { APIError, errorCauses, gristFetchApi } from '@/api';
4+
5+
export type UseGristTableDataArguments = {
6+
documentId: string;
7+
tableId: string;
8+
};
9+
10+
export const useGristTableData = ({
11+
documentId,
12+
tableId,
13+
}: UseGristTableDataArguments) => {
14+
const [tableData, setTableData] = useState<unknown[]>([]);
15+
16+
useEffect(() => {
17+
const fetchData = async () => {
18+
const url = `docs/${documentId}/tables/${tableId}/data`;
19+
const response = await gristFetchApi(url);
20+
if (!response.ok) {
21+
throw new APIError(
22+
'Failed to request ai transform',
23+
await errorCauses(response),
24+
);
25+
}
26+
return (await response.json()) as Promise<unknown[]>;
27+
};
28+
29+
fetchData()
30+
.then((res) => {
31+
setTableData(res);
32+
})
33+
.catch((error) => {
34+
console.error('Error fetching Grist table data:', error);
35+
});
36+
}, [documentId, tableId]);
37+
return {
38+
tableData,
39+
};
40+
};
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { useEffect, useState } from 'react';
2+
3+
import { APIError, errorCauses, gristFetchApi } from '@/api';
4+
5+
export interface Workspace {
6+
id: number;
7+
name: string;
8+
access: string;
9+
docs: Doc[];
10+
org: Org;
11+
}
12+
13+
export interface Doc {
14+
id: number;
15+
name: string;
16+
access: string;
17+
isPinned: boolean;
18+
urlId: null;
19+
}
20+
21+
export interface Org {
22+
id: number;
23+
name: string;
24+
domain: string;
25+
owner: Owner;
26+
access: string;
27+
createdAt: Date;
28+
updatedAt: Date;
29+
}
30+
31+
export interface Owner {
32+
id: number;
33+
name: string;
34+
picture: null;
35+
}
36+
37+
export const useListGristDocs = (): { docs: Doc[] } => {
38+
const [docs, setDocs] = useState<Doc[]>([]);
39+
40+
const fetchDocs = async () => {
41+
const DEFAULT_WORKSPACE_ID = 2;
42+
const url = `workspaces/${DEFAULT_WORKSPACE_ID}`;
43+
const response = await gristFetchApi(url);
44+
if (!response.ok) {
45+
throw new APIError(
46+
'Failed to request ai transform',
47+
await errorCauses(response),
48+
);
49+
}
50+
return (await response.json()) as Promise<Workspace>;
51+
};
52+
53+
useEffect(() => {
54+
fetchDocs()
55+
.then((workspace) => {
56+
setDocs(workspace.docs);
57+
})
58+
.catch((error) => {
59+
console.error('Error fetching Grist documents:', error);
60+
});
61+
}, []);
62+
63+
return {
64+
docs,
65+
};
66+
};
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { useEffect, useState } from 'react';
2+
3+
import { APIError, errorCauses, gristFetchApi } from '@/api';
4+
5+
export interface TableDescription {
6+
tables: Table[];
7+
}
8+
9+
export interface Table {
10+
id: string;
11+
fields: Fields;
12+
}
13+
14+
export interface Fields {
15+
tableRef: number;
16+
onDemand: boolean;
17+
}
18+
19+
export const useListGristTables = (
20+
documentId?: number,
21+
): { tables: Table[] | null } => {
22+
const [tables, setTables] = useState<Table[] | null>(null);
23+
24+
useEffect(() => {
25+
const fetchTables = async () => {
26+
if (!documentId) {
27+
console.warn('Document ID is required to fetch Grist tables');
28+
return;
29+
}
30+
const url = `docs/${documentId}/tables`;
31+
const response = await gristFetchApi(url);
32+
if (!response.ok) {
33+
throw new APIError(
34+
'Failed to fetch Grist tables',
35+
await errorCauses(response),
36+
);
37+
}
38+
return (await response.json()) as Promise<TableDescription>;
39+
};
40+
fetchTables()
41+
.then((response) => {
42+
if (response) {
43+
setTables(response.tables);
44+
}
45+
})
46+
.catch((error) => {
47+
console.error('Error fetching Grist documents:', error);
48+
});
49+
}, [documentId]);
50+
51+
return {
52+
tables,
53+
};
54+
};

0 commit comments

Comments
 (0)