diff --git a/resources/js/components/navigation/Navigation.tsx b/resources/js/components/navigation/Navigation.tsx index 4be443de..f58db1e6 100644 --- a/resources/js/components/navigation/Navigation.tsx +++ b/resources/js/components/navigation/Navigation.tsx @@ -5,8 +5,6 @@ * - SiteRail desktop side rail (collapsible 72px <-> 280px) * - SiteBottomNav mobile bottom bar * - SiteDrawer mobile top hamburger bar + slide-over drawer - * - AdminRail staff side rail (same rail language, admin items) - * - AdminDrawer staff mobile drawer (teleported, same slide motion) * * Pure state/logic stays in the existing TypeScript modules under * resources/js/lib (navigation, useDarkMode, useOrientation, @@ -27,7 +25,7 @@ import { toRef, watch, } from 'vue'; -import type { PropType, Ref } from 'vue'; +import type { Ref } from 'vue'; import AppLogo from '@/components/AppLogo.vue'; import LogoutConfirmModal from '@/components/LogoutConfirmModal.vue'; @@ -35,16 +33,8 @@ import NotificationDropdown from '@/components/NotificationDropdown.vue'; import MaterialIcon from '@/components/ui/MaterialIcon.vue'; import { allNavItems, primaryNavItems } from '@/lib/navigation'; import { useDarkMode } from '@/lib/useDarkMode'; -import { usePermissions } from '@/lib/usePermissions'; import { usePwa } from '@/lib/usePwa'; -export type AdminNavItem = { - name: string; - to: string; - icon: string; - permission?: string; -}; - /** Authenticated user shape used across the navigation surfaces. */ type AuthedUser = { name: string; @@ -98,21 +88,6 @@ function isActiveAdminRoute(to: string, currentUrl: string): boolean { return currentUrl.startsWith(to); } -function getCollapsedAdminLabel(name: string): string { - const map: Record = { - 'Manage Contents': 'Contents', - 'Manage Blogs': 'Blogs', - 'Manage Products': 'Products', - 'Manage Forum': 'Forum', - 'Support Tickets': 'Support', - 'Site Notice': 'Notice', - 'Global Chat': 'Chat', - 'Send Emails': 'Emails', - }; - - return map[name] || name.replace(/^Manage\s+/i, ''); -} - /** * Opens the dropdown from the row label. Stops propagation so the * label-targeted event never reaches the document outside-click @@ -1802,575 +1777,3 @@ export const SiteDrawer = defineComponent({ ); }, }); - -/* ------------------------------------------------------------------ */ -/* AdminRail — staff side rail */ -/* ------------------------------------------------------------------ */ - -export const AdminRail = defineComponent({ - name: 'AdminRail', - props: { - navigation: { - type: Array as PropType, - required: true, - }, - collapsed: { type: Boolean, required: true }, - }, - emits: ['toggle'], - setup(props, { emit }) { - const page = usePage(); - const user = computed( - () => page.props.auth?.user as AuthedUser | undefined, - ); - const currentUrl = computed(() => String(page.url)); - const { can } = usePermissions(); - const showLogoutModal = ref(false); - const notifRowRef = ref(null); - - const handleClearCache = () => { - if (confirm('Are you sure you want to clear all cache?')) { - router.post('/admin/clear-cache'); - } - }; - - return () => ( - - ); - }, -}); - -/* ------------------------------------------------------------------ */ -/* AdminDrawer — staff mobile drawer (teleported) */ -/* ------------------------------------------------------------------ */ - -export const AdminDrawer = defineComponent({ - name: 'AdminDrawer', - props: { - navigation: { - type: Array as PropType, - required: true, - }, - isOpen: { type: Boolean, required: true }, - }, - emits: ['close'], - setup(props, { emit }) { - const { can } = usePermissions(); - const page = usePage(); - const user = computed( - () => (page.props.auth as any)?.user as AuthedUser | undefined, - ); - const currentUrl = computed(() => String(page.url)); - - const showLogoutModal = ref(false); - const panelRef = ref(null); - const closeButtonRef = ref(null); - - const close = () => emit('close'); - - useDialogA11y({ - open: toRef(props, 'isOpen'), - panel: panelRef, - initial: closeButtonRef, - onEscape: close, - }); - - const askLogout = () => { - emit('close'); - showLogoutModal.value = true; - }; - - const handleClearCache = () => { - if (confirm('Are you sure you want to clear all cache?')) { - emit('close'); - router.post('/admin/clear-cache'); - } - }; - - return () => ( - <> - - {props.isOpen && ( -
-
- - - - -
- )} -
- - - - ); - }, -}); diff --git a/resources/js/layouts/AdminLayout.vue b/resources/js/layouts/AdminLayout.vue index 8bae17dd..57ad218a 100644 --- a/resources/js/layouts/AdminLayout.vue +++ b/resources/js/layouts/AdminLayout.vue @@ -1,166 +1,110 @@ diff --git a/resources/js/pages/admin/Dashboard.vue b/resources/js/pages/admin/Dashboard.vue index 760c97cc..d49d5792 100644 --- a/resources/js/pages/admin/Dashboard.vue +++ b/resources/js/pages/admin/Dashboard.vue @@ -2,18 +2,26 @@ import { Head, Link } from '@inertiajs/vue3'; import { LayoutGrid, - Sparkles, Users, Eye, Share2, - BookOpen, - ArrowRight, - Zap, RefreshCw, BarChart3, + CheckSquare, + BookOpen, + MessageSquare, + LifeBuoy, + Bell, + MessageCircle, + UserCheck, + Radio, + Mail, } from 'lucide-vue-next'; -import { ref } from 'vue'; +import { computed, ref } from 'vue'; import EmptyState from '@/components/EmptyState.vue'; +import { usePermissions } from '@/lib/usePermissions'; + +const { can } = usePermissions(); const props = defineProps<{ totalAccounts?: number; @@ -24,6 +32,75 @@ const isLoading = ref(false); const hasFetched = ref(false); const errorMsg = ref(null); +const adminTools = [ + { + name: 'Resource Moderation', + href: '/admin/moderation/resources', + icon: CheckSquare, + color: 'text-emerald-600 bg-emerald-50 dark:bg-emerald-500/10 dark:text-emerald-400', + permission: 'moderate resources', + }, + { + name: 'Manage Contents', + href: '/admin/subjects', + icon: BookOpen, + color: 'text-indigo-600 bg-indigo-50 dark:bg-indigo-500/10 dark:text-indigo-400', + }, + { + name: 'Manage Forum', + href: '/admin/forums', + icon: MessageSquare, + color: 'text-blue-600 bg-blue-50 dark:bg-blue-500/10 dark:text-blue-400', + permission: 'manage forums', + }, + { + name: 'Support Tickets', + href: '/admin/tickets', + icon: LifeBuoy, + color: 'text-amber-600 bg-amber-50 dark:bg-amber-500/10 dark:text-amber-400', + permission: 'manage tickets', + }, + { + name: 'Site Notice', + href: '/admin/notice', + icon: Bell, + color: 'text-purple-600 bg-purple-50 dark:bg-purple-500/10 dark:text-purple-400', + permission: 'edit notice', + }, + { + name: 'Global Chat', + href: '/admin/chat', + icon: MessageCircle, + color: 'text-teal-600 bg-teal-50 dark:bg-teal-500/10 dark:text-teal-400', + permission: 'manage chat', + }, + { + name: 'Users & Roles', + href: '/admin/users', + icon: UserCheck, + color: 'text-sky-600 bg-sky-50 dark:bg-sky-500/10 dark:text-sky-400', + permission: 'view users', + }, + { + name: 'Peer & Pokes', + href: '/admin/peers/settings', + icon: Radio, + color: 'text-rose-600 bg-rose-50 dark:bg-rose-500/10 dark:text-rose-400', + permission: 'manage peers', + }, + { + name: 'Send Emails', + href: '/admin/emails/send', + icon: Mail, + color: 'text-orange-600 bg-orange-50 dark:bg-orange-500/10 dark:text-orange-400', + permission: 'send email', + }, +]; + +const visibleTools = computed(() => + adminTools.filter((tool) => !tool.permission || can(tool.permission)), +); + const fetchAnalytics = async (refresh = false) => { isLoading.value = true; errorMsg.value = null; @@ -34,13 +111,13 @@ const fetchAnalytics = async (refresh = false) => { ); if (!res.ok) { - throw new Error('Analytics ডাটা লোড করা যায়নি'); + throw new Error('Analytics could not be loaded'); } stats.value = await res.json(); hasFetched.value = true; } catch (e: any) { - errorMsg.value = e.message || 'Analytics লোড করতে সমস্যা হয়েছে'; + errorMsg.value = e.message || 'Failed to load analytics'; } finally { isLoading.value = false; } @@ -48,343 +125,290 @@ const fetchAnalytics = async (refresh = false) => {