feat: complete UI/UX improvements, vuesax icons, media server, and section management
This commit is contained in:
@@ -143,6 +143,18 @@ export interface Paginated<T> {
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
export interface DashboardCategoryStat {
|
||||
id: number;
|
||||
name: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface DashboardTrendDay {
|
||||
date: string;
|
||||
dayName: string;
|
||||
messages: number;
|
||||
}
|
||||
|
||||
export interface DashboardDto {
|
||||
categories: number;
|
||||
products: number;
|
||||
@@ -151,4 +163,8 @@ export interface DashboardDto {
|
||||
media: number;
|
||||
recentMessages: ContactMessageDto[];
|
||||
recentActivity: { id: number; action: string; entity: string; summary: string; user: string | null; createdAt: string }[];
|
||||
categoryStats: DashboardCategoryStat[];
|
||||
messageBreakdown: { new: number; read: number; archived: number; total: number };
|
||||
sectionsSummary: { total: number; visible: number; hidden: number };
|
||||
trend: DashboardTrendDay[];
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export const slugSchema = z
|
||||
.max(80)
|
||||
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "فقط حروف کوچک انگلیسی، عدد و خط تیره (مثلاً safety-shoes)");
|
||||
|
||||
export const iconNameSchema = z.enum(ICON_NAMES);
|
||||
export const iconNameSchema = z.string().trim().min(1).max(80);
|
||||
|
||||
/** Image stored by URL inside section content */
|
||||
export const imageRefSchema = z.object({
|
||||
|
||||
+152
-52
@@ -1,56 +1,156 @@
|
||||
/**
|
||||
* Lucide icon names that content editors may pick in the admin panel.
|
||||
* The web app keeps a matching registry (DynamicIcon) so only these icons end up in the bundle.
|
||||
* Vuesax linear icon aliases and recommended picker list.
|
||||
*/
|
||||
export const ICON_NAMES = [
|
||||
"ShieldCheck",
|
||||
"ShieldPlus",
|
||||
"ShieldAlert",
|
||||
"BadgeCheck",
|
||||
"Award",
|
||||
"Medal",
|
||||
"PackageCheck",
|
||||
"Package",
|
||||
"Truck",
|
||||
"Warehouse",
|
||||
"Globe",
|
||||
"Users",
|
||||
"Handshake",
|
||||
"Headset",
|
||||
"ClipboardCheck",
|
||||
"FileCheck",
|
||||
"FileSearch",
|
||||
"GraduationCap",
|
||||
"Gauge",
|
||||
"Microscope",
|
||||
"Wrench",
|
||||
"HardHat",
|
||||
"Shirt",
|
||||
"Footprints",
|
||||
"Hand",
|
||||
"Glasses",
|
||||
"Ear",
|
||||
"Wind",
|
||||
"TriangleAlert",
|
||||
"Siren",
|
||||
"TrafficCone",
|
||||
"Construction",
|
||||
"Factory",
|
||||
"Building2",
|
||||
"Fuel",
|
||||
"Pickaxe",
|
||||
"Flame",
|
||||
"Zap",
|
||||
"Leaf",
|
||||
"HeartPulse",
|
||||
"Clock",
|
||||
"Phone",
|
||||
"Mail",
|
||||
"MapPin",
|
||||
"Star",
|
||||
"ThumbsUp",
|
||||
"Target",
|
||||
"Lock",
|
||||
export const ICON_ALIASES: Record<string, string> = {
|
||||
// Navigation & Actions
|
||||
ArrowLeft: "arrow-left",
|
||||
ArrowRight: "arrow-right",
|
||||
ArrowUp: "arrow-up",
|
||||
ArrowDown: "arrow-down",
|
||||
ChevronDown: "arrow-down-1",
|
||||
ChevronUp: "arrow-up-1",
|
||||
ChevronLeft: "arrow-left-1",
|
||||
ChevronRight: "arrow-right-1",
|
||||
Menu: "menu",
|
||||
X: "close-circle",
|
||||
Plus: "add",
|
||||
Trash: "trash",
|
||||
Edit: "edit-2",
|
||||
Copy: "copy",
|
||||
Check: "tick-circle",
|
||||
Search: "search-normal",
|
||||
Filter: "filter",
|
||||
Eye: "eye",
|
||||
EyeOff: "eye-slash",
|
||||
ImageOff: "gallery-slash",
|
||||
Quote: "quote-up",
|
||||
|
||||
// Contact & Social
|
||||
Phone: "call",
|
||||
Mail: "sms",
|
||||
MapPin: "location",
|
||||
Clock: "clock",
|
||||
Globe: "global",
|
||||
Send: "send-2",
|
||||
Instagram: "instagram",
|
||||
Linkedin: "link",
|
||||
MessageCircle: "messages-2",
|
||||
Youtube: "video-play",
|
||||
AtSign: "sms",
|
||||
|
||||
// Equipment & Safety Categories
|
||||
HardHat: "security-safe",
|
||||
Glasses: "glass",
|
||||
Shirt: "tag",
|
||||
Footprints: "routing-2",
|
||||
Hand: "shield-tick",
|
||||
Ear: "volume-high",
|
||||
Wind: "wind-2",
|
||||
TriangleAlert: "danger",
|
||||
Siren: "lamp-on",
|
||||
TrafficCone: "danger",
|
||||
Construction: "setting-2",
|
||||
Factory: "building-4",
|
||||
Building2: "buildings-2",
|
||||
Fuel: "gas-station",
|
||||
Pickaxe: "hierarchy",
|
||||
Flame: "flash",
|
||||
Zap: "flash-circle",
|
||||
Leaf: "tree",
|
||||
HeartPulse: "heart-tick",
|
||||
Star: "star",
|
||||
ThumbsUp: "like-1",
|
||||
Target: "radar-2",
|
||||
Lock: "lock",
|
||||
|
||||
// Trust, Certifications & Standards
|
||||
ShieldCheck: "shield-tick",
|
||||
ShieldPlus: "shield-security",
|
||||
ShieldAlert: "shield-cross",
|
||||
BadgeCheck: "verify",
|
||||
Award: "award",
|
||||
Medal: "medal-star",
|
||||
PackageCheck: "box-tick",
|
||||
Package: "box",
|
||||
Truck: "truck-time",
|
||||
Warehouse: "buildings",
|
||||
Users: "people",
|
||||
Handshake: "user-tag",
|
||||
Headset: "24-support",
|
||||
ClipboardCheck: "clipboard-tick",
|
||||
FileCheck: "document-text",
|
||||
FileSearch: "document-filter",
|
||||
GraduationCap: "teacher",
|
||||
Gauge: "status",
|
||||
Microscope: "search-status",
|
||||
Wrench: "setting-4",
|
||||
};
|
||||
|
||||
export function resolveVuesaxIcon(name: string): string {
|
||||
if (!name) return "shield-tick";
|
||||
if (name in ICON_ALIASES) return ICON_ALIASES[name];
|
||||
return name.replace(/^vuesax-linear:/, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Recommended icon names displayed in pickers and dropdowns
|
||||
*/
|
||||
export const RECOMMENDED_ICONS = [
|
||||
"shield-tick",
|
||||
"shield-security",
|
||||
"shield-cross",
|
||||
"security-safe",
|
||||
"security-user",
|
||||
"verify",
|
||||
"award",
|
||||
"medal-star",
|
||||
"box-tick",
|
||||
"box",
|
||||
"truck-time",
|
||||
"buildings",
|
||||
"building-4",
|
||||
"buildings-2",
|
||||
"global",
|
||||
"people",
|
||||
"user-tag",
|
||||
"24-support",
|
||||
"call",
|
||||
"sms",
|
||||
"location",
|
||||
"clock",
|
||||
"clipboard-tick",
|
||||
"document-text",
|
||||
"document-filter",
|
||||
"teacher",
|
||||
"status",
|
||||
"search-status",
|
||||
"setting-2",
|
||||
"setting-4",
|
||||
"glass",
|
||||
"tag",
|
||||
"routing-2",
|
||||
"volume-high",
|
||||
"wind-2",
|
||||
"danger",
|
||||
"lamp-on",
|
||||
"gas-station",
|
||||
"hierarchy",
|
||||
"flash",
|
||||
"flash-circle",
|
||||
"tree",
|
||||
"heart-tick",
|
||||
"star",
|
||||
"like-1",
|
||||
"radar-2",
|
||||
"lock",
|
||||
"eye",
|
||||
"gallery",
|
||||
"video-play",
|
||||
] as const;
|
||||
|
||||
export type IconName = (typeof ICON_NAMES)[number];
|
||||
export const ICON_NAMES = [
|
||||
...RECOMMENDED_ICONS,
|
||||
...Object.keys(ICON_ALIASES),
|
||||
] as const;
|
||||
|
||||
export type IconName = string;
|
||||
|
||||
@@ -77,6 +77,7 @@ export const settingsSectionSchema = z.object({
|
||||
});
|
||||
|
||||
export const heroSectionSchema = z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
badge: req(120),
|
||||
title: req(80),
|
||||
titleHighlight: req(80),
|
||||
@@ -91,10 +92,12 @@ export const heroSectionSchema = z.object({
|
||||
});
|
||||
|
||||
export const tapeSectionSchema = z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
items: z.array(req(80)).min(1).max(20),
|
||||
});
|
||||
|
||||
export const statsSectionSchema = z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
items: z
|
||||
.array(
|
||||
z.object({
|
||||
@@ -108,12 +111,14 @@ export const statsSectionSchema = z.object({
|
||||
});
|
||||
|
||||
export const productsIntroSectionSchema = z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
head: sectionHeadSchema,
|
||||
ctaLabel: req(60),
|
||||
trust: z.array(iconCard).max(6),
|
||||
});
|
||||
|
||||
export const aboutSectionSchema = z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
head: sectionHeadSchema,
|
||||
body: req(2000),
|
||||
image: imageRefSchema,
|
||||
@@ -123,6 +128,7 @@ export const aboutSectionSchema = z.object({
|
||||
});
|
||||
|
||||
export const servicesSectionSchema = z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
head: sectionHeadSchema,
|
||||
items: z
|
||||
.array(
|
||||
@@ -137,6 +143,7 @@ export const servicesSectionSchema = z.object({
|
||||
});
|
||||
|
||||
export const standardsSectionSchema = z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
head: sectionHeadSchema,
|
||||
note: text(500),
|
||||
ctaLabel: req(80),
|
||||
@@ -144,6 +151,7 @@ export const standardsSectionSchema = z.object({
|
||||
});
|
||||
|
||||
export const industriesSectionSchema = z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
head: sectionHeadSchema,
|
||||
items: z
|
||||
.array(
|
||||
@@ -158,11 +166,13 @@ export const industriesSectionSchema = z.object({
|
||||
});
|
||||
|
||||
export const testimonialsSectionSchema = z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
head: sectionHeadSchema,
|
||||
items: z.array(z.object({ text: req(600), name: req(80), role: text(160) })).max(12),
|
||||
});
|
||||
|
||||
export const contactSectionSchema = z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
head: sectionHeadSchema,
|
||||
emergencyTitle: req(80),
|
||||
emergencyText: req(400),
|
||||
@@ -173,6 +183,7 @@ export const contactSectionSchema = z.object({
|
||||
});
|
||||
|
||||
export const footerSectionSchema = z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
about: req(500),
|
||||
copyright: req(160),
|
||||
certLine: text(160),
|
||||
|
||||
Reference in New Issue
Block a user