commit d9905351bd47278ee097b2a39dbce35dd7d5fc0a Author: KarimTamani Date: Sun May 4 19:17:04 2025 +0100 Init Commit diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..af6ab76 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,20 @@ +.now/* +*.css +.changeset +dist +esm/* +public/* +tests/* +scripts/* +*.config.js +.DS_Store +node_modules +coverage +.next +build +!.commitlintrc.cjs +!.lintstagedrc.cjs +!jest.config.js +!plopfile.js +!react-shim.js +!tsup.config.ts \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..d3067d4 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,92 @@ +{ + "$schema": "https://json.schemastore.org/eslintrc.json", + "env": { + "browser": false, + "es2021": true, + "node": true + }, + "extends": [ + "plugin:react/recommended", + "plugin:prettier/recommended", + "plugin:react-hooks/recommended", + "plugin:jsx-a11y/recommended" + ], + "plugins": ["react", "unused-imports", "import", "@typescript-eslint", "jsx-a11y", "prettier"], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 12, + "sourceType": "module" + }, + "settings": { + "react": { + "version": "detect" + } + }, + "rules": { + "no-console": "warn", + "react/prop-types": "off", + "react/jsx-uses-react": "off", + "react/react-in-jsx-scope": "off", + "react-hooks/exhaustive-deps": "off", + "jsx-a11y/click-events-have-key-events": "warn", + "jsx-a11y/interactive-supports-focus": "warn", + "prettier/prettier": "warn", + "no-unused-vars": "off", + "unused-imports/no-unused-vars": "off", + "unused-imports/no-unused-imports": "warn", + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "args": "after-used", + "ignoreRestSiblings": false, + "argsIgnorePattern": "^_.*?$" + } + ], + "import/order": [ + "warn", + { + "groups": [ + "type", + "builtin", + "object", + "external", + "internal", + "parent", + "sibling", + "index" + ], + "pathGroups": [ + { + "pattern": "~/**", + "group": "external", + "position": "after" + } + ], + "newlines-between": "always" + } + ], + "react/self-closing-comp": "warn", + "react/jsx-sort-props": [ + "warn", + { + "callbacksLast": true, + "shorthandFirst": true, + "noSortAlphabetically": false, + "reservedFirst": true + } + ], + "padding-line-between-statements": [ + "warn", + {"blankLine": "always", "prev": "*", "next": "return"}, + {"blankLine": "always", "prev": ["const", "let", "var"], "next": "*"}, + { + "blankLine": "any", + "prev": ["const", "let", "var"], + "next": ["const", "let", "var"] + } + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d27651 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + + +pnpm-lock.yaml +yarn.lock +package-lock.json +bun.lockb \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..1e54ebc --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=true \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d3006aa --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Next UI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..30476e8 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# Vite & HeroUI Template + +This is a template for creating applications using Vite and HeroUI (v2). + +[Try it on CodeSandbox](https://githubbox.com/frontio-ai/vite-template) + +## Technologies Used + +- [Vite](https://vitejs.dev/guide/) +- [HeroUI](https://heroui.com) +- [Tailwind CSS](https://tailwindcss.com) +- [Tailwind Variants](https://tailwind-variants.org) +- [TypeScript](https://www.typescriptlang.org) +- [Framer Motion](https://www.framer.com/motion) + +## How to Use + +To clone the project, run the following command: + +```bash +git clone https://github.com/frontio-ai/vite-template.git +``` + +### Install dependencies + +You can use one of them `npm`, `yarn`, `pnpm`, `bun`, Example using `npm`: + +```bash +npm install +``` + +### Run the development server + +```bash +npm run dev +``` + +### Setup pnpm (optional) + +If you are using `pnpm`, you need to add the following code to your `.npmrc` file: + +```bash +public-hoist-pattern[]=*@heroui/* +``` + +After modifying the `.npmrc` file, you need to run `pnpm install` again to ensure that the dependencies are installed correctly. + +## License + +Licensed under the [MIT license](https://github.com/frontio-ai/vite-template/blob/main/LICENSE). diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..e38d32a Binary files /dev/null and b/favicon.ico differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..102806f --- /dev/null +++ b/index.html @@ -0,0 +1,28 @@ + + + + + + + Vite + HeroUI + + + + + + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..3f84255 --- /dev/null +++ b/package.json @@ -0,0 +1,54 @@ +{ + "name": "vite-template", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint -c .eslintrc.json ./src/**/**/*.{ts,tsx} --fix", + "preview": "vite preview" + }, + "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@heroui/react": "^2.7.6", + "@radix-ui/react-tooltip": "^1.2.3", + "@react-aria/visually-hidden": "3.8.21", + "@react-types/shared": "3.28.0", + "@xyflow/react": "^12.6.0", + "framer-motion": "11.15.0", + "i18next-browser-languagedetector": "^8.0.5", + "lucide-react": "^0.501.0", + "react": "18.3.1", + "react-dom": "18.3.1", + "react-i18next": "^15.5.1", + "react-router-dom": "6.23.0", + "tailwind-merge": "^3.2.0", + "tailwind-variants": "0.3.0", + "tailwindcss": "3.4.16" + }, + "devDependencies": { + "@types/node": "20.5.7", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", + "@typescript-eslint/eslint-plugin": "8.11.0", + "@typescript-eslint/parser": "8.11.0", + "@vitejs/plugin-react": "^4.2.1", + "autoprefixer": "10.4.19", + "eslint": "^8.57.0", + "eslint-config-prettier": "9.1.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "5.2.1", + "eslint-plugin-react": "^7.23.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-unused-imports": "4.1.4", + "postcss": "8.4.38", + "prettier": "3.3.3", + "typescript": "5.6.3", + "vite": "^5.2.0", + "vite-tsconfig-paths": "^4.3.2" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..2c02c54 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, + }; \ No newline at end of file diff --git a/public/stackrender.png b/public/stackrender.png new file mode 100644 index 0000000..4f350b7 Binary files /dev/null and b/public/stackrender.png differ diff --git a/public/stackrenderlogo.png b/public/stackrenderlogo.png new file mode 100644 index 0000000..f0c86b9 Binary files /dev/null and b/public/stackrenderlogo.png differ diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..f0ce546 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,18 @@ +import { Route, Routes } from "react-router-dom"; +import "@/styles/globals.css" +import { TooltipProvider } from "./components/tooltip/tooltip"; +import { ReactFlowProvider } from "@xyflow/react"; +import useAppRoutes from "./routes/app-route"; + +function App() { + const appRoutes = useAppRoutes() ; + return ( + + + {appRoutes} + + + ); +} + +export default App; diff --git a/src/components/color-picker/color-picker.tsx b/src/components/color-picker/color-picker.tsx new file mode 100644 index 0000000..dc2d981 --- /dev/null +++ b/src/components/color-picker/color-picker.tsx @@ -0,0 +1,56 @@ +import { colorOptions } from "@/lib/colors"; +import { Button, Popover, PopoverContent, PopoverTrigger } from "@heroui/react"; +import { useCallback, useState } from "react"; + +interface ColorPickerProps { + defaultColor?: string; + onChange?: (color: string) => void +} + + + +const ColorPicker: React.FC = ({ defaultColor, onChange }) => { + const [color , setColor] = useState( defaultColor as string ) ; + const [isOpen, setIsOpen] = useState(false); + + const onSelect = useCallback((color: string) => { + setIsOpen(false); + onChange && onChange(color) ; + setColor ( color) + }, [onChange]) + + return ( + setIsOpen(open)}> + + + + +
+ {colorOptions.map(color => ( +
onSelect(color)} + > + +
+ ))} +
+
+
+ ) +} + + +export default ColorPicker; \ No newline at end of file diff --git a/src/components/menu/menu-dropdown.tsx b/src/components/menu/menu-dropdown.tsx new file mode 100644 index 0000000..1649459 --- /dev/null +++ b/src/components/menu/menu-dropdown.tsx @@ -0,0 +1,90 @@ +import { Dropdown, DropdownMenu as HeroDropdownMenu, DropdownItem, DropdownTrigger, Button, useDisclosure, Popover, PopoverTrigger, PopoverContent, Divider, DropdownSection } from "@heroui/react"; +import { useMemo, useState } from "react"; +import SubmenuDropdown from "./submenu-dropdown"; + + + + + + + + + + +export interface MenuDropdownProps { + title?: string; + children?: MenuDropdownProps[]; + + theme?: "default" | "danger", + isDisabled?: boolean, + divide?: boolean, + shortcut?: string, + isOpen?: boolean +} + + + +const DropdownMenu: React.FC = ({ title, children, isOpen }) => { + + + + const disabledChilds: string[] = useMemo(() => { + return children ? children?.filter((child: MenuDropdownProps) => child.isDisabled && child.title).map((child: MenuDropdownProps) => child.title as string) : [] + }, [children]); + + + return + { + title && + + + + } + + + { + children ? children?.map((child: MenuDropdownProps) => ( + + + { + !child.children ? +
+ {child.title} + { + child.divide && +
+ } +
+ : +
+ + { + child.divide && +
+ } +
+ + } +
+ + )) : [] + } + +
+
+ + + +} + + +export default DropdownMenu; \ No newline at end of file diff --git a/src/components/menu/menu.tsx b/src/components/menu/menu.tsx new file mode 100644 index 0000000..6196526 --- /dev/null +++ b/src/components/menu/menu.tsx @@ -0,0 +1,117 @@ + +import DropdownMenu, { MenuDropdownProps } from "./menu-dropdown"; + + + + + + +const menu: MenuDropdownProps[] = [ + { + title: "File", + + children: [{ + title: "New" + }, + { + title: "Open", + shortcut: "Ctnl + O" + }, { + title: "Save", + shortcut: "Ctnl + S", + divide: true + }, { + title: "Import", + divide: true, + children: [{ + title: ".json", + }, { + title: ".dbml" + }, { + title: "MySql" + }, { + title: "Postgresql" + }] + }, { + title: "Export SQL", + children: [{ + title: "Generic", + }, { + title: "MySql" + }, { + title: "Postgresql" + }] + }, { + title: "Export ORM Models", + divide: true + }, { + title: "Delete Project", + theme: "danger" + }] + }, { + title: "Edit", + + children: [{ + title: "Undo", + isDisabled: true, + }, + { + title: "Redo", + + isDisabled: true, + }, { + title: "Clear", + + }] + } , { + title : "View" , + children : [{ + title : "Hide Controller" , + shortcut : "Ctnl + B" , + divide : true + } , { + title : "Zoom on scroll" , + divide : true , + children : [{ + title : "On" + } , { + title : "Off" + }] , + + } , { + title : "Theme" , + + children : [{ + title : "Light" + } , { + title : "Dark" + }] + }] + } , { + title : "Help" , + children : [{ + title : "Show Docs" , + } , { + title : "Join Discord" + }] + } +] + + + + + +interface MenuProps { + +} + + + +const Menu: React.FC = ({ }) => { + return menu.map(menuItem => ( + + )) +} + + +export default Menu; \ No newline at end of file diff --git a/src/components/menu/submenu-dropdown.tsx b/src/components/menu/submenu-dropdown.tsx new file mode 100644 index 0000000..ed93ecf --- /dev/null +++ b/src/components/menu/submenu-dropdown.tsx @@ -0,0 +1,35 @@ +import { Button, Popover, PopoverContent, PopoverTrigger, useDisclosure } from "@heroui/react"; +import { MenuDropdownProps } from "./menu-dropdown"; +import { ChevronRight } from "lucide-react"; + +import DropdownMenu from "./menu-dropdown"; + + + +const SubmenuDropdown: React.FC = ({ title, children }) => { + const { isOpen, onOpen, onClose } = useDisclosure(); + + return ( +
+ + + + + + + + + +
+ ) +}; + +export default SubmenuDropdown; \ No newline at end of file diff --git a/src/components/navbar/navbar.tsx b/src/components/navbar/navbar.tsx new file mode 100644 index 0000000..ac266c9 --- /dev/null +++ b/src/components/navbar/navbar.tsx @@ -0,0 +1,31 @@ +import { NavbarBrand, NavbarContent, Navbar as NavbarContainer, Image, Dropdown, DropdownTrigger, Button, DropdownMenu, DropdownItem } from "@heroui/react"; +import Menu from "../menu/menu"; + +interface Props { + +} + + +const Navbar: React.FC = ({ }) => { + return ( + + + + ) +} + + +export default Navbar; \ No newline at end of file diff --git a/src/components/sidebar/sidebar-item.tsx b/src/components/sidebar/sidebar-item.tsx new file mode 100644 index 0000000..939643d --- /dev/null +++ b/src/components/sidebar/sidebar-item.tsx @@ -0,0 +1,44 @@ +import { Link } from "react-router-dom"; +import { Tooltip, TooltipContent, TooltipTrigger } from "../tooltip/tooltip"; +import { Divider } from "@heroui/react"; +import React from "react"; + + + +export interface SidebarItemProps { + type?: "item" | "divider" + title?: string; + icon?: React.ReactNode; + href?: string; + isActive? : boolean +} + + +const SidebarItem: React.FC = ({ title, icon, href , type = "item" , isActive}) => { + + if (type == "item") + + + return ( + + + + {icon} + + + + {title} + + + + ) + else { + return + } +} + + +export default SidebarItem \ No newline at end of file diff --git a/src/components/sidebar/sidebar.tsx b/src/components/sidebar/sidebar.tsx new file mode 100644 index 0000000..a58b98c --- /dev/null +++ b/src/components/sidebar/sidebar.tsx @@ -0,0 +1,116 @@ +import { BookOpen, EarthLock, GitCommitHorizontal, Github, Settings, Sparkles, Table, TableProperties, Waypoints, Workflow, X, Zap } from "lucide-react"; +import SidebarItem, { SidebarItemProps } from "./sidebar-item"; +import { useMemo } from "react"; +import { useLocation } from "react-router-dom"; +import { useTranslation } from 'react-i18next'; + +interface SidebarProps { + children?: React.ReactNode +} + +const sidebarItemClass : string = "text-gray-700 size-4 data-[active=true]:text-primary" ; + +const Sidebar: React.FC = ({ }) => { + const { t , i18n } = useTranslation() ; + console.log ( i18n.language) + + console.log () + const location = useLocation() ; + + const sidebarItems: SidebarItemProps[] = useMemo(() => [ + { + title: t("sidebar.tables"), + icon: , + href: "/database/tables", + isActive : location.pathname.endsWith("/database/tables") + }, + { + title: t("sidebar.relationships"), + icon: , + href: "/database/relationships", + isActive : location.pathname.endsWith("/database/relationships") + }, + + { + title: "AI", + icon: , + href: "/database/bot", + isActive : location.pathname.endsWith("/database/bot") + }, + { + type: "divider" + }, + { + title: "API", + icon: , + href: "/database/ai-bot", + }, + { + title: "Authentication", + icon: , + href: "/database/ai-bot", + }, + { + title: "Controllers", + icon: , + href: "/database/ai-bot", + }, + { + type: "divider" + }, + { + title: "Git", + icon: , + href: "/database/ai-bot", + }, + { + type: "divider" + }, + { + title: "Project Setting", + icon: , + href: "/database/ai-bot", + }, + ], [location]) + + const bottomSidebarItems: SidebarItemProps[] = useMemo(() => [ + { + title: "Github", + icon: , + href: "/database/ai-bot", + }, + { + title: "Docs", + icon: , + href: "/database/ai-bot", + }, + ], []) ; + + return ( + + + ) +} + + + +export default Sidebar; \ No newline at end of file diff --git a/src/components/tooltip/tooltip.tsx b/src/components/tooltip/tooltip.tsx new file mode 100644 index 0000000..bd0e84b --- /dev/null +++ b/src/components/tooltip/tooltip.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import * as TooltipPrimitive from '@radix-ui/react-tooltip'; +import { cn } from '@heroui/react'; + + + +const TooltipProvider = TooltipPrimitive.Provider; + +const Tooltip = TooltipPrimitive.Root; + +const TooltipTrigger = TooltipPrimitive.Trigger; + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)); +TooltipContent.displayName = TooltipPrimitive.Content.displayName; + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; diff --git a/src/i18/index.ts b/src/i18/index.ts new file mode 100644 index 0000000..772a345 --- /dev/null +++ b/src/i18/index.ts @@ -0,0 +1,40 @@ +import { en, enLanguage } from "./languages/en"; +import { fr, frLanguage } from "./languages/fr"; +import { Language } from "./types"; + +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; +import { ar, arLanguage } from "./languages/ar"; + + + +export const languages: Language[] = [ + enLanguage, + frLanguage , + arLanguage +]; + + +const resources = { + en, + fr, + ar +}; + + + +i18n.use(LanguageDetector) + .use(initReactI18next) + .init({ + resources , + lng: 'en', // 👈 set default language to French + + interpolation: { + escapeValue: false, + }, + fallbackLng: enLanguage.code, + debug: false, + }); + +export { i18n }; \ No newline at end of file diff --git a/src/i18/languages/ar.ts b/src/i18/languages/ar.ts new file mode 100644 index 0000000..23ce530 --- /dev/null +++ b/src/i18/languages/ar.ts @@ -0,0 +1,51 @@ +import { Language } from "../types"; + + +export const ar = { + + translation: { + sidebar: { + tables: "الجداول", + relationships: "العلاقات" + }, + db_controller: { + filter : "تصفية" , + add_table : "إضافة جدول" , + add_field : "إضافة حقل" , + add_index : "إضافة فهرس" , + add_relationship : "إضافة علاقة" , + show_code : "عرض الكود" , + fields : "الحقول" , + indexes : "الفهارس" , + note : "ملاحظة" , + name : "الاسم" , + type : "النوع" , + primary_key : "المفتاح الأساسي" , + nullable : "يسمح بالقيمة الفارغة" , + select_fields : "اختر الحقول" , + unique : "فريد" , + table_note : "ملاحظة الجدول" , + collapse : "إخفاء الكل" , + primary_table : "الجدول الأساسي" , + referenced_table : "الجدول المشار إليه" , + cardinality :{ + name : "العلاقة" , + one_to_one : "واحد إلى واحد" , + one_to_many : "واحد إلى متعدد" , + many_to_one : "متعدد إلى واحد" , + many_to_many : "متعدد إلى متعدد" , + }, + delete : "حذف" , + } , + table: { + double_click: "انقر مرتين للتعديل" + }, + } + +} + +export const arLanguage: Language = { + name: 'Arabic', + nativeName: 'العربية', + code: 'ar', +}; diff --git a/src/i18/languages/en.ts b/src/i18/languages/en.ts new file mode 100644 index 0000000..bbc4b3c --- /dev/null +++ b/src/i18/languages/en.ts @@ -0,0 +1,52 @@ +import { Language } from "../types"; + + +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" , + + }, + table : { + double_click : "Double click to edit" + } + } + +} + +export const enLanguage : Language = { + name : "English" , + nativeName: 'English', + code: 'en', +} \ No newline at end of file diff --git a/src/i18/languages/fr.ts b/src/i18/languages/fr.ts new file mode 100644 index 0000000..dd88493 --- /dev/null +++ b/src/i18/languages/fr.ts @@ -0,0 +1,51 @@ +import { Language } from "../types"; + + +export const fr = { + translation: { + sidebar: { + tables: "Tableaux", + relationships: "Relations" + } , + db_controller : { + filter : "Filtrer" , + add_table : "Ajouter une table" , + add_field : "Ajouter un champ" , + add_index : "Ajouter un index" , + add_relationship : "Ajouter une relation" , + show_code : "Afficher le code" , + fields : "Champs" , + indexes : "Index" , + note : "Note" , + name : "Nom" , + type : "Type" , + primary_key : "Clé primaire" , + nullable : "Nullable" , + select_fields : "Sélectionner des champs" , + unique : "Unique" , + table_note : "Note de la table" , + collapse : "Tout réduire" , + primary_table : "Table primaire" , + referenced_table : "Table référencée" , + cardinality :{ + name : "Cardinalité" , + one_to_one : "Un à un" , + one_to_many : "Un à plusieurs" , + many_to_one : "Plusieurs à un" , + many_to_many : "Plusieurs à plusieurs" , + }, + delete : "Supprimer" , + } , + table: { + double_click: "Double-cliquez pour modifier" + }, + + } + +} + +export const frLanguage: Language = { + name: "French", + nativeName: 'Français', + code: 'fr', +} \ No newline at end of file diff --git a/src/i18/types.ts b/src/i18/types.ts new file mode 100644 index 0000000..2e03647 --- /dev/null +++ b/src/i18/types.ts @@ -0,0 +1,7 @@ + + +export type Language = { + name: string; + nativeName: string; + code: string; +}; diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..364f74e --- /dev/null +++ b/src/index.css @@ -0,0 +1,6 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + + + \ No newline at end of file diff --git a/src/lib/colors.ts b/src/lib/colors.ts new file mode 100644 index 0000000..fa0cda5 --- /dev/null +++ b/src/lib/colors.ts @@ -0,0 +1,21 @@ + +export const colorOptions = [ + "#fd7f6f", // Warm coral red + "#7eb0d5", // Soft sky blue + "#b2e061", // Fresh lime green + "#bd7ebe", // Soft lavender pink + "#ffb55a", // Vibrant orange + "#ffee65", // Warm yellow + "#beb9db", // Light purple grey + "#fdcce5", // Light blush pink + "#8bd3c7", // Minty teal + "#f4a1a1", // Soft peachy red + "#9bd78c", // Fresh pastel green + "#d1a9e0" // Light lilac purple + + +]; + +export const randomColor = () => { + return colorOptions[Math.floor(Math.random() * colorOptions.length)]; +}; \ No newline at end of file diff --git a/src/lib/data/data-types.ts b/src/lib/data/data-types.ts new file mode 100644 index 0000000..83967e8 --- /dev/null +++ b/src/lib/data/data-types.ts @@ -0,0 +1,4 @@ +export interface DataType { + id: string; + name: string; +} diff --git a/src/lib/interfaces/field.ts b/src/lib/interfaces/field.ts new file mode 100644 index 0000000..b61d1d8 --- /dev/null +++ b/src/lib/interfaces/field.ts @@ -0,0 +1,14 @@ +import { DataType } from "../data/data-types"; + +export interface Field { + id: string; + name: string; + type: DataType; + primaryKey: boolean; + unique: boolean; + nullable: boolean; + createdAt: number; + default?: string; + + +} \ No newline at end of file diff --git a/src/lib/interfaces/relationship.ts b/src/lib/interfaces/relationship.ts new file mode 100644 index 0000000..f229e61 --- /dev/null +++ b/src/lib/interfaces/relationship.ts @@ -0,0 +1,22 @@ + +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/interfaces/table.ts b/src/lib/interfaces/table.ts new file mode 100644 index 0000000..db2075c --- /dev/null +++ b/src/lib/interfaces/table.ts @@ -0,0 +1,14 @@ +import { Field } from "./field"; + +export interface Table { + id: string; + name: string; + x: number; + y: number; + fields: Field[]; + width?: number; + order?: number; + color: string; + createdAt: number; + comments?: string; +} \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..be89142 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,18 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import { BrowserRouter } from "react-router-dom"; + +import App from "./App.tsx"; +import { Provider } from "./provider.tsx"; +import "./index.css"; +import "./i18/index.ts" ; + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + + + + + , +); diff --git a/src/pages/dashboard/dashboard-page.tsx b/src/pages/dashboard/dashboard-page.tsx new file mode 100644 index 0000000..503a552 --- /dev/null +++ b/src/pages/dashboard/dashboard-page.tsx @@ -0,0 +1,27 @@ + +import Sidebar from "@/components/sidebar/sidebar"; +import DatabasePage from "../database/database-page"; +import Navbar from "@/components/navbar/navbar"; +import { Outlet } from "react-router-dom"; + + + +interface Props { + +} + +const DashboardPage: React.FC = ({ }) => { + return ( +
+ +
+ + +
+
+ ) +} + + + +export default DashboardPage; \ No newline at end of file diff --git a/src/pages/database/database-page.tsx b/src/pages/database/database-page.tsx new file mode 100644 index 0000000..1b69506 --- /dev/null +++ b/src/pages/database/database-page.tsx @@ -0,0 +1,211 @@ +import { Table as TableType } from "@/lib/interfaces/table"; +import { addEdge, applyEdgeChanges, applyNodeChanges, Background, Controls, MiniMap, ReactFlow, useEdgesState, useNodesState, useReactFlow } from "@xyflow/react"; +import { useCallback, useMemo } from "react"; +import Table from "./table/table"; + +import '@xyflow/react/dist/style.css'; +import { Relationship } from "./table/relationship"; +import DBController from "./db-controller/db-controller"; + +interface DatabaseProps { + initialTables?: TableType +} + + +const initialNodes = [ + { + id: '1', type: "table", position: { x: 500, y: 500 }, data: { + + table: { + id: "1", + name: "users", + fields: [ + { + id: 'e5n46eojbyxpg65pb1sesysi2', + name: 'id', + type: { + id: 'integer', + name: 'integer', + }, + primaryKey: true, + unique: true, + nullable: false, + createdAt: Date.now(), + }, + { + id: '7nbmg2bt6dzltr8rfouxctdlc', + name: 'reservation_id', + type: { + id: 'integer', + name: 'integer', + }, + primaryKey: false, + unique: false, + nullable: true, + createdAt: Date.now(), + }, + { + id: 'f9kcpflp010xe5ad3rmexla63', + name: 'rating', + type: { + id: 'integer', + name: 'integer', + }, + primaryKey: false, + unique: false, + nullable: false, + createdAt: Date.now(), + }, + { + id: '3znekob0pqusyvdoq14nhlvgo', + name: 'comment', + type: { + id: 'character_varying', + name: 'character varying', + }, + primaryKey: false, + unique: false, + nullable: true, + createdAt: Date.now(), + }, + ], + } + }, + style: { width: 224 } + }, + + { + id: '2', type: "table", position: { x: 200, y: 500 }, data: { + + table: { + id: "1", + name: "products", + fields: [ + { + id: 'e5n46eojbyxpg65pb1sesysi2', + name: 'id', + type: { + id: 'integer', + name: 'integer', + }, + primaryKey: true, + unique: true, + nullable: false, + createdAt: Date.now(), + }, + { + id: '7nbmg2bt6dzltr8rfouxctdlc', + name: 'user_id', + type: { + id: 'integer', + name: 'integer', + }, + primaryKey: false, + unique: false, + nullable: true, + createdAt: Date.now(), + }, + { + id: 'f9kcpflp010xe5ad3rmexla63', + name: 'rating', + type: { + id: 'integer', + name: 'integer', + }, + primaryKey: false, + unique: false, + nullable: false, + createdAt: Date.now(), + }, + { + id: 'f9kcpflp010xe5ad3rmexla63', + name: 'rating', + type: { + id: 'integer', + name: 'integer', + }, + primaryKey: false, + unique: false, + nullable: false, + createdAt: Date.now(), + }, + { + id: 'f9kcpflp010xe5ad3rmexla63', + name: 'rating', + type: { + id: 'integer', + name: 'integer', + }, + primaryKey: false, + unique: false, + nullable: false, + createdAt: Date.now(), + }, + { + id: '3znekob0pqusyvdoq14nhlvgo', + name: 'comment', + type: { + id: 'character_varying', + name: 'character varying', + }, + primaryKey: false, + unique: false, + nullable: true, + createdAt: Date.now(), + }, + ], + } + }, + style: { width: 224 } + }, +]; + +const initialEdges: any[] = []; + + +const edgeTypes = { + 'relationship-edge': Relationship, +}; + +const DatabasePage: React.FC = ({ initialTables }) => { + + const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + + + const nodeTypes = useMemo(() => ({ table: Table }), []); + const onConnect = useCallback((params: any) => setEdges((eds) => addEdge(params, eds)), [setEdges]); + + + return ( +
+ + + + + + +
+ ) +} + + +export default DatabasePage; \ No newline at end of file diff --git a/src/pages/database/db-controller/db-controller.tsx b/src/pages/database/db-controller/db-controller.tsx new file mode 100644 index 0000000..bb1fea8 --- /dev/null +++ b/src/pages/database/db-controller/db-controller.tsx @@ -0,0 +1,20 @@ +import { Outlet } from "react-router-dom" + + + +interface Props { + +} + + +const DBController : React.FC = ({ }) => { + return( +
+ +
+ ) +} + +export default DBController + + 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 new file mode 100644 index 0000000..5800a35 --- /dev/null +++ b/src/pages/database/db-controller/relationship-controller/relationship-accordion-item/relationship-accordion-body.tsx @@ -0,0 +1,105 @@ + +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip"; +import { Button, Select, SelectItem } from "@heroui/react"; +import { ChevronsLeftRightEllipsis, FileMinus2, FileOutput, SquareArrowLeft, SquareArrowRight, Trash2 } from "lucide-react"; +import { useTranslation } from "react-i18next"; + + + +interface Props { + +} + + +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 {t } = useTranslation() ; + return ( +
+
+
+ + + + + users(id) + + + + users(id) + + +
+ +
+ + + + + + products(user_id) + + + + products(user_id) + + +
+
+
+ + +
+
+ +
+
+ ) +} + + +export default RelationshipAccordionBody; \ No newline at end of file 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 new file mode 100644 index 0000000..dae570b --- /dev/null +++ b/src/pages/database/db-controller/relationship-controller/relationship-accordion-item/relationship-accordion-header.tsx @@ -0,0 +1,90 @@ +import { randomColor } from "@/lib/colors"; +import { Button, cn, Input } from "@heroui/react"; +import { Check, ChevronRight, EllipsisVertical, Focus, Pencil } from "lucide-react"; +import { useState } from "react"; + + + +interface RelationshipAccordionHeaderProps { + isOpen?: boolean; +} + + +const RelationshipAccordionHeader: React.FC = ({ isOpen }) => { + + const [editMode, setEditMode] = useState(false); + return ( +
+
+ +
+ + + { + editMode && <> + setEditMode(false)} + 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 " + /> + + + } + { + + !editMode && <> + + +
+ + + +
+ + + } +
+ + ) +} + + +export default RelationshipAccordionHeader; \ No newline at end of file diff --git a/src/pages/database/db-controller/relationship-controller/relationship-controller.tsx b/src/pages/database/db-controller/relationship-controller/relationship-controller.tsx new file mode 100644 index 0000000..627aa71 --- /dev/null +++ b/src/pages/database/db-controller/relationship-controller/relationship-controller.tsx @@ -0,0 +1,104 @@ +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip" +import { Accordion, AccordionItem, Button, Input } from "@heroui/react" +import { Code, List, ListCollapse, Table, Workflow } from "lucide-react" +import { 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"; + + + +interface Props { + +} + + +const RelationshipController: React.FC = ({ }) => { + const [items, setItems] = useState(["Item 1", "Item 2", "Item 3", "Item 4"]); + + + const { t } = useTranslation(); + + return ( +
+
+
+ + + + + + + + {t("db_controller.collapse")} + + +
+
+ setFilterText(e.target.value)} + /> +
+ +
+
+ + + {items.map(item => ( + + } + > + + + ))} + +
+
+ ) +} + +export default RelationshipController + + 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 new file mode 100644 index 0000000..209813c --- /dev/null +++ b/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-item.tsx @@ -0,0 +1,116 @@ +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip"; +import { useSortable } from "@dnd-kit/sortable"; +import { Autocomplete, AutocompleteItem, Button, Input } from "@heroui/react"; +import { EllipsisVertical, Grip, GripVertical, KeyRound } from "lucide-react"; +import { useTranslation } from "react-i18next"; +import { CSS } from "@dnd-kit/utilities"; + +interface Props { +id : string +} +export const animals = [ + { label: "Cat", key: "cat", description: "The second most popular pet in the world" }, + { label: "Dog", key: "dog", description: "The most popular pet in the world" }, + { label: "Elephant", key: "elephant", description: "The largest land animal" }, + { label: "Lion", key: "lion", description: "The king of the jungle" }, + { label: "Tiger", key: "tiger", description: "The largest cat species" }, + { label: "Giraffe", key: "giraffe", description: "The tallest land animal" }, + { + label: "Dolphin", + key: "dolphin", + description: "A widely distributed and diverse group of aquatic mammals", + }, + { label: "Penguin", key: "penguin", description: "A group of aquatic flightless birds" }, + { label: "Zebra", key: "zebra", description: "A several species of African equids" }, + { + label: "Shark", + key: "shark", + description: "A group of elasmobranch fish characterized by a cartilaginous skeleton", + }, + { + label: "Whale", + key: "whale", + description: "Diverse group of fully aquatic placental marine mammals", + }, + { label: "Otter", key: "otter", description: "A carnivorous mammal in the subfamily Lutrinae" }, + { label: "Crocodile", key: "crocodile", description: "A large semiaquatic reptile" }, +]; + + +const FieldItem: React.FC = ({id }) => { + const { t } = useTranslation(); + + const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id }); + + const style = { + transform: CSS.Transform.toString(transform), + + + }; + + return ( +
+
+ +
+ + + {(item) => {item.label}} + +
+ + + + + + {t("db_controller.nullable")}? + + + + + + + + + {t("db_controller.primary_key")} + + + +
+ + + +
+ + ) +} + + + +export default FieldItem; \ No newline at end of file diff --git a/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-list.tsx b/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-list.tsx new file mode 100644 index 0000000..eec8b09 --- /dev/null +++ b/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-list.tsx @@ -0,0 +1,72 @@ +import { Button } from "@heroui/react"; +import { Plus } from "lucide-react"; +import FieldItem from "./field-item"; +import { useTranslation } from "react-i18next"; +import { useState } from "react"; +import { closestCenter, DndContext, PointerSensor, useSensor, useSensors } from "@dnd-kit/core"; +import { arrayMove, SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable"; + +interface Props { + +} + + +const FieldList: React.FC = ({ }) => { + const { t } = useTranslation(); + const [fields, setFields] = useState(["Field 1", "Field 2", "Field 3", "Field 4"]) + + const sensors = useSensors( + useSensor(PointerSensor) + ); + + function handleDragEnd(event: any) { + const { active, over } = event; + + if (active.id !== over?.id) { + setFields((items) => { + const oldIndex = items.indexOf(active.id); + const newIndex = items.indexOf(over.id); + + return arrayMove(items, oldIndex, newIndex); + }); + } + } + + return ( +
+ +
+ + { + fields.map((field: string) => ( + + + )) + } + +
+
+ +
+ ) +} + + +export default FieldList; \ No newline at end of file diff --git a/src/pages/database/db-controller/tables-controller/table-accordion-item/index/index-item.tsx b/src/pages/database/db-controller/tables-controller/table-accordion-item/index/index-item.tsx new file mode 100644 index 0000000..5c113b6 --- /dev/null +++ b/src/pages/database/db-controller/tables-controller/table-accordion-item/index/index-item.tsx @@ -0,0 +1,73 @@ +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip"; +import { Button, Select, SelectItem } from "@heroui/react"; +import { EllipsisVertical } from "lucide-react"; +import { useTranslation } from "react-i18next"; + + + +export const animals = [ + { key: "cat", label: "Cat" }, + { key: "dog", label: "Dog" }, + { key: "elephant", label: "Elephant" }, + { key: "lion", label: "Lion" }, + { key: "tiger", label: "Tiger" }, + { key: "giraffe", label: "Giraffe" }, + { key: "dolphin", label: "Dolphin" }, + { key: "penguin", label: "Penguin" }, + { key: "zebra", label: "Zebra" }, + { key: "shark", label: "Shark" }, + { key: "whale", label: "Whale" }, + { key: "otter", label: "Otter" }, + { key: "crocodile", label: "Crocodile" }, +]; + + +interface Props { + +} + + +const IndexItem: React.FC = ({ }) => { + const { t } = useTranslation(); + return ( +
+ +
+ + + + + + {t("db_controller.unique")}? + + + + +
+ +
+ ) +} + + +export default IndexItem; \ No newline at end of file diff --git a/src/pages/database/db-controller/tables-controller/table-accordion-item/index/indexes-list.tsx b/src/pages/database/db-controller/tables-controller/table-accordion-item/index/indexes-list.tsx new file mode 100644 index 0000000..ca14ff6 --- /dev/null +++ b/src/pages/database/db-controller/tables-controller/table-accordion-item/index/indexes-list.tsx @@ -0,0 +1,36 @@ +import { Button, Select, SelectItem } from "@heroui/react"; +import IndexItem from "./index-item"; +import { Plus } from "lucide-react"; +import { useTranslation } from "react-i18next"; + +interface Props { + +} + + + +const IndexesList: React.FC = ({ }) => { + const { t} = useTranslation() ; + return ( +
+
+ +
+ + +
+ ) +} + + +export default IndexesList; \ No newline at end of file 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 new file mode 100644 index 0000000..ef6969f --- /dev/null +++ b/src/pages/database/db-controller/tables-controller/table-accordion-item/table-accordion-body.tsx @@ -0,0 +1,137 @@ +import { Table } from "@/lib/interfaces/table" +import { Accordion, AccordionItem, Button, cn, Divider, Textarea } from "@heroui/react"; +import { ChevronLeft, ChevronRight, FileKey, FileText, FileType, Key, MessageSquareQuote } from "lucide-react"; + +import { useCallback, useState } from "react"; +import { useTranslation } from "react-i18next"; +import ColorPicker from "@/components/color-picker/color-picker"; +import FieldList from "./field/field-list"; +import IndexesList from "./index/indexes-list"; + + +export interface TableAccordionBodyProps { + table?: Table , +} + +const TableAccordionBody: React.FC = ({ table }) => { + + const [selectedKeys, setSelectedKeys] = useState(new Set(["fields"])); + const { t } = useTranslation(); + + const onColorChange = useCallback((color : string) => { + console.log ( color) + } ,[]) + + return ( +
+ + ( +
+ +
+ )} + classNames={{ + trigger: "hover:bg-default h-6 " + }} + subtitle={ +
+ + +
+ } + > + +
+ ( +
+ +
+ )} + classNames={{ + trigger: "hover:bg-default" + }} + subtitle={ +
+ + +
+ }> + +
+ ( +
+ +
+ )} + classNames={{ + trigger: "hover:bg-default" + }} + subtitle={ +
+ + +
+ }> +