diff --git a/src/components/auto-complete/auto-complete.tsx b/src/components/auto-complete/auto-complete.tsx index 33a404e..3def6c1 100644 --- a/src/components/auto-complete/auto-complete.tsx +++ b/src/components/auto-complete/auto-complete.tsx @@ -4,18 +4,18 @@ import { Key } from "react"; interface AutocompleteProps { - items: any[] + items?: any[] label?: string, - defaultSelection: string | undefined + defaultSelection?: string | undefined onSelectionChange?: (item: any) => void, placeholder?: string + isDisabled?: boolean + selectedItem?: Key } -const Autocomplete: React.FC = ({ items, label = "name", onSelectionChange, defaultSelection, placeholder }) => { - - +const Autocomplete: React.FC = ({ items, label = "name", onSelectionChange, defaultSelection, placeholder, isDisabled, selectedItem }) => { const onItemChange = (item: Key | null) => { onSelectionChange && onSelectionChange(item); @@ -23,18 +23,21 @@ const Autocomplete: React.FC = ({ items, label = "name", onSe return ( {(item: any) => {item[label]}} ) } -export default Autocomplete ; \ No newline at end of file +export default Autocomplete; \ No newline at end of file diff --git a/src/components/hooks/use-relationship-name.tsx b/src/components/hooks/use-relationship-name.tsx new file mode 100644 index 0000000..8609c81 --- /dev/null +++ b/src/components/hooks/use-relationship-name.tsx @@ -0,0 +1,14 @@ +import { Cardinality, RelationshipType } from "@/lib/schemas/relationship-schema"; +import { useMemo } from "react"; +import { useTranslation } from "react-i18next"; + + +export const useRelationshipName = (relationship: RelationshipType) => { + const { t } = useTranslation(); + const name: string = useMemo(() => { + return `${relationship.sourceTable.name}_${relationship.sourceField.name} - ${relationship.targetTable.name}_${relationship.targetField.name}_fk` + }, [relationship, t]) + return { + name + } +} \ No newline at end of file diff --git a/src/components/modal/modal.tsx b/src/components/modal/modal.tsx new file mode 100644 index 0000000..9b51781 --- /dev/null +++ b/src/components/modal/modal.tsx @@ -0,0 +1,78 @@ + +import React, { ReactNode } from "react"; +import { + Modal as HeroUiModal, + ModalContent, + ModalHeader, + ModalBody, + ModalFooter, + Button, + useDraggable, +} from "@heroui/react"; +import { useTranslation } from "react-i18next"; + + +interface ModalProps { + isOpen?: boolean, + onOpenChange: (open: boolean) => void, + className?: string, + backdrop?: "blur" | "transparent" | "opaque", + title: string, + children: ReactNode, + actionName?: string, + actionHandler?: () => void , + isDisabled? : boolean + +} + +const Modal: React.FC = ({ isOpen, onOpenChange, className, backdrop = "opaque", title, children, actionName = "Action" , actionHandler , isDisabled}) => { + + + const targetRef = React.useRef(null); + const { moveProps } = useDraggable({ targetRef, canOverflow: true, isDisabled: !isOpen }); + + const {t} = useTranslation() ; + + const handleAction = (onClose: () => void) => { + actionHandler && actionHandler() ; + onClose() + } + return ( + + + {(onClose) => ( + <> + + {title} + + + {children} + + +
+ + +
+
+ + )} +
+
+ ) + +} + + +export default Modal; diff --git a/src/i18/languages/en.ts b/src/i18/languages/en.ts index 932226a..cc2bdc2 100644 --- a/src/i18/languages/en.ts +++ b/src/i18/languages/en.ts @@ -1,56 +1,73 @@ import { Language } from "../types"; -export const en = { +export const en = { - translation : { - sidebar : { - tables : "Tables" , - relationships : "Relationships" - } , - db_controller : { - filter : "Filter" , - add_table : "Add Table" , - add_field : "Add Field" , - add_index : "Add Index" , - add_relationship : "Add Relationship" , - show_code : "Show code" , - fields : "Fields" , - indexes : "Indexes" , - note : "Note" , - name : "Name" , - type : "Type" , - primary_key : "Primary Key" , - nullable : "Nullable" , - select_fields : "Select fields" , - unique : "Unique" , - table_note : "Table note" , - collapse : "Collapse All" , - primary_table : "Primary Table" , - referenced_table : "Referenced Table" , - cardinality :{ - name : "Cardinality" , - one_to_one : "One to One" , - one_to_many : "One to Many" , - many_to_one : "Many to One" , - many_to_many : "Many to Many" , - } , - delete : "Delete" , - field_setting : "Field Setting" , - table_actions : "Field Actions" , - field_note : "Field note" , - delete_field : "Delete Field" , - - }, - table : { - double_click : "Double click to edit" + translation: { + sidebar: { + tables: "Tables", + relationships: "Relationships" + }, + modal: { + close: "Close", + create: "Create" + }, + db_controller: { + filter: "Filter", + add_table: "Add Table", + add_field: "Add Field", + add_index: "Add Index", + add_relationship: "Add Relationship", + show_code: "Show code", + fields: "Fields", + indexes: "Indexes", + note: "Note", + name: "Name", + type: "Type", + nullable: "Nullable", + select_fields: "Select fields", + unique: "Unique", + table_note: "Table note", + collapse: "Collapse All", + + + + primary_key: "Primary Key", + foreign_key: "Foreign Key", + + source_table: "Source Table", + target_table: "Target Table", + + select_table: "Select table", + select_field: "Select field", + + + cardinality: { + name: "Cardinality", + one_to_one: "One to One", + one_to_many: "One to Many", + many_to_one: "Many to One", + many_to_many: "Many to Many", + + }, + delete: "Delete", + field_setting: "Field Setting", + table_actions: "Field Actions", + field_note: "Field note", + delete_field: "Delete Field", + + create_relationship: "Create Relationship", + relationship_error: "To create a relationship, the primary key and foreign key must be of the same type.", + }, + table: { + double_click: "Double click to edit" } } -} +} -export const enLanguage : Language = { - name : "English" , +export const enLanguage: Language = { + name: "English", nativeName: 'English', code: 'en', } \ No newline at end of file diff --git a/src/lib/interfaces/relationship.ts b/src/lib/interfaces/relationship.ts deleted file mode 100644 index f229e61..0000000 --- a/src/lib/interfaces/relationship.ts +++ /dev/null @@ -1,22 +0,0 @@ - -export interface Relationship { - id: string; - name: string; - sourceSchema?: string; - sourceTableId: string; - targetSchema?: string; - targetTableId: string; - sourceFieldId: string; - targetFieldId: string; - sourceCardinality: Cardinality; - targetCardinality: Cardinality; - createdAt: number; -} - - -export type RelationshipType = - | 'one_to_one' - | 'one_to_many' - | 'many_to_one' - | 'many_to_many'; -export type Cardinality = 'one' | 'many'; \ No newline at end of file diff --git a/src/lib/schemas/app-schema.ts b/src/lib/schemas/app-schema.ts index c27f09a..e7fadd0 100644 --- a/src/lib/schemas/app-schema.ts +++ b/src/lib/schemas/app-schema.ts @@ -3,16 +3,18 @@ import { DrizzleAppSchema, wrapPowerSyncWithDrizzle } from '@powersync/drizzle-d import { data_types } from './data-type-schema'; import { tables, tablesRelations } from './table-schema'; import { fields, fieldsRelations } from './field-schema'; +import { relationshipRelations, relationships } from './relationship-schema'; export const drizzleSchema = { data_types , tables , fields , - + relationships , // relationships tablesRelations , - fieldsRelations + fieldsRelations , + relationshipRelations , }; // Infer the PowerSync schema from your Drizzle schema diff --git a/src/lib/schemas/field-schema.ts b/src/lib/schemas/field-schema.ts index 20c68ad..88bd226 100644 --- a/src/lib/schemas/field-schema.ts +++ b/src/lib/schemas/field-schema.ts @@ -2,6 +2,7 @@ import { sqliteTable, text, real, integer } from 'drizzle-orm/sqlite-core'; import { tables } from './table-schema'; import { data_types } from './data-type-schema'; import { InferInsertModel, InferSelectModel, relations } from 'drizzle-orm'; +import { relationships } from './relationship-schema'; export const fields = sqliteTable("fields", { @@ -24,7 +25,11 @@ export const fieldsRelations = relations(fields, ({ one }) => ({ table: one(tables, { fields: [fields.tableId], references: [tables.id], + }), + sourceRelations: one(relationships), + targetRelations: one(relationships), + })); diff --git a/src/lib/schemas/relationship-schema.ts b/src/lib/schemas/relationship-schema.ts new file mode 100644 index 0000000..a955f24 --- /dev/null +++ b/src/lib/schemas/relationship-schema.ts @@ -0,0 +1,83 @@ +import { + sqliteTable, + text, +} from 'drizzle-orm/sqlite-core'; +import { InferInsertModel, InferSelectModel, relations } from 'drizzle-orm'; +import { tables, TableType } from './table-schema'; +import { fields, FieldType } from './field-schema'; + +export const relationships = sqliteTable('relationships', { + id: text('id').primaryKey().notNull(), + + name: text('name'), + + sourceTableId: text('sourceTableId') + .notNull() + .references(() => tables.id, { onDelete: 'cascade' }), + + targetTableId: text('targetTableId') + .notNull() + .references(() => tables.id, { onDelete: 'cascade' }), + + sourceFieldId: text('sourceFieldId') + .notNull() + .references(() => fields.id, { onDelete: 'cascade' }), + + targetFieldId: text('targetFieldId') + .notNull() + .references(() => fields.id, { onDelete: 'cascade' }), + + cardinality: text('cardinality', { + enum: [ + 'one_to_one', + 'one_to_many', + 'many_to_one', + 'many_to_many', + ], + }) + .notNull() + .default('one_to_many'), + + sourceAliasName: text('sourceAliasName'), + targetAliasName: text('targetAliasName'), + createdAt: text('createdAt'), +}); + + +export const relationshipRelations = relations(relationships, ({ one }) => ({ + sourceTable: one(tables, { + fields: [relationships.sourceTableId], + references: [tables.id], + }), + targetTable: one(tables, { + fields: [relationships.targetTableId], + references: [tables.id], + }), + sourceField: one(fields, { + fields: [relationships.sourceFieldId], + references: [fields.id], + }), + targetField: one(fields, { + fields: [relationships.targetFieldId], + references: [fields.id], + }), +})); + + +export interface RelationshipType extends InferSelectModel { + sourceTable : TableType , + targetTable : TableType , + sourceField : FieldType , + targetField : FieldType , + }; + +export interface RelationshipInsertType extends InferInsertModel { }; + + +export enum Cardinality { + one_to_one = "one_to_one", + one_to_many = "one_to_many", + many_to_one = "many_to_one", + many_to_many = "many_to_many" + +} \ No newline at end of file diff --git a/src/lib/schemas/table-schema.ts b/src/lib/schemas/table-schema.ts index 720521a..5407587 100644 --- a/src/lib/schemas/table-schema.ts +++ b/src/lib/schemas/table-schema.ts @@ -1,6 +1,7 @@ import { InferInsertModel, InferSelectModel, relations } from 'drizzle-orm'; import { sqliteTable, text, real, integer } from 'drizzle-orm/sqlite-core'; import { fields, FieldType } from './field-schema'; +import { relationships } from './relationship-schema'; export const tables = sqliteTable('tables', { id: text('id') @@ -25,15 +26,17 @@ export const tables = sqliteTable('tables', { export const tablesRelations = relations(tables, ({ many }) => ({ fields: many(fields), + sourceRelations: many(relationships), + targetRelations: many(relationships), })); -export interface TableType extends InferSelectModel { - fields : FieldType[] -}; +export interface TableType extends InferSelectModel { + fields: FieldType[] +}; -export interface TableInsertType extends InferInsertModel { - fields? : FieldType[] +export interface TableInsertType extends InferInsertModel { + fields?: FieldType[] }; \ No newline at end of file diff --git a/src/pages/database/db-controller/relationship-controller/create-relationship-form/create-relationship-form.tsx b/src/pages/database/db-controller/relationship-controller/create-relationship-form/create-relationship-form.tsx new file mode 100644 index 0000000..a4623c7 --- /dev/null +++ b/src/pages/database/db-controller/relationship-controller/create-relationship-form/create-relationship-form.tsx @@ -0,0 +1,133 @@ +import Autocomplete from "@/components/auto-complete/auto-complete"; +import { FieldType } from "@/lib/schemas/field-schema"; +import { RelationshipInsertType } from "@/lib/schemas/relationship-schema"; +import { TableType } from "@/lib/schemas/table-schema"; +import { useDatabase } from "@/providers/database-provider/database-provider"; +import { FileKey, FileMinus2, FileOutput, Key, KeyRound } from "lucide-react"; +import { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; + + + + +interface CreateRelationshipFormProps { + onRelationshipChanges: (relationship: RelationshipInsertType) => void, + onValidationChanges?: (isValid: boolean) => void + +} + + +const CreateRelationshipForm: React.FC = ({ onRelationshipChanges, onValidationChanges }) => { + + const [relationship, setRelationship] = useState({ + sourceTableId: "", + targetTableId: "", + sourceFieldId: "", + targetFieldId: "" + } as RelationshipInsertType) + + + const { tables } = useDatabase(); + const { t } = useTranslation(); + + const sourceFields: FieldType[] = useMemo(() => { + const sourceTable: TableType | undefined = tables.find((table: TableType) => table.id == relationship.sourceTableId); + return sourceTable?.fields ? sourceTable.fields : []; + }, [tables, relationship.sourceTableId]); + + + const targetFields: FieldType[] = useMemo(() => { + const targetTable: TableType | undefined = tables.find((table: TableType) => table.id == relationship.targetTableId); + return targetTable?.fields ? targetTable.fields : []; + }, [tables, relationship.targetTableId]); + + + const fieldTypesMatches: boolean = useMemo(() => { + const sourceField: FieldType | undefined = sourceFields.find((field: FieldType) => field.id == relationship.sourceFieldId); + const targetField: FieldType | undefined = targetFields.find((field: FieldType) => field.id == relationship.targetFieldId); + if (sourceField && targetField) + return sourceField.typeId == targetField.typeId; + else + return true; + }, [relationship, sourceFields, targetFields]) + + + useEffect(() => setRelationship({ ...relationship, sourceFieldId: "" }), [sourceFields]); + useEffect(() => setRelationship({ ...relationship, targetFieldId: "" }), [targetFields]); + + useEffect(() => { + onRelationshipChanges && onRelationshipChanges(relationship) + }, [relationship , sourceFields , targetFields]); + + useEffect(() => { + onValidationChanges && onValidationChanges ((relationship.sourceFieldId && relationship.targetFieldId && fieldTypesMatches) as boolean) + }, [relationship, fieldTypesMatches]) + + return ( + <> +
+
+ + setRelationship({ ...relationship, sourceTableId: key })} + /> +
+
+ + + setRelationship({ ...relationship, targetTableId: key })} + /> +
+
+ + setRelationship({ ...relationship, sourceFieldId: key })} + isDisabled={!relationship.sourceTableId} + selectedItem={relationship.sourceFieldId} + /> +
+
+ + setRelationship({ ...relationship, targetFieldId: key })} + isDisabled={!relationship.targetTableId} + items={targetFields} + selectedItem={relationship.targetFieldId} + /> + +
+
+ { + !fieldTypesMatches && +
+ {t("db_controller.relationship_error")} + +
+ } + + ) +} + + +export default CreateRelationshipForm; \ No newline at end of file diff --git a/src/pages/database/db-controller/relationship-controller/relationship-accordion-item/relationship-accordion-body.tsx b/src/pages/database/db-controller/relationship-controller/relationship-accordion-item/relationship-accordion-body.tsx index 5800a35..167fb2a 100644 --- a/src/pages/database/db-controller/relationship-controller/relationship-accordion-item/relationship-accordion-body.tsx +++ b/src/pages/database/db-controller/relationship-controller/relationship-accordion-item/relationship-accordion-body.tsx @@ -1,45 +1,59 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip"; -import { Button, Select, SelectItem } from "@heroui/react"; +import { Cardinality, RelationshipInsertType, RelationshipType } from "@/lib/schemas/relationship-schema"; +import { useDatabase } from "@/providers/database-provider/database-provider"; +import { Button, Select, SelectItem, SharedSelection } from "@heroui/react"; import { ChevronsLeftRightEllipsis, FileMinus2, FileOutput, SquareArrowLeft, SquareArrowRight, Trash2 } from "lucide-react"; +import { Key, useState } from "react"; import { useTranslation } from "react-i18next"; -interface Props { - +interface RelationshipAccordionBodyProps { + relationship: RelationshipType } -export const cardinalities = [ - { key: "one2one", label: "One to One" }, - { key: "one2many", label: "One to Many" }, - { key: "many2one", label: "Many to One" }, - { key: "many2many", label: "Many 2 many" }, - -]; -const RelationshipAccordionBody: React.FC = ({ }) => { +const RelationshipAccordionBody: React.FC = ({ relationship }) => { + const [cardinality, setCardinality] = useState(new Set([relationship.cardinality])); + const { editRelationship, deleteRelationship } = useDatabase(); - const {t } = useTranslation() ; + const { t } = useTranslation(); + + + const changeCardinality = (keys: SharedSelection) => { + + if (keys.anchorKey != relationship.cardinality) + editRelationship({ + id: relationship.id, + cardinality: keys.anchorKey as Cardinality + } as RelationshipInsertType); + + setCardinality(keys as any); + } + const removeRelationship = () => { + deleteRelationship(relationship.id); + } return (
- users(id) + {relationship.sourceTable.name}({relationship.sourceField.name}) - users(id) + + {relationship.sourceTable.name}({relationship.sourceField.name})
@@ -47,18 +61,18 @@ const RelationshipAccordionBody: React.FC = ({ }) => {
- products(user_id) + {relationship.targetTable.name}({relationship.targetField.name}) - products(user_id) + {relationship.targetTable.name}({relationship.targetField.name})
@@ -72,16 +86,14 @@ const RelationshipAccordionBody: React.FC = ({ }) => { className="w-full" size="sm" variant="bordered" + selectedKeys={cardinality} + onSelectionChange={changeCardinality} + > - {t("db_controller.cardinality.one_to_one")} - - {t("db_controller.cardinality.one_to_many")} - - {t("db_controller.cardinality.many_to_one")} - - {t("db_controller.cardinality.many_to_many")} - - + {t("db_controller.cardinality.one_to_one")} + {t("db_controller.cardinality.one_to_many")} + {t("db_controller.cardinality.many_to_one")} + {t("db_controller.cardinality.many_to_many")}
@@ -90,6 +102,7 @@ const RelationshipAccordionBody: React.FC = ({ }) => { className="bg-transparent text-xs" radius="sm" size="sm" + onPressEnd={removeRelationship} >
diff --git a/src/pages/database/db-controller/relationship-controller/relationship-accordion-item/relationship-accordion-header.tsx b/src/pages/database/db-controller/relationship-controller/relationship-accordion-item/relationship-accordion-header.tsx index dae570b..12429fb 100644 --- a/src/pages/database/db-controller/relationship-controller/relationship-accordion-item/relationship-accordion-header.tsx +++ b/src/pages/database/db-controller/relationship-controller/relationship-accordion-item/relationship-accordion-header.tsx @@ -1,18 +1,38 @@ +import { useRelationshipName } from "@/components/hooks/use-relationship-name"; import { randomColor } from "@/lib/colors"; +import { RelationshipInsertType, RelationshipType } from "@/lib/schemas/relationship-schema"; +import { useDatabase } from "@/providers/database-provider/database-provider"; import { Button, cn, Input } from "@heroui/react"; import { Check, ChevronRight, EllipsisVertical, Focus, Pencil } from "lucide-react"; import { useState } from "react"; +import { useTranslation } from "react-i18next"; interface RelationshipAccordionHeaderProps { isOpen?: boolean; + relationship: RelationshipType } -const RelationshipAccordionHeader: React.FC = ({ isOpen }) => { +const RelationshipAccordionHeader: React.FC = ({ isOpen, relationship }) => { const [editMode, setEditMode] = useState(false); + const { name: defaultName } = useRelationshipName(relationship); + const { editRelationship } = useDatabase(); + + const [name, setName] = useState(relationship.name ? relationship.name : defaultName) + + + const editRelationshipName = () => { + if (relationship.name || name.trim().toLocaleLowerCase() != defaultName) + editRelationship({ + id: relationship.id, + name + } as RelationshipInsertType); + setEditMode(false); + } + return (
= )}>
- - { editMode && <> = autoFocus size="sm" variant="bordered" - onBlur={() => setEditMode(false)} + value={name} + onValueChange={setName} + onBlur={editRelationshipName} type="text" className="rounded-md px-2 py-0.5 w-full border-blue-400 focus-visible:ring-0 dark:bg-slate-900 text-sm " /> @@ -38,7 +58,7 @@ const RelationshipAccordionHeader: React.FC = variant="light" className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-200" size="sm" - onPress={() => setEditMode(false)} + onPress={editRelationshipName} isIconOnly > @@ -52,7 +72,7 @@ const RelationshipAccordionHeader: React.FC =
diff --git a/src/pages/database/db-controller/relationship-controller/relationship-controller.tsx b/src/pages/database/db-controller/relationship-controller/relationship-controller.tsx index 627aa71..d528658 100644 --- a/src/pages/database/db-controller/relationship-controller/relationship-controller.tsx +++ b/src/pages/database/db-controller/relationship-controller/relationship-controller.tsx @@ -1,10 +1,16 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip" -import { Accordion, AccordionItem, Button, Input } from "@heroui/react" +import { Accordion, AccordionItem, Button, Input, useDisclosure } from "@heroui/react" import { Code, List, ListCollapse, Table, Workflow } from "lucide-react" -import { useState } from "react"; +import { useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import RelationshipAccordionHeader from "./relationship-accordion-item/relationship-accordion-header"; import RelationshipAccordionBody from "./relationship-accordion-item/relationship-accordion-body"; +import Modal from "@/components/modal/modal"; +import CreateRelationshipForm from "./create-relationship-form/create-relationship-form"; +import { RelationshipInsertType, RelationshipType } from "@/lib/schemas/relationship-schema"; +import { useDatabase } from "@/providers/database-provider/database-provider"; +import { v4 } from "uuid"; + @@ -14,10 +20,26 @@ interface Props { const RelationshipController: React.FC = ({ }) => { - const [items, setItems] = useState(["Item 1", "Item 2", "Item 3", "Item 4"]); - - + const [relationship, setRelationship] = useState(undefined); + const [isValid, setIsValid] = useState(false); + const { isOpen, onOpen, onOpenChange } = useDisclosure(); + const { createRelationship, relationships } = useDatabase(); const { t } = useTranslation(); + const [selectedRelationship, setSelectedRelationship] = useState(new Set([])); + + const addRelationship = useCallback(() => { + + const newRelationshipId: string = v4(); + + createRelationship({ + id: newRelationshipId, + ...relationship, + createdAt: new Date().toISOString() + } as RelationshipInsertType); + + setSelectedRelationship(new Set([newRelationshipId]) as any); + }, [relationship]); + return (
@@ -34,6 +56,7 @@ const RelationshipController: React.FC = ({ }) => { //setShowDBML((value) => !value) console.log("hello world ") } + > @@ -62,6 +85,7 @@ const RelationshipController: React.FC = ({ }) => { variant="solid" radius="sm" color="primary" + onPressEnd={onOpen} startContent={ } @@ -75,26 +99,44 @@ const RelationshipController: React.FC = ({ }) => { - {items.map(item => ( + {relationships.map((relationship: RelationshipType) => ( + } > - + ))}
+ + +
) } diff --git a/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-item.tsx b/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-item.tsx index 2a52e18..3e31c1b 100644 --- a/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-item.tsx +++ b/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-item.tsx @@ -63,7 +63,6 @@ const FieldItem: React.FC = ({ field }) => { } as FieldType); } - const toggleNullable = (nullable: boolean) => { editField({ id: field.id, diff --git a/src/pages/database/db-controller/tables-controller/table-accordion-item/table-accordion-body.tsx b/src/pages/database/db-controller/tables-controller/table-accordion-item/table-accordion-body.tsx index c59e76c..bbd037e 100644 --- a/src/pages/database/db-controller/tables-controller/table-accordion-item/table-accordion-body.tsx +++ b/src/pages/database/db-controller/tables-controller/table-accordion-item/table-accordion-body.tsx @@ -50,6 +50,7 @@ const TableAccordionBody: React.FC = ({ table }) => { return (
( @@ -92,7 +93,7 @@ const TableAccordionBody: React.FC = ({ table }) => {
)} classNames={{ - trigger: "hover:bg-default" + trigger: "hover:bg-default h-6" }} subtitle={
@@ -114,7 +115,7 @@ const TableAccordionBody: React.FC = ({ table }) => {
)} classNames={{ - trigger: "hover:bg-default" + trigger: "hover:bg-default h-6" }} subtitle={
diff --git a/src/pages/database/db-controller/tables-controller/tables-controller.tsx b/src/pages/database/db-controller/tables-controller/tables-controller.tsx index dd4fdd1..9ede7e0 100644 --- a/src/pages/database/db-controller/tables-controller/tables-controller.tsx +++ b/src/pages/database/db-controller/tables-controller/tables-controller.tsx @@ -101,13 +101,15 @@ const TablesController: React.FC = ({ }) => { variant="splitted" selectedKeys={selectedTable} onSelectionChange={setSelectedTable as any} + isCompact + > {tables.map((table: TableType) => ( Promise, editTable: (table: TableInsertType) => Promise, deleteTable: (id: string) => Promise, - + // field operations createField: (field: FieldInsertType) => Promise, editField: (field: FieldInsertType) => Promise, deleteField: (id: string) => Promise, - orderTableFields: (fields: FieldType[]) => Promise - + orderTableFields: (fields: FieldType[]) => Promise , + // relationship operations + createRelationship : ( relationship : RelationshipInsertType) => Promise , + editRelationship : ( relationship : RelationshipInsertType) => Promise , + deleteRelationship : ( id : string) => Promise , } @@ -28,12 +33,3 @@ export interface DatabaseContextType { export default createContext({} as DatabaseContextType); -/* - - return Promise.all( - fields.map((field : FieldType , index : number) => editField({id : field.id , sequence : index} as FieldType)) - ) - - - -*/ \ No newline at end of file diff --git a/src/providers/database-provider/database-provider.tsx b/src/providers/database-provider/database-provider.tsx index 2cf6fb1..7ff765a 100644 --- a/src/providers/database-provider/database-provider.tsx +++ b/src/providers/database-provider/database-provider.tsx @@ -8,6 +8,7 @@ import { toCompilableQuery } from "@powersync/drizzle-driver"; import { asc, desc, eq, sql } from "drizzle-orm"; import { QueryResult } from "@powersync/web"; import { FieldInsertType, fields, FieldType } from "@/lib/schemas/field-schema"; +import { RelationshipInsertType, relationships, RelationshipType } from "@/lib/schemas/relationship-schema"; interface Props { children: React.ReactNode } @@ -26,6 +27,18 @@ const DatabaseProvider: React.FC = ({ children }) => { }) )); + + const { data: relationshipsList } = useQuery(toCompilableQuery( + db.query.relationships.findMany({ + with: { + sourceTable: true, + targetTable: true, + sourceField: true, + targetField: true, + }, + orderBy: desc(tables.createdAt) + }) + )); const { data: data_types } = useQuery(toCompilableQuery( db.query.data_types.findMany() )); @@ -52,24 +65,32 @@ const DatabaseProvider: React.FC = ({ children }) => { } const orderTableFields = async (fields: FieldType[]): Promise => { - const caseStatements = fields .map((field: FieldType, index: number) => `WHEN '${field.id}' THEN ${index}`) .join('\n '); - const ids = fields.map(u => `'${u.id}'`).join(',\n '); - const sql = ` - UPDATE fields - SET sequence = CASE id - ${caseStatements} - END - WHERE id IN ( - ${ids} - );`; - + UPDATE fields + SET sequence = CASE id + ${caseStatements} + END + WHERE id IN ( + ${ids} + );`; return await powerSyncDb.execute(sql) } + + const createRelationship = async (relationship: RelationshipInsertType): Promise => { + return await db.insert(relationships).values(relationship); + } + const editRelationship = async (relationship: RelationshipInsertType): Promise => { + return await db.update(relationships).set(relationship).where(eq(relationships.id, relationship.id)) + } + const deleteRelationship = async (id: string): Promise => { + return await db.delete(relationships).where(eq(relationships.id, id)); + } + + return ( = ({ children }) => { editField, deleteField, orderTableFields, + + createRelationship, + editRelationship, + deleteRelationship, + tables: tablesList as TableType[], + relationships : relationshipsList as RelationshipType[] , data_types }}> {children} diff --git a/src/styles/globals.css b/src/styles/globals.css index 0978d8a..b7d6c34 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -48,4 +48,9 @@ div[data-slot="mainWrapper"] button[data-slot="trigger"] { div[data-slot="content"] hr[role="separator"] { display: none ; +} + + +button[data-slot="trogger"] { + background-color: red !important; } \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index 4ae8dcb..9d43fea 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -6,7 +6,7 @@ export default { "./index.html", "./src/**/*.{js,ts,jsx,tsx}", "./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}", - "./node_modules/@heroui/theme/dist/components/(button|code|dropdown|input|kbd|link|navbar|snippet|toggle|popover|ripple|spinner|menu|divider|form).js", + "./node_modules/@heroui/theme/dist/components/(button|code|dropdown|input|kbd|link|navbar|snippet|toggle|popover|ripple|spinner|menu|divider|form|modal).js", ], darkMode: "class",