diff --git a/artifacts/tx-os/src/pages/admin.tsx b/artifacts/tx-os/src/pages/admin.tsx index 7ea915a9..6151a1ff 100644 --- a/artifacts/tx-os/src/pages/admin.tsx +++ b/artifacts/tx-os/src/pages/admin.tsx @@ -138,7 +138,17 @@ import { useQueryClient, useQuery } from "@tanstack/react-query"; import { useAuth } from "@/contexts/AuthContext"; import { useUpload, type UploadResponse } from "@workspace/object-storage-web"; import { resolveServiceImageUrl } from "@/lib/image-url"; -import { ArrowRight, ArrowLeft, Settings, Plus, Pencil, Trash2, Shield, Upload, Loader2, LayoutDashboard, Grid2X2, Coffee, Users as UsersIcon, Menu as MenuIcon, TrendingUp, TrendingDown, UserPlus, Activity, KeyRound, Copy, ChevronDown, ScrollText, Download, X, ImageIcon, Power } from "lucide-react"; +import { ArrowRight, ArrowLeft, Settings, Plus, Pencil, Trash2, Shield, Upload, Loader2, LayoutDashboard, Grid2X2, Coffee, Users as UsersIcon, Menu as MenuIcon, TrendingUp, TrendingDown, UserPlus, Activity, KeyRound, Copy, ChevronDown, ScrollText, Download, X, ImageIcon, Power, HelpCircle, Palette, Link2, Hash, type LucideIcon } from "lucide-react"; +import * as LucideIcons from "lucide-react"; + +function resolveAppIcon(name: string): LucideIcon | null { + if (!name) return null; + const Candidate = (LucideIcons as Record)[name]; + if (typeof Candidate === "function" || (typeof Candidate === "object" && Candidate !== null)) { + return Candidate as LucideIcon; + } + return null; +} import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -1183,76 +1193,250 @@ export default function AdminPage() { {/* Edit App Modal */} - {editingApp && ( -
-
-

- {editingApp.id ? t("admin.editApp") : t("admin.addApp")} -

- {( - [ - { field: "nameAr", label: t("admin.appNameAr") }, - { field: "nameEn", label: t("admin.appNameEn") }, - { field: "slug", label: t("admin.appSlug") }, - { field: "iconName", label: t("admin.appIcon") }, - { field: "route", label: t("admin.appRoute") }, - { field: "color", label: t("admin.appColor") }, - ] as { field: keyof AppForm; label: string }[] - ).map(({ field, label }) => ( -
- - setEditingApp({ ...editingApp, form: { ...editingApp.form, [field]: e.target.value } })} - className="bg-white/70 border-slate-200" - /> + {editingApp && (() => { + const PreviewIcon = resolveAppIcon(editingApp.form.iconName); + const previewColor = /^#[0-9a-fA-F]{6}$/.test(editingApp.form.color) + ? editingApp.form.color + : "#6366f1"; + return ( +
{ if (e.target === e.currentTarget) setEditingApp(null); }} + > +
+ {/* Header */} +
+
+
+ {PreviewIcon ? : } +
+
+

+ {editingApp.id ? t("admin.editApp") : t("admin.addApp")} +

+

+ {editingApp.id + ? (lang === "ar" ? "حدّث تفاصيل التطبيق وأذوناته" : "Update app details and permissions") + : (lang === "ar" ? "أضف تطبيقاً جديداً للوحة" : "Add a new app to the launcher")} +

+
- ))} - {editingApp.id !== undefined ? ( - - ) : ( - - setEditingApp({ - ...editingApp, - form: { ...editingApp.form, permissionIds: next }, - }) - } - /> - )} - {editingApp.id !== undefined && ( - { - const p = (permissionsList ?? []).find((x) => x.id === id); - return p ? p.name : `#${id}`; - }} - /> - )} - {editingApp.id !== undefined && ( - - openAuditLogForTarget("app", editingApp.id!) - } - /> - )} -
- - + +
+ + {/* Body */} +
+ {/* Names row */} +
+
+ + setEditingApp({ ...editingApp, form: { ...editingApp.form, nameAr: e.target.value } })} + className="bg-slate-50/70 border-slate-200 rounded-lg h-10 text-sm focus-visible:ring-2 focus-visible:ring-indigo-500/30 focus-visible:border-indigo-400" + dir="rtl" + placeholder="الملاحظات" + /> +
+
+ + setEditingApp({ ...editingApp, form: { ...editingApp.form, nameEn: e.target.value } })} + className="bg-slate-50/70 border-slate-200 rounded-lg h-10 text-sm focus-visible:ring-2 focus-visible:ring-indigo-500/30 focus-visible:border-indigo-400" + dir="ltr" + placeholder="Notes" + /> +
+
+ + {/* Identifier row */} +
+
+ + setEditingApp({ ...editingApp, form: { ...editingApp.form, slug: e.target.value } })} + className="bg-slate-50/70 border-slate-200 rounded-lg h-10 text-sm font-mono focus-visible:ring-2 focus-visible:ring-indigo-500/30 focus-visible:border-indigo-400" + dir="ltr" + placeholder="notes" + /> +
+
+ + setEditingApp({ ...editingApp, form: { ...editingApp.form, route: e.target.value } })} + className="bg-slate-50/70 border-slate-200 rounded-lg h-10 text-sm font-mono focus-visible:ring-2 focus-visible:ring-indigo-500/30 focus-visible:border-indigo-400" + dir="ltr" + placeholder="/notes" + /> +
+
+ + {/* Visuals row — icon + color with live previews */} +
+ {/* Icon picker */} +
+ +
+
+ {PreviewIcon ? : } +
+ setEditingApp({ ...editingApp, form: { ...editingApp.form, iconName: e.target.value } })} + className="bg-slate-50/70 border-slate-200 rounded-lg h-10 text-sm font-mono focus-visible:ring-2 focus-visible:ring-indigo-500/30 focus-visible:border-indigo-400" + dir="ltr" + placeholder="StickyNote" + /> +
+

+ {lang === "ar" ? "اسم أيقونة من Lucide (مثل: StickyNote, Coffee, Users)" : "Lucide icon name (e.g. StickyNote, Coffee, Users)"} +

+
+ + {/* Color picker */} +
+ +
+ + setEditingApp({ ...editingApp, form: { ...editingApp.form, color: e.target.value } })} + className="bg-slate-50/70 border-slate-200 rounded-lg h-10 text-sm font-mono uppercase focus-visible:ring-2 focus-visible:ring-indigo-500/30 focus-visible:border-indigo-400" + dir="ltr" + placeholder="#6366F1" + /> +
+

+ {lang === "ar" ? "كود اللون السداسي (مثل #6366F1)" : "Hex color code (e.g. #6366F1)"} +

+
+
+ + {/* Permissions section in a bordered card */} +
+
+ + + {lang === "ar" ? "الأذونات" : "Permissions"} + +
+
+ {editingApp.id !== undefined ? ( + + ) : ( + + setEditingApp({ + ...editingApp, + form: { ...editingApp.form, permissionIds: next }, + }) + } + /> + )} +
+
+ + {editingApp.id !== undefined && ( + { + const p = (permissionsList ?? []).find((x) => x.id === id); + return p ? p.name : `#${id}`; + }} + /> + )} + {editingApp.id !== undefined && ( + + openAuditLogForTarget("app", editingApp.id!) + } + /> + )} +
+ + {/* Footer */} +
+ +
- )} + ); + })()} {/* Edit Service Modal */} {editingService && (