From 06658a155954289a728be77e46de35a921cb098e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96zt=C3=BCrk?= Date: Sun, 19 Oct 2025 11:24:55 +0300 Subject: [PATCH] =?UTF-8?q?Mobile=20men=C3=BC=20tasar=C4=B1m=C4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/intranet/IntranetSidebar.tsx | 268 ++++++++++++++---- ui/src/components/intranet/Tasks/index.tsx | 6 +- 2 files changed, 212 insertions(+), 62 deletions(-) diff --git a/ui/src/components/intranet/IntranetSidebar.tsx b/ui/src/components/intranet/IntranetSidebar.tsx index a96b2ba5..be8684d5 100644 --- a/ui/src/components/intranet/IntranetSidebar.tsx +++ b/ui/src/components/intranet/IntranetSidebar.tsx @@ -1,4 +1,4 @@ -import React, { useState, useMemo } from 'react' +import React, { useState, useMemo, useEffect } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { HiHome, @@ -14,9 +14,28 @@ import { HiClipboardDocumentCheck, HiUserPlus, HiBars3, - HiXMark, HiChevronLeft, + HiCog6Tooth, + HiArrowsUpDown, + HiSquares2X2, } from 'react-icons/hi2' +import { + DndContext, + closestCenter, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, + DragEndEvent, +} from '@dnd-kit/core' +import { + arrayMove, + SortableContext, + sortableKeyboardCoordinates, + useSortable, + verticalListSortingStrategy, +} from '@dnd-kit/sortable' +import { CSS } from '@dnd-kit/utilities' import { mockTasks, mockEvents, @@ -131,7 +150,36 @@ interface IntranetSidebarProps { const IntranetSidebar: React.FC = ({ activePath, onNavigate }) => { const [expandedMenus, setExpandedMenus] = useState(['hr']) const [isCollapsed, setIsCollapsed] = useState(false) - const [isMobileOpen, setIsMobileOpen] = useState(false) + const [isDesignMode, setIsDesignMode] = useState(false) + const [customOrder, setCustomOrder] = useState([]) + + // Mobil ekranlarda otomatik daralt + useEffect(() => { + const handleResize = () => { + if (window.innerWidth < 1024) { + // lg breakpoint (1024px) + setIsCollapsed(true) + } + } + + // İlk yükleme + handleResize() + + window.addEventListener('resize', handleResize) + return () => window.removeEventListener('resize', handleResize) + }, []) + + // localStorage'dan özel sıralamayı yükle + useEffect(() => { + const savedOrder = localStorage.getItem('intranet-menu-order') + if (savedOrder) { + try { + setCustomOrder(JSON.parse(savedOrder)) + } catch (e) { + console.error('Failed to load menu order:', e) + } + } + }, []) // Dinamik badge sayılarını hesapla const badgeCounts = useMemo(() => { @@ -172,30 +220,113 @@ const IntranetSidebar: React.FC = ({ activePath, onNavigat const menuItems = useMemo(() => getMenuItems(badgeCounts), [badgeCounts]) - const renderMenuItem = (item: MenuItem, level: number = 0) => { + // Menü sıralamasını customOrder'a göre düzenle + const orderedMenuItems = useMemo(() => { + if (customOrder.length === 0) { + return menuItems + } + + const ordered = [...menuItems].sort((a, b) => { + const indexA = customOrder.indexOf(a.id) + const indexB = customOrder.indexOf(b.id) + + // Eğer her ikisi de customOrder'da varsa, sıralarına göre sırala + if (indexA !== -1 && indexB !== -1) { + return indexA - indexB + } + + // Sadece a customOrder'da varsa, a önce gelsin + if (indexA !== -1) return -1 + + // Sadece b customOrder'da varsa, b önce gelsin + if (indexB !== -1) return 1 + + // İkisi de yoksa, orijinal sıraları koru + return 0 + }) + + return ordered + }, [menuItems, customOrder]) + + // Drag & Drop sensörleri + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }), + ) + + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event + + if (over && active.id !== over.id) { + const oldIndex = orderedMenuItems.findIndex((item) => item.id === active.id) + const newIndex = orderedMenuItems.findIndex((item) => item.id === over.id) + + const newOrder = arrayMove(orderedMenuItems, oldIndex, newIndex) + const newOrderIds = newOrder.map((item) => item.id) + + setCustomOrder(newOrderIds) + localStorage.setItem('intranet-menu-order', JSON.stringify(newOrderIds)) + } + } + + const toggleDesignMode = () => { + setIsDesignMode(!isDesignMode) + } + + const resetMenuOrder = () => { + setCustomOrder([]) + localStorage.removeItem('intranet-menu-order') + } + + // Sortable MenuItem komponenti + const SortableMenuItem = ({ item, level = 0 }: { item: MenuItem; level?: number }) => { + const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ + id: item.id, + disabled: !isDesignMode, + }) + + const style = { + transform: CSS.Transform.toString(transform), + transition, + opacity: isDragging ? 0.5 : 1, + } + const hasChildren = item.children && item.children.length > 0 const isExpanded = expandedMenus.includes(item.id) const active = isActive(item.path) return ( -
+
- - {/* Mobile Overlay */} - - {isMobileOpen && ( - setIsMobileOpen(false)} - className="lg:hidden fixed inset-0 bg-black/50 z-40" - /> - )} - - {/* Sidebar */} -
+
{!isCollapsed ? ( - <> -

- İntranet Portal -

- - +
+
+ +
+
+ + +
+
) : ( )}
- + {/* Tasarım Modu Bilgi Paneli */} + {!isCollapsed && isDesignMode && ( +
+
+

+ + Menü öğelerini sürükleyerek sıralayın +

+ {customOrder.length > 0 && ( + + )} +
+
+ )} + + + item.id)} + strategy={verticalListSortingStrategy} + > + + + ) diff --git a/ui/src/components/intranet/Tasks/index.tsx b/ui/src/components/intranet/Tasks/index.tsx index 1c8a2804..d9190db6 100644 --- a/ui/src/components/intranet/Tasks/index.tsx +++ b/ui/src/components/intranet/Tasks/index.tsx @@ -371,10 +371,10 @@ const TasksModule: React.FC = () => {
{column.icon}

{column.title}