commit 4bfc10fd98a3dab982a387fa418257589e854be6 Author: Thibault Pouch Date: Thu Feb 26 16:26:16 2026 +0100 feat : Initial commit diff --git a/nest-intra/README.md b/nest-intra/README.md new file mode 100644 index 0000000..017d26d --- /dev/null +++ b/nest-intra/README.md @@ -0,0 +1,32 @@ +# CrowMate Intranet + +This folder contains the intranet section separated from the main website repository. + +## Files Moved + +### Pages +- `src/pages/intranet/IntranetDashboard.tsx` +- `src/pages/intranet/IntranetBugs.tsx` +- `src/pages/intranet/IntranetFeed.tsx` +- `src/pages/intranet/IntranetEvents.tsx` +- `src/pages/intranet/IntranetUsers.tsx` +- `src/pages/intranet/IntranetModeration.tsx` + +### Components +- `src/components/layout/IntranetLayout.tsx` + +## Next Steps + +To set up this as a separate repository: + +1. Initialize Git repository: + ```bash + cd /home/norsys/Delivery/website-intranet + git init + ``` + +2. Copy necessary configuration files from the main website (package.json, tsconfig, vite.config, etc.) + +3. Install dependencies and set up the project structure + +4. Update imports and routing as needed for the standalone intranet application diff --git a/nest-intra/src/components/layout/IntranetLayout.tsx b/nest-intra/src/components/layout/IntranetLayout.tsx new file mode 100644 index 0000000..52bf4a0 --- /dev/null +++ b/nest-intra/src/components/layout/IntranetLayout.tsx @@ -0,0 +1,150 @@ +import { NavLink, Outlet, useNavigate } from 'react-router-dom'; +import { useAuth } from '../../contexts/AuthContext'; +import { useCallback } from 'react'; + +const INTRANET_LINKS = [ + { to: '/intranet', label: 'Dashboard', icon: '[>]', end: true }, + { to: '/intranet/bugs', label: 'Bug Reports', icon: '[!]', end: false }, + { to: '/intranet/feed', label: 'Team Feed', icon: '[~]', end: false }, + { to: '/intranet/events', label: 'Events', icon: '[E]', end: false }, + { to: '/intranet/users', label: 'Users', icon: '[U]', end: false }, + { to: '/intranet/moderation', label: 'Moderation', icon: '[M]', end: false }, +]; + +export function IntranetLayout() { + const { user, logout } = useAuth(); + const navigate = useNavigate(); + + const handleLogout = useCallback(() => { + logout(); + navigate('/'); + }, [logout, navigate]); + + return ( +
+ {/* Sidebar */} + + + {/* Main content */} +
+ +
+
+ ); +} diff --git a/nest-intra/src/pages/intranet/IntranetBugs.tsx b/nest-intra/src/pages/intranet/IntranetBugs.tsx new file mode 100644 index 0000000..05722bf --- /dev/null +++ b/nest-intra/src/pages/intranet/IntranetBugs.tsx @@ -0,0 +1,251 @@ +import { useState, useMemo, useCallback } from 'react'; +import { MOCK_BUGS, MOCK_USERS } from '../../data/mockData'; +import { useAuth } from '../../contexts/AuthContext'; +import { formatDate, formatDateTime } from '../../utils/format'; +import type { BugReport, BugSeverity, BugStatus, BugReportNote } from '../../types'; + +function StatusBadge({ status }: { status: BugStatus }) { + const map: Record = { open: 'badge-open', in_progress: 'badge-progress', resolved: 'badge-resolved', closed: 'badge-closed' }; + const labels: Record = { open: 'Open', in_progress: 'In Progress', resolved: 'Resolved', closed: 'Closed' }; + return {labels[status]}; +} + +function SeverityBadge({ severity }: { severity: BugSeverity }) { + const map: Record = { low: 'badge-low', medium: 'badge-medium', high: 'badge-high', critical: 'badge-critical' }; + return {severity}; +} + +const STATUSES: BugStatus[] = ['open', 'in_progress', 'resolved', 'closed']; +const STAFF_MEMBERS = MOCK_USERS.filter((u) => u.role === 'dev' || u.role === 'com'); + +export default function IntranetBugs() { + const { user } = useAuth(); + + const [bugs, setBugs] = useState(MOCK_BUGS); + const [selected, setSelected] = useState(null); + const [statusFilter, setStatusFilter] = useState('all'); + const [severityFilter, setSeverityFilter] = useState('all'); + const [assignedFilter, setAssignedFilter] = useState('all'); + const [noteText, setNoteText] = useState(''); + + const openCount = bugs.filter((b) => b.status === 'open').length; + const criticalCount = bugs.filter((b) => b.severity === 'critical').length; + const myCount = bugs.filter((b) => b.assignedToId === user?.id).length; + + const filtered = useMemo(() => { + return bugs.filter((b) => { + if (statusFilter !== 'all' && b.status !== statusFilter) return false; + if (severityFilter !== 'all' && b.severity !== severityFilter) return false; + if (assignedFilter !== 'all') { + if (assignedFilter === 'unassigned' && b.assignedToId) return false; + if (assignedFilter !== 'unassigned' && b.assignedToId !== assignedFilter) return false; + } + return true; + }); + }, [bugs, statusFilter, severityFilter, assignedFilter]); + + const updateBug = useCallback((id: string, changes: Partial) => { + setBugs((prev) => prev.map((b) => b.id === id ? { ...b, ...changes, updatedAt: new Date().toISOString() } : b)); + setSelected((prev) => prev?.id === id ? { ...prev, ...changes, updatedAt: new Date().toISOString() } : prev); + }, []); + + const handleAssign = useCallback((bugId: string, staffId: string) => { + const staff = STAFF_MEMBERS.find((s) => s.id === staffId); + updateBug(bugId, { assignedToId: staffId || undefined, assignedToName: staff?.username }); + }, [updateBug]); + + const handleStatusChange = useCallback((bugId: string, status: BugStatus) => { + updateBug(bugId, { status }); + }, [updateBug]); + + const handleAddNote = useCallback((bugId: string) => { + if (!noteText.trim() || !user) return; + const note: BugReportNote = { + id: `n${Date.now()}`, + bugReportId: bugId, + authorId: user.id, + authorName: user.username, + content: noteText.trim(), + createdAt: new Date().toISOString(), + }; + setBugs((prev) => prev.map((b) => b.id === bugId ? { ...b, notes: [...(b.notes ?? []), note] } : b)); + setSelected((prev) => prev?.id === bugId ? { ...prev, notes: [...(prev.notes ?? []), note] } : prev); + setNoteText(''); + }, [noteText, user]); + + return ( +
+ {/* Left panel */} +
+
+
+ INTRANET / BUG REPORTS +
+

BUG DASHBOARD

+ + {/* Stats */} +
+ {[ + { label: 'Open', value: openCount, color: 'var(--color-green)' }, + { label: 'Critical', value: criticalCount, color: 'var(--color-red)' }, + { label: 'Mine', value: myCount, color: 'var(--color-amber)' }, + ].map(({ label, value, color }) => ( +
+
{value}
+
{label}
+
+ ))} +
+ + {/* Filters */} +
+ + + +
+
+ + {/* Bug list */} +
+ {filtered.length === 0 ? ( +
+ No reports match filters. +
+ ) : ( + filtered.map((bug) => ( +
setSelected(bug === selected ? null : bug)} + style={{ + background: selected?.id === bug.id ? 'rgba(37,99,235,0.08)' : 'var(--color-surface)', + border: `1px solid ${selected?.id === bug.id ? 'var(--color-yellow)' : 'var(--color-border)'}`, + padding: '0.85rem 1.1rem', + cursor: 'pointer', + transition: 'all 0.15s', + }} + role="button" + tabIndex={0} + onKeyDown={(e) => e.key === 'Enter' && setSelected(bug === selected ? null : bug)} + aria-label={`Select bug report ${bug.uniqueCode}`} + > +
+
+ {bug.uniqueCode} + + +
+ + {formatDate(bug.createdAt)} + +
+
+ {bug.title} +
+
+ {bug.submittedByName} — Assigned: {bug.assignedToName ?? 'None'} +
+
+ )) + )} +
+
+ + {/* Right panel — detail */} + {selected && ( +
+
+
+
{selected.uniqueCode}
+

{selected.title}

+
+ +
+ + {/* Controls */} +
+
+ + +
+
+ + +
+
+ + {/* Description */} +
+
DESCRIPTION
+
+ {selected.description} +
+
+ +
+
STEPS TO REPRODUCE
+
+ {selected.stepsToReproduce} +
+
+ + {/* Internal notes */} +
+
INTERNAL NOTES (staff only)
+ {(selected.notes ?? []).length === 0 ? ( +
No notes yet.
+ ) : ( +
+ {(selected.notes ?? []).map((note) => ( +
+
+ {note.authorName} — {formatDateTime(note.createdAt)} +
+
+ {note.content} +
+
+ ))} +
+ )} +