Files
encoach_frontend_new_v2/src/pages/admin/AdminBatchDetail.tsx
Yamen Ahmad fbd58fa5a6 feat(i18n,rtl): full Arabic localization + RTL sweep across all layouts
Frontend
- i18n: install tailwindcss-rtl, Cairo font, RTL-aware direction in index.css.
- Language toggle: localize aria-label / menu label, persist choice, update
  document dir synchronously.
- Sidebar: add `side` prop so the drawer pins to the right in RTL; wire up
  AdminLmsLayout, RoleLayout (student/teacher) and AppSidebar to pass
  side = i18n.dir() === 'rtl' ? 'right' : 'left'.
- AdminLmsLayout: convert every nav item from hard-coded title to titleKey,
  translate group labels (incl. the collapsible Training), breadcrumbs,
  user menu (Profile / Settings / Logout), help button and toggle aria
  labels; replace physical mr-/right- utilities with logical me-/end-.
- AI components (AiTipBanner, AiInsightsPanel, AiAlertBanner, AiSearchBar,
  AiAssistantDrawer): apply dir="auto" at the container level, localize
  titles, loading / error / empty states.
- Dashboards (admin / student / teacher): wrap numeric values in <bdi>,
  localize dates via ar-EG, fix flex direction for KPI and assignment cards.
- UI primitives (breadcrumb, calendar, carousel, dropdown-menu, menubar,
  context-menu, pagination, sidebar): flip chevrons in RTL via a scoped
  CSS rule, swap pl-/pr-/ml-/mr- for ps-/pe-/ms-/me-.
- Add logical-direction helpers and bidirectional isolation classes.

Locales
- Expand en.ts and ar.ts with full `nav`, `sidebarGroup`, `breadcrumb`,
  `userMenu`, `chrome`, `ai`, and dashboard key sets; keep key parity.

API client
- `api-client.ts` reads the active language from localStorage/i18n and sends
  `Accept-Language` on every request so the backend can localize AI output.

Backend (encoach_ai)
- openai_service: add _LANGUAGE_NAMES, normalize_language, language-aware
  system prompt injection for every OpenAI call.
- coach_service + controllers (coach_controller, ai_controller): thread
  the requested language from headers / user locale down to OpenAIService.
- ai_feedback: fix latent registry error by pointing course_id at op.course
  instead of the non-existent encoach.course.

Other
- .gitignore: ignore runtime odoo logs and local caches.

Made-with: Cursor
2026-04-19 18:13:16 +04:00

90 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useParams, Link } from "react-router-dom";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { useBatches, useStudents, useTimetable } from "@/hooks/queries";
import { ArrowLeft, Users, Calendar, BookOpen } from "lucide-react";
export default function AdminBatchDetail() {
const { id } = useParams();
const batchId = id ? Number(id) : NaN;
const { data: batchesData, isLoading: lb } = useBatches();
const { data: studentsData, isLoading: ls } = useStudents(
Number.isFinite(batchId) ? { batch_id: batchId, size: 500 } : { size: 500 },
);
const { data: timetableData, isLoading: ltt } = useTimetable();
const batches = batchesData?.items ?? [];
const students = studentsData?.items ?? [];
const timetableSessions = Array.isArray(timetableData) ? timetableData : [];
if (lb || ls || ltt) return <div className="flex items-center justify-center min-h-[400px]"><div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" /></div>;
const batch = batches.find(b => String(b.id) === id);
if (!batch) return <div className="p-8 text-center text-muted-foreground">Batch not found.</div>;
const studentIds = Array.isArray(batch.student_ids) ? batch.student_ids : [];
const batchStudents = Number.isFinite(batchId)
? students.filter((s) => s.batch_id === batchId)
: students.filter((s) => studentIds.includes(s.id));
const coursePrefix = (batch.course_name || "").split(" ")[0];
const batchSessions = timetableSessions.filter(s =>
s.batch_name === batch.name || (coursePrefix && (s.course_name || "").includes(coursePrefix)),
);
return (
<div className="space-y-6">
<div className="flex items-center gap-3">
<Button variant="ghost" size="icon" asChild><Link to="/admin/batches"><ArrowLeft className="h-4 w-4 rtl:rotate-180" /></Link></Button>
<div>
<h1 className="text-2xl font-bold">{batch.name}</h1>
<p className="text-muted-foreground">{batch.course_name} · {batch.teacher_name}</p>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-4 gap-4">
<Card><CardContent className="pt-4"><Users className="h-4 w-4 text-primary mb-1" /><p className="text-lg font-bold">{batch.student_count}/{batch.capacity}</p><p className="text-xs text-muted-foreground">Students</p></CardContent></Card>
<Card><CardContent className="pt-4"><Calendar className="h-4 w-4 text-info mb-1" /><p className="text-sm font-bold">{batch.schedule}</p><p className="text-xs text-muted-foreground">Schedule</p></CardContent></Card>
<Card><CardContent className="pt-4"><BookOpen className="h-4 w-4 text-success mb-1" /><p className="text-sm font-bold">{batch.start_date} {batch.end_date}</p><p className="text-xs text-muted-foreground">Duration</p></CardContent></Card>
<Card><CardContent className="pt-4"><Badge variant={batch.status === "active" ? "default" : "secondary"} className="capitalize">{batch.status}</Badge><p className="text-xs text-muted-foreground mt-1">Status</p></CardContent></Card>
</div>
<Card>
<CardHeader><CardTitle className="text-lg">Enrolled Students</CardTitle></CardHeader>
<CardContent>
<Table>
<TableHeader><TableRow><TableHead>Student</TableHead><TableHead>Attendance</TableHead><TableHead>Avg Grade</TableHead><TableHead>Risk</TableHead></TableRow></TableHeader>
<TableBody>
{batchStudents.map(s => (
<TableRow key={s.id}>
<TableCell className="font-medium">{s.name}</TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell><Badge variant="secondary" className="capitalize"></Badge></TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
<Card>
<CardHeader><CardTitle className="text-lg">Timetable</CardTitle></CardHeader>
<CardContent className="space-y-2">
{batchSessions.length === 0 ? (
<p className="text-muted-foreground text-sm">No sessions scheduled.</p>
) : batchSessions.map(s => (
<div key={s.id} className="flex items-center justify-between p-3 rounded border">
<div>
<p className="text-sm font-medium">{s.day} {s.start_time}{s.end_time}</p>
<p className="text-xs text-muted-foreground">{s.room}</p>
</div>
<span className="text-xs px-2 py-1 rounded text-white bg-primary">{s.course_name}</span>
</div>
))}
</CardContent>
</Card>
</div>
);
}