diff --git a/backend/package.json b/backend/package.json index f11858d..b651993 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "backend", - "version": "v0.64", + "version": "v0.65", "description": "", "main": "index.js", "scripts": { diff --git a/frontend/components/Sidebar.tsx b/frontend/components/Sidebar.tsx index 68a54e5..645fd02 100644 --- a/frontend/components/Sidebar.tsx +++ b/frontend/components/Sidebar.tsx @@ -9,7 +9,6 @@ import SidebarNav from './Sidebar/SidebarNav'; import SidebarNotes from './Sidebar/SidebarNotes'; import SidebarProjects from './Sidebar/SidebarProjects'; import SidebarTags from './Sidebar/SidebarTags'; -import CreateNewDropdownButton from './Sidebar/CreateNewDropdownButton'; interface SidebarProps { isSidebarOpen: boolean; @@ -70,12 +69,6 @@ const Sidebar: React.FC = ({
{/* Sidebar Contents */} - openTaskModal(type || 'full')} - openProjectModal={openProjectModal} - openNoteModal={openNoteModal} - openAreaModal={openAreaModal} - /> = ({ setIsSidebarOpen={setIsSidebarOpen} isDropdownOpen={isDropdownOpen} toggleDropdown={toggleDropdown} + openTaskModal={openTaskModal} + openProjectModal={openProjectModal} + openNoteModal={openNoteModal} + openAreaModal={openAreaModal} + openTagModal={openTagModal} />
)} diff --git a/frontend/components/Sidebar/CreateNewDropdownButton.tsx b/frontend/components/Sidebar/CreateNewDropdownButton.tsx deleted file mode 100644 index fd85588..0000000 --- a/frontend/components/Sidebar/CreateNewDropdownButton.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import React, { useState, useEffect, useRef } from 'react'; -import { - PlusCircleIcon, - ChevronDownIcon, - ClipboardIcon, - FolderIcon, - BookOpenIcon, - Squares2X2Icon, -} from '@heroicons/react/24/outline'; -import { useTranslation } from 'react-i18next'; -import { Note } from '../../entities/Note'; -import { Area } from '../../entities/Area'; - -interface CreateNewDropdownButtonProps { - openTaskModal: (type?: 'simplified' | 'full') => void; - openProjectModal: () => void; - openNoteModal: (note: Note | null) => void; - openAreaModal: (area: Area | null) => void; -} - -const CreateNewDropdownButton: React.FC = ({ - openTaskModal, - openProjectModal, - openNoteModal, - openAreaModal, -}) => { - const { t } = useTranslation(); - const [isDropdownOpen, setIsDropdownOpen] = useState(false); - const dropdownRef = useRef(null); - - const toggleDropdown = () => { - setIsDropdownOpen(!isDropdownOpen); - }; - - // Handle click outside to close dropdown - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { - setIsDropdownOpen(false); - } - }; - - if (isDropdownOpen) { - document.addEventListener('mousedown', handleClickOutside); - } - - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [isDropdownOpen]); - - const handleDropdownSelect = (type: string) => { - switch (type) { - case 'Task': - openTaskModal('full'); - break; - case 'Project': - openProjectModal(); - break; - case 'Note': - openNoteModal(null); - break; - case 'Area': - openAreaModal(null); - break; - default: - break; - } - setIsDropdownOpen(false); - }; - - const dropdownItems = [ - { label: 'Task', translationKey: 'dropdown.task', icon: }, - { label: 'Project', translationKey: 'dropdown.project', icon: }, - { label: 'Note', translationKey: 'dropdown.note', icon: }, - { label: 'Area', translationKey: 'dropdown.area', icon: }, - ]; - - return ( -
-
- - - {isDropdownOpen && ( -
-
-
    - {dropdownItems.map(({ label, translationKey, icon }) => ( -
  • handleDropdownSelect(label)} - role="menuitem" - > - {icon} - {t(translationKey, label)} -
  • - ))} -
-
-
- )} -
-
- ); -}; - -export default CreateNewDropdownButton; diff --git a/frontend/components/Sidebar/SidebarFooter.tsx b/frontend/components/Sidebar/SidebarFooter.tsx index 83f02b2..ac3b859 100644 --- a/frontend/components/Sidebar/SidebarFooter.tsx +++ b/frontend/components/Sidebar/SidebarFooter.tsx @@ -1,5 +1,18 @@ -import React from 'react'; -import { SunIcon, MoonIcon } from '@heroicons/react/24/outline'; +import React, { useState, useEffect, useRef } from 'react'; +import { + SunIcon, + MoonIcon, + PlusIcon, + CheckIcon, + FolderIcon, + BookOpenIcon, + Squares2X2Icon, + TagIcon, + InboxIcon, +} from '@heroicons/react/24/outline'; +import { useTranslation } from 'react-i18next'; +import { Note } from '../../entities/Note'; +import { Area } from '../../entities/Area'; interface SidebarFooterProps { currentUser: { email: string }; @@ -9,6 +22,11 @@ interface SidebarFooterProps { setIsSidebarOpen: React.Dispatch>; isDropdownOpen: boolean; toggleDropdown: () => void; + openTaskModal: (type?: 'simplified' | 'full') => void; + openProjectModal: () => void; + openNoteModal: (note: Note | null) => void; + openAreaModal: (area: Area | null) => void; + openTagModal: (tag: any | null) => void; } const SidebarFooter: React.FC = ({ @@ -16,16 +34,160 @@ const SidebarFooter: React.FC = ({ toggleDarkMode, isSidebarOpen, setIsSidebarOpen, + openTaskModal, + openProjectModal, + openNoteModal, + openAreaModal, + openTagModal, }) => { + const { t } = useTranslation(); + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const dropdownRef = useRef(null); + + const toggleDropdown = () => { + setIsDropdownOpen(!isDropdownOpen); + }; + + // Handle click outside to close dropdown + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setIsDropdownOpen(false); + } + }; + + if (isDropdownOpen) { + document.addEventListener('mousedown', handleClickOutside); + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isDropdownOpen]); + + // Handle keyboard shortcuts + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + // Check for Ctrl/Cmd key combinations + if (event.ctrlKey || event.metaKey) { + switch (event.key.toLowerCase()) { + case 'i': + event.preventDefault(); + handleDropdownSelect('Inbox'); + break; + case 't': + event.preventDefault(); + handleDropdownSelect('Task'); + break; + case 'p': + event.preventDefault(); + handleDropdownSelect('Project'); + break; + case 'n': + event.preventDefault(); + handleDropdownSelect('Note'); + break; + case 'a': + event.preventDefault(); + handleDropdownSelect('Area'); + break; + case 'g': + event.preventDefault(); + handleDropdownSelect('Tag'); + break; + default: + break; + } + } + }; + + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, []); + + const handleDropdownSelect = (type: string) => { + switch (type) { + case 'Inbox': + openTaskModal('simplified'); + break; + case 'Task': + openTaskModal('full'); + break; + case 'Project': + openProjectModal(); + break; + case 'Note': + openNoteModal(null); + break; + case 'Area': + openAreaModal(null); + break; + case 'Tag': + openTagModal(null); + break; + default: + break; + } + setIsDropdownOpen(false); + }; + + const dropdownItems = [ + { label: 'Inbox', translationKey: 'dropdown.inbox', icon: , shortcut: '⌃I' }, + { label: 'Task', translationKey: 'dropdown.task', icon: , shortcut: '⌃T' }, + { label: 'Project', translationKey: 'dropdown.project', icon: , shortcut: '⌃P' }, + { label: 'Note', translationKey: 'dropdown.note', icon: , shortcut: '⌃N' }, + { label: 'Area', translationKey: 'dropdown.area', icon: , shortcut: '⌃A' }, + { label: 'Tag', translationKey: 'dropdown.tag', icon: , shortcut: '⌃G' }, + ]; return (
-
- {/* Dark Mode Toggle */} - {isSidebarOpen && ( + {isSidebarOpen && ( +
+ {/* Plus Icon Button - Left */} +
+ + + {/* Dropdown Menu */} + {isDropdownOpen && ( +
+
+ {dropdownItems.map(({ label, translationKey, icon, shortcut }) => ( + + ))} +
+
+ )} +
+ + {/* Dark Mode Toggle - Right */} - )} -
+
+ )}
); diff --git a/frontend/components/Sidebar/SidebarNav.tsx b/frontend/components/Sidebar/SidebarNav.tsx index 5c45024..6e54e35 100644 --- a/frontend/components/Sidebar/SidebarNav.tsx +++ b/frontend/components/Sidebar/SidebarNav.tsx @@ -77,9 +77,6 @@ const SidebarNav: React.FC = ({ handleNavClick, location }) => )} - {link.path === '/inbox' && ( -
  • - )} ))} diff --git a/package.json b/package.json index 6a0b896..b1e9df6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tududi", - "version": "v0.64", + "version": "v0.65", "description": "Self-hosted task management with hierarchical organization (Areas > Projects > Tasks), multi-language support, and Telegram integration. Built with React/TypeScript frontend and functional programming Express.js backend.", "main": "index.js", "directories": {