diff --git a/client/pages/staff/StaffInternalMarketplace.tsx b/client/pages/staff/StaffInternalMarketplace.tsx index e0953997..ad6f5317 100644 --- a/client/pages/staff/StaffInternalMarketplace.tsx +++ b/client/pages/staff/StaffInternalMarketplace.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import Layout from "@/components/Layout"; import SEO from "@/components/SEO"; import { Button } from "@/components/ui/button"; @@ -13,127 +13,153 @@ import { Badge } from "@/components/ui/badge"; import { ShoppingCart, Search, - Users, Clock, - AlertCircle, - CheckCircle, + Gift, + Star, + Package, + Loader2, + Coins, } from "lucide-react"; import { Input } from "@/components/ui/input"; +import { useAuth } from "@/lib/auth"; +import { aethexToast } from "@/components/ui/aethex-toast"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; -interface Service { +interface MarketplaceItem { id: string; name: string; - provider: string; - category: string; description: string; - availability: "Available" | "Booked" | "Coming Soon"; - turnaround: string; - requests: number; + category: string; + points_cost: number; + image_url?: string; + stock_count?: number; + is_available: boolean; } -const services: Service[] = [ - { - id: "1", - name: "Design Consultation", - provider: "Design Team", - category: "Design", - description: "1-on-1 design review and UX guidance for your project", - availability: "Available", - turnaround: "2 days", - requests: 8, - }, - { - id: "2", - name: "Code Review", - provider: "Engineering", - category: "Development", - description: "Thorough code review with architectural feedback", - availability: "Available", - turnaround: "1 day", - requests: 15, - }, - { - id: "3", - name: "Security Audit", - provider: "Security Team", - category: "Security", - description: "Comprehensive security review of your application", - availability: "Booked", - turnaround: "5 days", - requests: 4, - }, - { - id: "4", - name: "Performance Optimization", - provider: "DevOps", - category: "Infrastructure", - description: "Optimize your application performance and scalability", - availability: "Available", - turnaround: "3 days", - requests: 6, - }, - { - id: "5", - name: "Product Strategy Session", - provider: "Product Team", - category: "Product", - description: "Alignment session on product roadmap and features", - availability: "Coming Soon", - turnaround: "4 days", - requests: 12, - }, - { - id: "6", - name: "API Integration Support", - provider: "Backend Team", - category: "Development", - description: "Help integrating with AeThex APIs and services", - availability: "Available", - turnaround: "2 days", - requests: 10, - }, -]; +interface Order { + id: string; + quantity: number; + status: string; + created_at: string; + item?: { + name: string; + image_url?: string; + }; +} -const getAvailabilityColor = (availability: string) => { - switch (availability) { - case "Available": - return "bg-green-500/20 text-green-300 border-green-500/30"; - case "Booked": - return "bg-amber-500/20 text-amber-300 border-amber-500/30"; - case "Coming Soon": - return "bg-blue-500/20 text-blue-300 border-blue-500/30"; - default: - return "bg-slate-500/20 text-slate-300"; - } -}; +interface Points { + balance: number; + lifetime_earned: number; +} export default function StaffInternalMarketplace() { + const { session } = useAuth(); + const [items, setItems] = useState([]); + const [orders, setOrders] = useState([]); + const [points, setPoints] = useState({ balance: 0, lifetime_earned: 0 }); const [searchQuery, setSearchQuery] = useState(""); const [selectedCategory, setSelectedCategory] = useState("All"); + const [loading, setLoading] = useState(true); + const [orderDialog, setOrderDialog] = useState(null); + const [shippingAddress, setShippingAddress] = useState(""); - const categories = [ - "All", - "Design", - "Development", - "Security", - "Infrastructure", - "Product", - ]; + useEffect(() => { + if (session?.access_token) { + fetchMarketplace(); + } + }, [session?.access_token]); - const filtered = services.filter((service) => { + const fetchMarketplace = async () => { + try { + const res = await fetch("/api/staff/marketplace", { + headers: { Authorization: `Bearer ${session?.access_token}` }, + }); + const data = await res.json(); + if (res.ok) { + setItems(data.items || []); + setOrders(data.orders || []); + setPoints(data.points || { balance: 0, lifetime_earned: 0 }); + } + } catch (err) { + aethexToast.error("Failed to load marketplace"); + } finally { + setLoading(false); + } + }; + + const placeOrder = async () => { + if (!orderDialog) return; + try { + const res = await fetch("/api/staff/marketplace", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${session?.access_token}`, + }, + body: JSON.stringify({ + item_id: orderDialog.id, + quantity: 1, + shipping_address: shippingAddress, + }), + }); + const data = await res.json(); + if (res.ok) { + aethexToast.success("Order placed successfully!"); + setOrderDialog(null); + setShippingAddress(""); + fetchMarketplace(); + } else { + aethexToast.error(data.error || "Failed to place order"); + } + } catch (err) { + aethexToast.error("Failed to place order"); + } + }; + + const categories = ["All", ...new Set(items.map((i) => i.category))]; + + const filtered = items.filter((item) => { const matchesSearch = - service.name.toLowerCase().includes(searchQuery.toLowerCase()) || - service.provider.toLowerCase().includes(searchQuery.toLowerCase()); + item.name.toLowerCase().includes(searchQuery.toLowerCase()) || + item.description.toLowerCase().includes(searchQuery.toLowerCase()); const matchesCategory = - selectedCategory === "All" || service.category === selectedCategory; + selectedCategory === "All" || item.category === selectedCategory; return matchesSearch && matchesCategory; }); + const getStatusColor = (status: string) => { + switch (status) { + case "shipped": + return "bg-green-500/20 text-green-300"; + case "processing": + return "bg-blue-500/20 text-blue-300"; + case "pending": + return "bg-amber-500/20 text-amber-300"; + default: + return "bg-slate-500/20 text-slate-300"; + } + }; + + if (loading) { + return ( + +
+ +
+
+ ); + } + return (
@@ -148,47 +174,57 @@ export default function StaffInternalMarketplace() {
- +

- Internal Marketplace + Points Marketplace

- Request services and resources from other teams + Redeem your earned points for rewards

- {/* Summary */} + {/* Points Summary */}
-

- {services.length} -

-

- Available Services -

+
+
+

Your Balance

+

+ {points.balance.toLocaleString()} +

+
+ +
-

- { - services.filter((s) => s.availability === "Available") - .length - } -

-

Ready to Book

+
+
+

Lifetime Earned

+

+ {points.lifetime_earned.toLocaleString()} +

+
+ +
-

- {services.reduce((sum, s) => sum + s.requests, 0)} -

-

Total Requests

+
+
+

My Orders

+

+ {orders.length} +

+
+ +
@@ -198,7 +234,7 @@ export default function StaffInternalMarketplace() {
setSearchQuery(e.target.value)} className="pl-10 bg-slate-800 border-slate-700 text-slate-100" @@ -225,66 +261,134 @@ export default function StaffInternalMarketplace() {
- {/* Services Grid */} -
- {filtered.map((service) => ( + {/* Items Grid */} +
+ {filtered.map((item) => (
- {service.name} + {item.name} - {service.provider} + {item.category}
- - {service.availability} - + {item.stock_count !== null && item.stock_count < 10 && ( + + Only {item.stock_count} left + + )}
-

- {service.description} -

-
-
- - {service.turnaround} -
-
- - {service.requests} requests +

{item.description}

+
+
+ + {item.points_cost.toLocaleString()} pts
+
- ))}
{filtered.length === 0 && ( -
-

No services found

+
+

No rewards found

+
+ )} + + {/* Recent Orders */} + {orders.length > 0 && ( +
+

Recent Orders

+
+ {orders.slice(0, 5).map((order) => ( + + +
+
+

+ {order.item?.name || "Unknown Item"} +

+

+ Qty: {order.quantity} • {new Date(order.created_at).toLocaleDateString()} +

+
+ + {order.status.replace(/\b\w/g, (l) => l.toUpperCase())} + +
+
+
+ ))} +
)}
+ + {/* Order Dialog */} + setOrderDialog(null)}> + + + + Redeem {orderDialog?.name} + + +
+
+
+ Cost + + {orderDialog?.points_cost.toLocaleString()} pts + +
+
+ Your Balance After + + {(points.balance - (orderDialog?.points_cost || 0)).toLocaleString()} pts + +
+
+
+ + setShippingAddress(e.target.value)} + className="bg-slate-700 border-slate-600 text-slate-100" + /> +
+
+ + +
+
+
+
); } diff --git a/client/pages/staff/StaffKnowledgeBase.tsx b/client/pages/staff/StaffKnowledgeBase.tsx index 33f84925..f820f21f 100644 --- a/client/pages/staff/StaffKnowledgeBase.tsx +++ b/client/pages/staff/StaffKnowledgeBase.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import Layout from "@/components/Layout"; import SEO from "@/components/SEO"; import { Button } from "@/components/ui/button"; @@ -20,105 +20,133 @@ import { Users, Settings, Code, + Loader2, + ThumbsUp, + Eye, } from "lucide-react"; +import { useAuth } from "@/lib/auth"; +import { aethexToast } from "@/components/ui/aethex-toast"; interface KnowledgeArticle { id: string; title: string; category: string; - description: string; + content: string; tags: string[]; views: number; - updated: string; - icon: React.ReactNode; + helpful_count: number; + updated_at: string; + author?: { + full_name: string; + avatar_url?: string; + }; } -const articles: KnowledgeArticle[] = [ - { - id: "1", - title: "Getting Started with AeThex Platform", - category: "Onboarding", - description: "Complete guide for new team members to get up to speed", - tags: ["onboarding", "setup", "beginner"], - views: 324, - updated: "2 days ago", - icon: , - }, - { - id: "2", - title: "Troubleshooting Common Issues", - category: "Support", - description: "Step-by-step guides for resolving frequent problems", - tags: ["troubleshooting", "support", "faq"], - views: 156, - updated: "1 week ago", - icon: , - }, - { - id: "3", - title: "API Integration Guide", - category: "Development", - description: "How to integrate with AeThex APIs from your applications", - tags: ["api", "development", "technical"], - views: 89, - updated: "3 weeks ago", - icon: , - }, - { - id: "4", - title: "Team Communication Standards", - category: "Process", - description: "Best practices for internal communications and channel usage", - tags: ["communication", "process", "standards"], - views: 201, - updated: "4 days ago", - icon: , - }, - { - id: "5", - title: "Security & Access Control", - category: "Security", - description: - "Security policies, password management, and access procedures", - tags: ["security", "access", "compliance"], - views: 112, - updated: "1 day ago", - icon: , - }, - { - id: "6", - title: "Release Management Process", - category: "Operations", - description: "How to manage releases, deployments, and rollbacks", - tags: ["devops", "release", "operations"], - views: 67, - updated: "2 weeks ago", - icon: , - }, -]; - -const categories = [ - "All", - "Onboarding", - "Support", - "Development", - "Process", - "Security", - "Operations", -]; +const getCategoryIcon = (category: string) => { + switch (category) { + case "Onboarding": + return ; + case "Support": + return ; + case "Development": + return ; + case "Process": + return ; + case "Security": + return ; + default: + return ; + } +}; export default function StaffKnowledgeBase() { + const { session } = useAuth(); + const [articles, setArticles] = useState([]); + const [categories, setCategories] = useState([]); const [searchQuery, setSearchQuery] = useState(""); - const [selectedCategory, setSelectedCategory] = useState("All"); + const [selectedCategory, setSelectedCategory] = useState("all"); + const [loading, setLoading] = useState(true); - const filtered = articles.filter((article) => { - const matchesSearch = - article.title.toLowerCase().includes(searchQuery.toLowerCase()) || - article.description.toLowerCase().includes(searchQuery.toLowerCase()); - const matchesCategory = - selectedCategory === "All" || article.category === selectedCategory; - return matchesSearch && matchesCategory; - }); + useEffect(() => { + if (session?.access_token) { + fetchArticles(); + } + }, [session?.access_token, selectedCategory, searchQuery]); + + const fetchArticles = async () => { + try { + const params = new URLSearchParams(); + if (selectedCategory !== "all") params.append("category", selectedCategory); + if (searchQuery) params.append("search", searchQuery); + + const res = await fetch(`/api/staff/knowledge-base?${params}`, { + headers: { Authorization: `Bearer ${session?.access_token}` }, + }); + const data = await res.json(); + if (res.ok) { + setArticles(data.articles || []); + setCategories(data.categories || []); + } + } catch (err) { + aethexToast.error("Failed to load articles"); + } finally { + setLoading(false); + } + }; + + const trackView = async (articleId: string) => { + try { + await fetch("/api/staff/knowledge-base", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${session?.access_token}`, + }, + body: JSON.stringify({ action: "view", id: articleId }), + }); + } catch (err) { + // Silent fail for analytics + } + }; + + const markHelpful = async (articleId: string) => { + try { + await fetch("/api/staff/knowledge-base", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${session?.access_token}`, + }, + body: JSON.stringify({ action: "helpful", id: articleId }), + }); + aethexToast.success("Marked as helpful!"); + fetchArticles(); + } catch (err) { + aethexToast.error("Failed to mark as helpful"); + } + }; + + const formatDate = (dateStr: string) => { + const date = new Date(dateStr); + const now = new Date(); + const diff = now.getTime() - date.getTime(); + const days = Math.floor(diff / (1000 * 60 * 60 * 24)); + if (days === 0) return "Today"; + if (days === 1) return "Yesterday"; + if (days < 7) return `${days} days ago`; + if (days < 30) return `${Math.floor(days / 7)} weeks ago`; + return date.toLocaleDateString(); + }; + + if (loading) { + return ( + +
+ +
+
+ ); + } return ( @@ -163,12 +191,22 @@ export default function StaffKnowledgeBase() { {/* Category Filter */}
+ {categories.map((category) => (
@@ -236,7 +290,7 @@ export default function StaffKnowledgeBase() { ))}
- {filtered.length === 0 && ( + {articles.length === 0 && (

No articles found

diff --git a/client/pages/staff/StaffLearningPortal.tsx b/client/pages/staff/StaffLearningPortal.tsx index cda91581..65fc37fa 100644 --- a/client/pages/staff/StaffLearningPortal.tsx +++ b/client/pages/staff/StaffLearningPortal.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import Layout from "@/components/Layout"; import SEO from "@/components/SEO"; import { Button } from "@/components/ui/button"; @@ -19,109 +19,112 @@ import { FileText, Clock, CheckCircle, + Loader2, } from "lucide-react"; +import { useAuth } from "@/lib/auth"; +import { aethexToast } from "@/components/ui/aethex-toast"; interface Course { id: string; title: string; - instructor: string; + description: string; category: string; - duration: string; + duration_weeks: number; + lesson_count: number; + is_required: boolean; progress: number; - status: "In Progress" | "Completed" | "Available"; - lessons: number; - icon: React.ReactNode; + status: string; + started_at?: string; + completed_at?: string; } -const courses: Course[] = [ - { - id: "1", - title: "Advanced TypeScript Patterns", - instructor: "Sarah Chen", - category: "Development", - duration: "4 weeks", - progress: 65, - status: "In Progress", - lessons: 12, - icon: , - }, - { - id: "2", - title: "Leadership Fundamentals", - instructor: "Marcus Johnson", - category: "Leadership", - duration: "6 weeks", - progress: 0, - status: "Available", - lessons: 15, - icon: , - }, - { - id: "3", - title: "AWS Solutions Architect", - instructor: "David Lee", - category: "Infrastructure", - duration: "8 weeks", - progress: 100, - status: "Completed", - lessons: 20, - icon: , - }, - { - id: "4", - title: "Product Management Essentials", - instructor: "Elena Rodriguez", - category: "Product", - duration: "5 weeks", - progress: 40, - status: "In Progress", - lessons: 14, - icon: