From ba57eb7e0318e07a0692bcd24d6c30670f6b839c Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Thu, 12 Feb 2026 22:28:39 +0700 Subject: [PATCH] feat: Introduce new marketing module with dedicated layout, styling, and pages for AI content, live chat, and analytics. --- .../Layout/MarketingLayout.razor | 117 ++++ .../Pages/Marketing/AiChatbot.razor | 210 ++++++ .../Pages/Marketing/AiContentStudio.razor | 176 +++++ .../Pages/Marketing/AnalyticsDashboard.razor | 180 +++++ .../Pages/Marketing/ChatbotAutomation.razor | 145 ++++ .../Pages/Marketing/CustomerCrm.razor | 173 +++++ .../Pages/Marketing/LivechatConsole.razor | 192 ++++++ .../Pages/Marketing/MarketingBase.cs | 64 ++ .../Pages/Marketing/SocialHub.razor | 162 +++++ .../wwwroot/css/marketing.css | 633 ++++++++++++++++++ .../WebClientTpos.Client/wwwroot/index.html | 1 + 11 files changed, 2053 insertions(+) create mode 100644 apps/web-client-tpos-net/src/WebClientTpos.Client/Layout/MarketingLayout.razor create mode 100644 apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/AiChatbot.razor create mode 100644 apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/AiContentStudio.razor create mode 100644 apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/AnalyticsDashboard.razor create mode 100644 apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/ChatbotAutomation.razor create mode 100644 apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/CustomerCrm.razor create mode 100644 apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/LivechatConsole.razor create mode 100644 apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/MarketingBase.cs create mode 100644 apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/SocialHub.razor create mode 100644 apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/css/marketing.css diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Layout/MarketingLayout.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Layout/MarketingLayout.razor new file mode 100644 index 00000000..58d1812a --- /dev/null +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Layout/MarketingLayout.razor @@ -0,0 +1,117 @@ +@* + EN: Marketing layout — Industrial sidebar + TopBar + Content area. + VI: Layout Marketing — Sidebar công nghiệp + TopBar + Vùng nội dung. + Design: pencil-design/src/pages/tMarketing/desktop/ +*@ +@inherits LayoutComponentBase +@inject NavigationManager NavigationManager +@inject IJSRuntime JS + + + + + + +
+ @* ═══ SIDEBAR / THANH ĐIỀU HƯỚNG ═══ *@ + + + @* Mobile overlay *@ + @if (_sidebarOpen) + { +
+ } + + @* ═══ MAIN AREA / VÙNG NỘI DUNG ═══ *@ +
+ @Body +
+
+ +@code { + private bool _sidebarOpen = false; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + // EN: Re-init Lucide icons after every render (Blazor navigation replaces DOM) + // VI: Khởi tạo lại Lucide icons sau mỗi lần render + try { await JS.InvokeVoidAsync("lucide.createIcons"); } catch { } + } + + private void ToggleSidebar() => _sidebarOpen = !_sidebarOpen; + private void CloseSidebar() => _sidebarOpen = false; + private void Logout() => NavigationManager.NavigateTo("/login"); + + private MudTheme _theme = new() + { + PaletteDark = new PaletteDark() + { + Primary = "#FACC15", + PrimaryContrastText = "#18181B", + AppbarBackground = "#0F0F10", + AppbarText = "#FAFAFA", + Background = "#18181B", + Surface = "#0F0F10", + TextPrimary = "#FAFAFA", + TextSecondary = "#71717A", + ActionDefault = "#FAFAFA", + LinesDefault = "#27272A" + } + }; +} diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/AiChatbot.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/AiChatbot.razor new file mode 100644 index 00000000..54564670 --- /dev/null +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/AiChatbot.razor @@ -0,0 +1,210 @@ +@page "/marketing/ai-chatbot" +@layout MarketingLayout +@inherits MarketingBase + +@* + EN: AI Chatbot — Configure and manage AI-powered chatbot scenarios. + VI: AI Chatbot — Cấu hình và quản lý kịch bản chatbot AI. + Design: pencil-design/src/pages/tMarketing/desktop/ai-chatbot.pen +*@ + +AI Chatbot — tMarketing + +
+ @* ═══ LEFT: Scenario List ═══ *@ +
+
+
+ + SCENARIOS +
+
+ +
+
+ +
+ @foreach (var scenario in _scenarios) + { +
+
+ @scenario.Name + @scenario.Status +
+ @scenario.Description +
+ @if (!string.IsNullOrEmpty(scenario.Stat1)) + { + @scenario.Stat1 + } + @if (!string.IsNullOrEmpty(scenario.Stat2)) + { + @scenario.Stat2 + } +
+
+ } +
+
+ + @* ═══ CENTER: Scenario Detail ═══ *@ +
+ @* Header *@ +
+
+ Product Consultant + Tư vấn sản phẩm tự động với AI +
+
+ + +
+
+ + @* Scenario content *@ +
+ @* Training Data Panel *@ +
+
+

+ + TRAINING DATA +

+ +
+
+ @foreach (var ds in _dataSources) + { +
+ +
+ @ds.Name + @ds.Size • @ds.Updated +
+ SYNCED +
+ } +
+
+ + @* Performance Metrics *@ +
+
+

+ + PERFORMANCE +

+
+
+ @foreach (var metric in _metrics) + { +
+
+ @metric.Label + @metric.Value +
+
+
+
+
+ } +
+
+
+
+ + @* ═══ RIGHT: Test Chat ═══ *@ +
+
+ + TEST CHAT +
+ + @* Chat messages *@ +
+ @* User message *@ +
+
+

Cho hỏi về sản phẩm coffee đặc biệt

+
+
+ + @* AI response *@ +
+
+ +
+
+

Xin chào! 🤖 Chúng tôi có các dòng coffee đặc biệt:

+

1. Specialty Blend — 89K
2. Single Origin — 120K
3. Cold Brew — 75K

+
+
+ + @* User message *@ +
+
+

Specialty Blend có gì đặc biệt?

+
+
+ + @* AI response *@ +
+
+ +
+
+

Specialty Blend là hỗn hợp từ 3 nguồn Arabica chọn lọc, rang medium để giữ hương vị phong phú. Notes: chocolate, caramel, citrus 🍊

+
+
+
+ + @* Input *@ +
+
+ +
+
+ +
+
+
+
+ +@code { + // EN: Demo scenario data / VI: Dữ liệu kịch bản demo + private record ScenarioInfo(string Name, string Status, string BadgeClass, string Description, string Stat1, string Stat2, bool IsSelected); + private record DataSource(string Name, string Icon, string Color, string Size, string Updated); + private record MetricInfo(string Label, string Value, string FillPct, string Color); + + private readonly ScenarioInfo[] _scenarios = new[] + { + new ScenarioInfo("Product Consultant", "ACTIVE", "mkt-badge--active", "Tư vấn sản phẩm tự động với AI", "2.4K convos", "92% success", true), + new ScenarioInfo("FAQ Handler", "ACTIVE", "mkt-badge--active", "Trả lời câu hỏi thường gặp", "5.1K convos", "88% success", false), + new ScenarioInfo("Lead Generator", "TRAINING", "mkt-badge--training", "Thu thập thông tin khách hàng", "342 test runs", "", false), + new ScenarioInfo("Promo Campaign", "DRAFT", "mkt-badge--draft", "Giới thiệu khuyến mãi mới", "", "", false), + }; + + private readonly DataSource[] _dataSources = new[] + { + new DataSource("Product Catalog", "package", "#FACC15", "2.4 MB", "Updated 2h ago"), + new DataSource("FAQ Database", "help-circle", "#3B82F6", "892 KB", "Updated 1d ago"), + new DataSource("Price List", "tag", "#22C55E", "156 KB", "Updated 6h ago"), + }; + + private readonly MetricInfo[] _metrics = new[] + { + new MetricInfo("Success Rate", "92%", "92%", "#22C55E"), + new MetricInfo("Avg Response Time", "1.2s", "75%", "#3B82F6"), + new MetricInfo("Customer Satisfaction", "4.5/5", "90%", "#FACC15"), + new MetricInfo("Handoff Rate", "8%", "8%", "#EF4444"), + }; +} diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/AiContentStudio.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/AiContentStudio.razor new file mode 100644 index 00000000..0d3ee402 --- /dev/null +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/AiContentStudio.razor @@ -0,0 +1,176 @@ +@page "/marketing/content" +@layout MarketingLayout +@inherits MarketingBase + +@* + EN: AI Content Studio — Generate and schedule content with Google Gemini AI. + VI: AI Content Studio — Tạo và lên lịch nội dung với Google Gemini AI. + Design: pencil-design/src/pages/tMarketing/desktop/ai-content-studio.pen +*@ + +AI Content Studio — tMarketing + +@* ═══ TOP BAR ═══ *@ +
+
+

AI CONTENT STUDIO

+

Generate content with Google Gemini AI

+
+
+ + +
+
+ +@* ═══ CONTENT ═══ *@ +
+ @* ── LEFT: Calendar + Upcoming ── *@ +
+ @* Content Calendar *@ +
+
+

+ + CONTENT CALENDAR +

+ FEBRUARY 2026 +
+
+ @* Calendar grid *@ +
+ @* Day headers *@ + @foreach (var day in new[] { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }) + { +
@day
+ } + @* Calendar days *@ + @for (int i = 0; i < 35; i++) + { + var dayNum = i - 0 + 1; // Starting from day 1 + if (dayNum < 1 || dayNum > 28) + { +
+ } + else + { + var hasEvent = _calendarEvents.ContainsKey(dayNum); +
+ @dayNum + @if (hasEvent) + { +
@_calendarEvents[dayNum]
+ } +
+ } + } +
+
+
+ + @* Upcoming Posts *@ +
+
+

+ + UPCOMING POSTS +

+
+
+ @foreach (var post in _upcomingPosts) + { +
+
+ @post.Day + @post.Month +
+
+ @post.Title + @post.Channels +
+ @post.Time +
+ } +
+
+
+ + @* ── RIGHT: AI Generation Panel ── *@ +
+ @* Header *@ +
+ + AI GENERATION +
+ + @* Form *@ +
+
+ +
+ +
+
+ +
+ +
+ + + +
+
+ +
+ +
+ + + +
+
+ + @* Preview *@ +
+ +
+ AI-generated content will appear here... + Powered by Google Gemini +
+
+
+ + @* Generate button *@ +
+ +
+
+
+ +@code { + // EN: Demo calendar data / VI: Dữ liệu lịch demo + private record UpcomingPost(string Day, string Month, string Title, string Channels, string Time); + + private readonly Dictionary _calendarEvents = new() + { + { 6, "FB Post" }, + { 8, "IG Reel" }, + { 10, "AI Banner" }, + { 14, "Valentine" }, + }; + + private readonly UpcomingPost[] _upcomingPosts = new[] + { + new UpcomingPost("06", "FEB", "Spring Collection Announcement", "FB • IG", "10:00 AM"), + new UpcomingPost("10", "FEB", "AI Generated Banner", "GEMINI", "2:00 PM"), + new UpcomingPost("14", "FEB", "Valentine's Day Special Campaign", "FB • IG • ZALO", "9:00 AM"), + }; +} diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/AnalyticsDashboard.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/AnalyticsDashboard.razor new file mode 100644 index 00000000..771acc00 --- /dev/null +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/AnalyticsDashboard.razor @@ -0,0 +1,180 @@ +@page "/marketing/analytics" +@layout MarketingLayout +@inherits MarketingBase + +@* + EN: Analytics Dashboard — AI-powered insights and performance metrics. + VI: Analytics Dashboard — Phân tích thông minh và chỉ số hiệu suất. + Design: pencil-design/src/pages/tMarketing/desktop/analytics-dashboard.pen +*@ + +Analytics — tMarketing + +@* ═══ TOP BAR ═══ *@ +
+
+

ANALYTICS

+

AI-powered insights and performance metrics

+
+
+ + +
+
+ +@* ═══ CONTENT ═══ *@ +
+ @* ── KPI Row ── *@ +
+ @foreach (var kpi in _kpis) + { +
+
+
+ +
+ @kpi.Label +
+ @kpi.Value +
+ + @kpi.Change +
+
+ } +
+ + @* ── ROW 2: Engagement Chart + Top Content ── *@ +
+ @* Engagement Chart (placeholder) *@ +
+
+

+ + ENGAGEMENT OVERVIEW +

+
+ WEEK + MONTH + YEAR +
+
+
+ @* Mini chart bars *@ + @foreach (var bar in _chartBars) + { +
+
+ @bar.Day +
+ } +
+
+ + @* Top Content *@ +
+
+

+ + TOP CONTENT +

+
+
+ @foreach (var content in _topContent) + { +
+
+ @content.Title + @content.Channel • @content.Time +
+
+ @content.Reach + @content.Engagement +
+
+ } +
+
+
+ + @* ── ROW 3: Channel Performance ── *@ +
+
+

+ + CHANNEL PERFORMANCE +

+
+
+
+ @foreach (var channel in _channelPerf) + { +
+
+
+ @channel.Name +
+
+
+
Reach
+
@channel.Reach
+
+
+
Engagement
+
@channel.Engagement
+
+
+ @* Progress bar *@ +
+
+
+
+ } +
+
+
+
+ +@code { + // EN: Demo analytics data / VI: Dữ liệu phân tích demo + private record KpiData(string Label, string Value, string Change, string Icon, string IconColor, string IconBg); + private record ChartBar(string Day, int Height); + private record TopContentItem(string Title, string Channel, string Time, string Reach, string Engagement); + private record ChannelPerf(string Name, string Color, string Reach, string Engagement, string FillPct); + + private readonly KpiData[] _kpis = new[] + { + new KpiData("TOTAL REACH", "245.8K", "+18.5%", "eye", "#22C55E", "rgba(34,197,94,0.12)"), + new KpiData("ENGAGEMENT", "14.2%", "+5.3%", "heart", "#EC4899", "rgba(236,72,153,0.12)"), + new KpiData("CONVERSIONS", "1,842", "+22.1%", "target", "#3B82F6", "rgba(59,130,246,0.12)"), + new KpiData("ROI", "324%", "+45.2%", "trending-up", "#FACC15", "rgba(250,204,21,0.12)"), + }; + + private readonly ChartBar[] _chartBars = new[] + { + new ChartBar("Mon", 80), new ChartBar("Tue", 120), new ChartBar("Wed", 95), + new ChartBar("Thu", 150), new ChartBar("Fri", 130), new ChartBar("Sat", 170), new ChartBar("Sun", 110), + }; + + private readonly TopContentItem[] _topContent = new[] + { + new TopContentItem("Spring Collection Ad", "FB", "2 days ago", "45.2K", "+24%"), + new TopContentItem("Behind the Scenes", "IG", "3 days ago", "32.1K", "+18%"), + new TopContentItem("Promo Announcement", "ZALO", "5 days ago", "28.4K", "+15%"), + }; + + private readonly ChannelPerf[] _channelPerf = new[] + { + new ChannelPerf("Facebook", "#3B82F6", "125K", "12.4%", "75%"), + new ChannelPerf("Instagram", "#E1306C", "85K", "18.2%", "65%"), + new ChannelPerf("Zalo OA", "#0068FF", "45K", "22.1%", "55%"), + new ChannelPerf("WhatsApp", "#25D366", "15K", "8.5%", "35%"), + }; +} diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/ChatbotAutomation.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/ChatbotAutomation.razor new file mode 100644 index 00000000..1653b0f5 --- /dev/null +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/ChatbotAutomation.razor @@ -0,0 +1,145 @@ +@page "/marketing/chatbot" +@layout MarketingLayout +@inherits MarketingBase + +@* + EN: Chatbot Automation — Build automation flows for customer engagement. + VI: Chatbot Automation — Xây dựng luồng tự động hóa tương tác khách hàng. + Design: pencil-design/src/pages/tMarketing/desktop/chatbot-automation.pen +*@ + +Chatbot Automation — tMarketing + +
+ @* ═══ LEFT: Flow List ═══ *@ +
+
+
+ + FLOWS +
+
+ +
+
+ +
+ @foreach (var flow in _flows) + { +
+
+ @flow.Name +
+ @flow.Description +
+ @flow.Stat1 + @flow.Stat2 +
+
+ } +
+
+ + @* ═══ CENTER: Flow Builder Canvas ═══ *@ +
+ @* Header *@ +
+
+ Welcome Message + Greet new customers automatically +
+
+ + +
+
+ + @* Flow canvas *@ +
+ @* Trigger Node *@ +
+
+ + TRIGGER +
+ New Customer Message + When customer sends first message +
+ + @* Connector *@ +
+ + @* Action Node *@ +
+
+ + ACTION +
+ Send Welcome Reply + Auto-reply with greeting template +
+ + @* Connector *@ +
+ + @* Condition Node *@ +
+
+ + CONDITION +
+ Check Customer Type + Route VIP vs standard customers +
+
+
+ + @* ═══ RIGHT: Node Palette ═══ *@ +
+
+ + ADD NODE +
+
+ @foreach (var node in _nodeTypes) + { +
+ +
+ @node.Name + @node.Desc +
+
+ } +
+
+
+ +@code { + // EN: Demo flow data / VI: Dữ liệu luồng demo + private record FlowInfo(string Name, string Description, string Stat1, string Stat2, bool IsActive); + private record NodeType(string Name, string Desc, string Icon, string Color); + + private readonly FlowInfo[] _flows = new[] + { + new FlowInfo("Welcome Message", "Greet new customers", "1.2K sent", "85% open", true), + new FlowInfo("Product Inquiry", "Handle product questions", "890 sent", "72% open", false), + new FlowInfo("Abandoned Cart", "Recover lost sales", "456 sent", "28% conv", false), + new FlowInfo("Order Update", "Shipping notifications", "Draft", "", false), + }; + + private readonly NodeType[] _nodeTypes = new[] + { + new NodeType("Trigger", "Start automation", "zap", "#22C55E"), + new NodeType("Message", "Send a message", "message-circle", "#3B82F6"), + new NodeType("Condition", "Branch logic", "git-branch", "#FACC15"), + new NodeType("Delay", "Wait before next", "clock", "#8B5CF6"), + new NodeType("AI Response", "Gemini auto-reply", "sparkles", "#EC4899"), + }; +} diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/CustomerCrm.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/CustomerCrm.razor new file mode 100644 index 00000000..90ab85b1 --- /dev/null +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/CustomerCrm.razor @@ -0,0 +1,173 @@ +@page "/marketing/customers" +@layout MarketingLayout +@inherits MarketingBase + +@* + EN: Customer CRM — Manage customer profiles for re-marketing campaigns. + VI: Customer CRM — Quản lý hồ sơ khách hàng cho chiến dịch re-marketing. + Design: pencil-design/src/pages/tMarketing/desktop/customer-crm.pen +*@ + +Customers — tMarketing + +@* ═══ TOP BAR ═══ *@ +
+
+

CUSTOMERS

+

Manage customer profiles for re-marketing campaigns

+
+
+ + + +
+
+ +@* ═══ CONTENT ═══ *@ +
+ @* ── KPI Row ── *@ +
+
+ TOTAL CUSTOMERS +
+ 12,456 +
+ + +8.2% +
+
+
+
+ ACTIVE +
+ 4,892 +
+ + +12.1% +
+
+
+
+ NEW TODAY +
+ +342 + NEW +
+
+
+ SEGMENTS +
+ 8 + GROUPS +
+
+
+ + @* ── Customer Table ── *@ +
+
+

+ + CUSTOMER LIST +

+
+ + + +
+
+
+ + + + + + + + + + + + + @foreach (var customer in _customers) + { + + + + + + + + + } + +
NAMEPHONECHANNELSCOREACTIONS
+
@customer.Initials
+
@customer.Name@customer.Phone@customer.Channel@customer.Score +
+ + +
+
+
+
+ + @* ── Bottom Actions + Pagination ── *@ +
+
+ + + + +
+
+
01
+
02
+
03
+
+
+
+ +@code { + // EN: Demo customer data / VI: Dữ liệu khách hàng demo + private record CustomerInfo(string Name, string Initials, string Phone, string Channel, string Score, string AvatarBg); + + private readonly CustomerInfo[] _customers = new[] + { + new CustomerInfo("Nguyễn Văn A", "NA", "0912 xxx xxx", "ZALO", "★★★★☆", "#3B82F6"), + new CustomerInfo("Trần Thị B", "TB", "0987 xxx xxx", "FB", "★★★☆☆", "#8B5CF6"), + new CustomerInfo("Lê Văn C", "LC", "0909 xxx xxx", "IG", "★★★★★", "#EC4899"), + new CustomerInfo("Phạm Thị D", "PD", "0888 xxx xxx", "WA", "★★☆☆☆", "#22C55E"), + }; +} diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/LivechatConsole.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/LivechatConsole.razor new file mode 100644 index 00000000..672a8959 --- /dev/null +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/LivechatConsole.razor @@ -0,0 +1,192 @@ +@page "/marketing/livechat" +@layout MarketingLayout +@inherits MarketingBase + +@* + EN: Livechat Console — Real-time customer chat with 3-panel layout. + VI: Livechat Console — Chat khách hàng thời gian thực với bố cục 3 panel. + Design: pencil-design/src/pages/tMarketing/desktop/livechat-console.pen +*@ + +Livechat — tMarketing + +
+ @* ═══ LEFT: Conversation List ═══ *@ +
+ @* Header *@ +
+ CONVERSATIONS +
+ ALL + UNREAD +
+
+ + @* Conversation items *@ + @foreach (var conv in _conversations) + { +
+
+ @conv.Initials +
+
+
+ @conv.Name + @conv.Time +
+ @conv.LastMessage +
+ @conv.Channel + @if (conv.Unread > 0) + { + @conv.Unread + } +
+
+
+ } +
+ + @* ═══ CENTER: Chat Area ═══ *@ +
+ @* Chat header *@ +
+
NA
+
+ Nguyễn Văn A + ● Online +
+
+ + +
+
+ + @* Chat messages *@ +
+ @* Customer message *@ +
+
+

Xin chào, tôi muốn hỏi về sản phẩm mới ạ 🤗

+ 10:30 AM +
+
+ + @* Agent reply *@ +
+
+

Chào bạn! 👋 Cảm ơn bạn đã liên hệ. Bạn quan tâm đến sản phẩm nào ạ?

+ 10:31 AM +
+
+ + @* Customer message *@ +
+
+

Tôi muốn hỏi về sản phẩm coffee đặc biệt, giá bao nhiêu vậy shop?

+ 10:32 AM +
+
+ + @* Typing indicator *@ +
+
+
+
+ Customer is typing... +
+
+ + @* Quick replies *@ +
+ QUICK: + @foreach (var reply in _quickReplies) + { + + } +
+ + @* Message input *@ +
+ +
+ +
+ +
+
+ + @* ═══ RIGHT: Customer Info ═══ *@ +
+
+
NA
+
Nguyễn Văn A
+
khách hàng VIP
+
+ +
+
+ PHONE + 0912 xxx xxx +
+
+ CHANNEL + ZALO +
+
+ SCORE + ★★★★☆ +
+
+ ORDERS + 12 +
+
+ TOTAL SPENT + 2.4M +
+
+ +
+ TAGS +
+ VIP + COFFEE LOVER + LOYAL +
+
+
+
+ +@code { + // EN: Demo conversation data / VI: Dữ liệu hội thoại demo + private record ConversationInfo(string Name, string Initials, string LastMessage, string Time, string Channel, string ChannelColor, string ChannelBg, string AvatarBg, int Unread, bool IsActive); + + private ConversationInfo _selectedConversation = null!; + + private readonly string[] _quickReplies = new[] + { + "Xin chào!", "Cảm ơn", "Đang kiểm tra", "Liên hệ CSKH" + }; + + private readonly ConversationInfo[] _conversations = new[] + { + new ConversationInfo("Nguyễn Văn A", "NA", "Tôi muốn hỏi về sản phẩm...", "10:32", "ZALO", "#0068FF", "rgba(0,104,255,0.12)", "#3B82F6", 2, true), + new ConversationInfo("Trần Thị B", "TB", "Giá bao nhiêu vậy shop?", "10:15", "FB", "#3B82F6", "rgba(59,130,246,0.12)", "#8B5CF6", 1, false), + new ConversationInfo("Lê Văn C", "LC", "Cảm ơn shop nhé!", "09:45", "IG", "#E1306C", "rgba(225,48,108,0.12)", "#EC4899", 0, false), + new ConversationInfo("David Chen", "DC", "Hi, I want to order...", "09:20", "WA", "#25D366", "rgba(37,211,102,0.12)", "#22C55E", 0, false), + }; + + private void SelectConversation(ConversationInfo conv) + { + _selectedConversation = conv; + } +} diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/MarketingBase.cs b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/MarketingBase.cs new file mode 100644 index 00000000..6c101132 --- /dev/null +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/MarketingBase.cs @@ -0,0 +1,64 @@ +using Microsoft.AspNetCore.Components; + +namespace WebClientTpos.Client.Pages.Marketing; + +/// +/// EN: Base class for all Marketing pages — provides navigation, formatting helpers. +/// VI: Lớp cơ sở cho tất cả trang Marketing — cung cấp điều hướng, định dạng số liệu. +/// +public abstract class MarketingBase : ComponentBase +{ + [Inject] protected NavigationManager NavigationManager { get; set; } = default!; + + /// + /// EN: Page title shown in topbar. / VI: Tiêu đề trang. + /// + protected string PageTitle { get; set; } = string.Empty; + + /// + /// EN: Page subtitle. / VI: Phụ đề trang. + /// + protected string PageSubtitle { get; set; } = string.Empty; + + /// + /// EN: Search query. / VI: Truy vấn tìm kiếm. + /// + protected string SearchQuery { get; set; } = string.Empty; + + /// + /// EN: Navigate to a marketing sub-page. + /// VI: Điều hướng đến trang con marketing. + /// + protected void NavigateTo(string path) + { + NavigationManager.NavigateTo($"/marketing/{path}"); + } + + /// + /// EN: Format large numbers (1.2K, 45.2K, 1.5M). + /// VI: Định dạng số lớn. + /// + protected static string FormatNumber(double value) + { + if (value >= 1_000_000) return $"{value / 1_000_000:0.#}M"; + if (value >= 1_000) return $"{value / 1_000:0.#}K"; + return value.ToString("N0"); + } + + /// + /// EN: Format percentage with sign. / VI: Định dạng phần trăm có dấu. + /// + protected static string FormatPercent(double value) + { + var sign = value >= 0 ? "+" : ""; + return $"{sign}{value:0.#}%"; + } + + /// + /// EN: Get today formatted. / VI: Ngày hôm nay. + /// + protected static string GetTodayFormatted() + { + return DateTime.Now.ToString("dd/MM/yyyy"); + } +} diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/SocialHub.razor b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/SocialHub.razor new file mode 100644 index 00000000..925084b1 --- /dev/null +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Marketing/SocialHub.razor @@ -0,0 +1,162 @@ +@page "/marketing" +@layout MarketingLayout +@inherits MarketingBase + +@* + EN: Social Hub — Manage all social media channels in one place. + VI: Social Hub — Quản lý tất cả kênh mạng xã hội tại một nơi. + Design: pencil-design/src/pages/tMarketing/desktop/social-hub.pen +*@ + +Social Hub — tMarketing + +@* ═══ TOP BAR ═══ *@ +
+
+

SOCIAL HUB

+

Manage all your social media channels in one place

+
+
+ + +
+
+ +@* ═══ CONTENT ═══ *@ +
+ @* ── ROW 1: Platform Status + Quick Actions ── *@ +
+ @* Platform Status *@ +
+
+

+ + PLATFORM STATUS +

+
+
+ @foreach (var platform in _platforms) + { +
+
+
+ @platform.Name +
+ + @(platform.IsConnected ? "CONNECTED" : "DISCONNECTED") + + @platform.Followers followers +
+ } +
+
+ + @* Quick Actions *@ +
+
+

+ + QUICK ACTIONS +

+
+
+ @foreach (var action in _quickActions) + { +
+ + @action.Label +
+ } +
+
+
+ + @* ── ROW 2: Unified Feed ── *@ +
+
+

+ + UNIFIED FEED +

+
+ ALL + SCHEDULED +
+
+
+ @foreach (var post in _feedPosts) + { +
+
+
+ @post.Content + @post.Time • @post.Engagement +
+ @post.Channel +
+ } +
+
+ + @* ── ROW 3: KPI Stats ── *@ +
+ @foreach (var kpi in _kpis) + { +
+ @kpi.Label + @kpi.Value +
+ + @kpi.Change +
+
+ } +
+
+ +@code { + // EN: Demo platform data / VI: Dữ liệu nền tảng demo + private record PlatformInfo(string Name, string Color, string StatusColor, bool IsConnected, string Followers); + private record ActionInfo(string Icon, string Label); + private record FeedPost(string Content, string Time, string Engagement, string Channel, string ChannelColor); + private record KpiInfo(string Label, string Value, string Change); + + private readonly PlatformInfo[] _platforms = new[] + { + new PlatformInfo("Facebook", "#3B82F6", "#22C55E", true, "12.5K"), + new PlatformInfo("Instagram", "#E1306C", "#22C55E", true, "8.2K"), + new PlatformInfo("Zalo OA", "#0068FF", "#22C55E", true, "15.1K"), + new PlatformInfo("WhatsApp", "#25D366", "#22C55E", true, "3.4K"), + new PlatformInfo("TikTok", "#FF0050", "#EF4444", false, "—"), + }; + + private readonly ActionInfo[] _quickActions = new[] + { + new ActionInfo("edit-3", "Create New Post"), + new ActionInfo("calendar", "Schedule Content"), + new ActionInfo("bar-chart-2", "View Analytics"), + new ActionInfo("settings", "Channel Settings"), + }; + + private readonly FeedPost[] _feedPosts = new[] + { + new FeedPost("🎉 Spring Collection ra mắt — Giảm 20% tất cả sản phẩm mới!", "2 giờ trước", "234 likes • 45 comments", "FB", "#3B82F6"), + new FeedPost("Behind the scenes — Quy trình sản xuất coffee đặc biệt", "4 giờ trước", "1.2K views • 89 likes", "IG", "#E1306C"), + new FeedPost("Thông báo: Chương trình khách hàng thân thiết mới", "6 giờ trước", "567 reads", "ZALO", "#0068FF"), + new FeedPost("Try our new seasonal drinks! 🍵 Limited time only", "8 giờ trước", "89 likes • 12 shares", "WA", "#25D366"), + }; + + private readonly KpiInfo[] _kpis = new[] + { + new KpiInfo("TOTAL REACH", "45.2K", "12.5%"), + new KpiInfo("ENGAGEMENT", "12.8%", "8.2%"), + new KpiInfo("NEW FOLLOWERS", "+1.2K", "24.1%"), + new KpiInfo("MESSAGES", "234", "15.3%"), + }; +} diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/css/marketing.css b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/css/marketing.css new file mode 100644 index 00000000..61319861 --- /dev/null +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/css/marketing.css @@ -0,0 +1,633 @@ +/* ═══════════════════════════════════════════════════════════════ + tMarketing — CSS Design System + EN: Industrial dark theme with yellow accent for marketing module. + VI: Theme công nghiệp tối với điểm nhấn vàng cho module marketing. + Design source: pencil-design/src/pages/tMarketing/desktop/ + ═══════════════════════════════════════════════════════════════ */ + +/* ── DESIGN TOKENS ── */ +:root { + --mkt-bg-page: #18181B; + --mkt-bg-card: #0F0F10; + --mkt-bg-elevated: #1C1C1F; + --mkt-yellow-primary: #FACC15; + --mkt-yellow-light: #FDE047; + --mkt-yellow-dim: rgba(250, 204, 21, 0.08); + --mkt-yellow-glow: rgba(250, 204, 21, 0.15); + --mkt-text-primary: #FAFAFA; + --mkt-text-secondary: #71717A; + --mkt-text-tertiary: #52525B; + --mkt-border: #27272A; + --mkt-border-light: #3F3F46; + --mkt-success: #22C55E; + --mkt-warning: #FACC15; + --mkt-error: #EF4444; + --mkt-info: #3B82F6; + --mkt-purple: #8B5CF6; + --mkt-pink: #EC4899; + --mkt-sidebar-w: 240px; + --mkt-font: 'Roboto', sans-serif; +} + +/* ── LAYOUT ── */ +.mkt-layout { + display: flex; + min-height: 100vh; + background: var(--mkt-bg-page); + color: var(--mkt-text-primary); + font-family: var(--mkt-font); +} + +.mkt-main { + flex: 1; + display: flex; + flex-direction: column; + overflow-y: auto; + background: var(--mkt-bg-page); +} + +/* ── SIDEBAR ── */ +.mkt-sidebar { + width: var(--mkt-sidebar-w); + background: var(--mkt-bg-card); + display: flex; + flex-direction: column; + border-right: 1px solid var(--mkt-border); + flex-shrink: 0; + overflow-y: auto; +} + +.mkt-sidebar__logo { + display: flex; + align-items: center; + gap: 8px; + padding: 20px 16px; +} + +.mkt-sidebar__logo-icon { + width: 32px; + height: 32px; + background: var(--mkt-yellow-primary); + color: var(--mkt-bg-card); + border-radius: 6px; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + font-weight: 700; +} + +.mkt-sidebar__logo-text { + font-size: 14px; + font-weight: 700; + letter-spacing: 1px; + color: var(--mkt-text-primary); +} + +/* Navigation items */ +.mkt-sidebar__nav { + display: flex; + flex-direction: column; + gap: 2px; + padding: 0 12px; + flex: 1; +} + +.mkt-nav-item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 12px; + border-radius: 6px; + font-size: 12px; + font-weight: 500; + color: var(--mkt-text-secondary); + text-decoration: none; + transition: all 0.2s ease; + letter-spacing: 0.5px; +} + +.mkt-nav-item:hover { + color: var(--mkt-text-primary); + background: var(--mkt-yellow-dim); +} + +.mkt-nav-item--active { + color: var(--mkt-text-primary) !important; + background: var(--mkt-yellow-glow) !important; +} + +.mkt-nav-item--active .mkt-nav-num { + color: var(--mkt-yellow-primary) !important; +} + +.mkt-nav-num { + font-size: 12px; + font-weight: 600; + color: var(--mkt-text-tertiary); + min-width: 20px; +} + +/* Sidebar stats card */ +.mkt-sidebar__stats { + margin: 16px 12px; + padding: 16px; + background: rgba(250, 204, 21, 0.06); + border-radius: 10px; + display: flex; + flex-direction: column; + gap: 12px; +} + +.mkt-sidebar__stats-header { + display: flex; + align-items: center; + gap: 8px; + font-size: 11px; + font-weight: 600; + color: var(--mkt-yellow-primary); + letter-spacing: 0.5px; +} + +.mkt-sidebar__stats-header i { + width: 14px; + height: 14px; +} + +.mkt-sidebar__stats-value { + font-size: 28px; + font-weight: 700; + color: var(--mkt-text-primary); +} + +/* Sidebar user */ +.mkt-sidebar__user { + display: flex; + align-items: center; + gap: 10px; + padding: 16px; + border-top: 1px solid var(--mkt-border); +} + +.mkt-user-avatar { + width: 28px; + height: 28px; + border-radius: 6px; + background: var(--mkt-border); + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + font-weight: 700; + color: var(--mkt-text-primary); +} + +.mkt-user-name { + flex: 1; + font-size: 12px; + font-weight: 500; + color: var(--mkt-text-primary); + letter-spacing: 0.5px; +} + +.mkt-sidebar__user button { + background: none; + border: none; + color: var(--mkt-text-tertiary); + cursor: pointer; + padding: 4px; +} + +.mkt-sidebar__user button i { + width: 14px; + height: 14px; +} + +/* ── TOPBAR ── */ +.mkt-topbar { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 28px; + flex-shrink: 0; +} + +.mkt-topbar__left { + display: flex; + flex-direction: column; + gap: 4px; +} + +.mkt-topbar__title { + font-size: 22px; + font-weight: 700; + color: var(--mkt-text-primary); + margin: 0; + letter-spacing: 1px; + text-transform: uppercase; +} + +.mkt-topbar__subtitle { + font-size: 13px; + color: var(--mkt-text-secondary); + margin: 0; +} + +.mkt-topbar__right { + display: flex; + align-items: center; + gap: 10px; +} + +/* ── BUTTONS ── */ +.mkt-btn-primary { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 16px; + background: var(--mkt-yellow-primary); + color: var(--mkt-bg-card); + border: none; + border-radius: 6px; + font-size: 12px; + font-weight: 600; + cursor: pointer; + letter-spacing: 0.5px; + transition: background 0.2s; +} + +.mkt-btn-primary:hover { + background: var(--mkt-yellow-light); +} + +.mkt-btn-primary i { + width: 14px; + height: 14px; +} + +.mkt-btn-ghost { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 12px; + background: transparent; + color: var(--mkt-text-secondary); + border: 1px solid var(--mkt-border); + border-radius: 6px; + font-size: 12px; + font-weight: 500; + cursor: pointer; + letter-spacing: 0.5px; + transition: all 0.2s; +} + +.mkt-btn-ghost:hover { + color: var(--mkt-text-primary); + border-color: var(--mkt-border-light); +} + +.mkt-btn-ghost i { + width: 14px; + height: 14px; +} + +/* ── CONTENT AREA ── */ +.mkt-content { + display: flex; + flex-direction: column; + gap: 24px; + padding: 0 28px 28px; + flex: 1; +} + +/* ── KPI CARDS ── */ +.mkt-kpi-row { + display: flex; + gap: 16px; +} + +.mkt-kpi-card { + flex: 1; + background: var(--mkt-bg-card); + border-radius: 10px; + padding: 16px; + display: flex; + flex-direction: column; + gap: 8px; +} + +.mkt-kpi-card__header { + display: flex; + align-items: center; + justify-content: space-between; +} + +.mkt-kpi-card__icon { + width: 32px; + height: 32px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; +} + +.mkt-kpi-card__icon i { + width: 16px; + height: 16px; +} + +.mkt-kpi-card__badge { + font-size: 11px; + font-weight: 600; + padding: 2px 6px; + border-radius: 4px; + display: flex; + align-items: center; + gap: 2px; +} + +.mkt-kpi-card__badge--up { + color: var(--mkt-success); + background: rgba(34, 197, 94, 0.1); +} + +.mkt-kpi-card__badge--down { + color: var(--mkt-error); + background: rgba(239, 68, 68, 0.1); +} + +.mkt-kpi-card__badge i { + width: 10px; + height: 10px; +} + +.mkt-kpi-card__value { + font-size: 24px; + font-weight: 700; + color: var(--mkt-text-primary); +} + +.mkt-kpi-card__label { + font-size: 12px; + color: var(--mkt-text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* ── PANELS / CARDS ── */ +.mkt-panel { + background: var(--mkt-bg-card); + border-radius: 10px; + overflow: hidden; +} + +.mkt-panel__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + border-bottom: 1px solid var(--mkt-border); +} + +.mkt-panel__title { + font-size: 13px; + font-weight: 700; + color: var(--mkt-text-primary); + display: flex; + align-items: center; + gap: 8px; + letter-spacing: 0.5px; + text-transform: uppercase; + margin: 0; +} + +.mkt-panel__title i { + width: 16px; + height: 16px; + color: var(--mkt-yellow-primary); +} + +.mkt-panel__action { + font-size: 12px; + color: var(--mkt-yellow-primary); + text-decoration: none; + font-weight: 500; + letter-spacing: 0.3px; +} + +.mkt-panel__body { + padding: 16px 20px; +} + +/* ── TABLE ── */ +.mkt-table { + width: 100%; + border-collapse: collapse; + font-size: 13px; +} + +.mkt-table th { + text-align: left; + padding: 10px 12px; + font-size: 11px; + font-weight: 600; + color: var(--mkt-text-tertiary); + text-transform: uppercase; + letter-spacing: 0.5px; + border-bottom: 1px solid var(--mkt-border); +} + +.mkt-table td { + padding: 12px; + border-bottom: 1px solid var(--mkt-border); + color: var(--mkt-text-primary); +} + +.mkt-table tr:last-child td { + border-bottom: none; +} + +.mkt-table tr:hover td { + background: var(--mkt-yellow-dim); +} + +/* ── STATUS BADGES ── */ +.mkt-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 3px 8px; + border-radius: 4px; + font-size: 11px; + font-weight: 600; + letter-spacing: 0.3px; + text-transform: uppercase; +} + +.mkt-badge--active { + background: rgba(34, 197, 94, 0.12); + color: var(--mkt-success); +} + +.mkt-badge--draft { + background: rgba(113, 113, 122, 0.12); + color: var(--mkt-text-secondary); +} + +.mkt-badge--training { + background: rgba(250, 204, 21, 0.12); + color: var(--mkt-warning); +} + +.mkt-badge--error { + background: rgba(239, 68, 68, 0.12); + color: var(--mkt-error); +} + +.mkt-badge--info { + background: rgba(59, 130, 246, 0.12); + color: var(--mkt-info); +} + +/* ── SEARCH ── */ +.mkt-search { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + background: var(--mkt-bg-card); + border: 1px solid var(--mkt-border); + border-radius: 6px; + min-width: 200px; +} + +.mkt-search i { + width: 14px; + height: 14px; + color: var(--mkt-text-tertiary); +} + +.mkt-search input { + flex: 1; + background: none; + border: none; + outline: none; + color: var(--mkt-text-primary); + font-size: 12px; + font-family: var(--mkt-font); +} + +.mkt-search input::placeholder { + color: var(--mkt-text-tertiary); +} + +/* ── CHANNEL CARDS (Social Hub) ── */ +.mkt-channel-card { + background: var(--mkt-bg-card); + border-radius: 10px; + padding: 16px; + display: flex; + flex-direction: column; + gap: 12px; + border: 1px solid var(--mkt-border); + transition: border-color 0.2s; +} + +.mkt-channel-card:hover { + border-color: var(--mkt-border-light); +} + +/* ── CHAT (Livechat) ── */ +.mkt-chat-layout { + display: flex; + flex: 1; + min-height: 0; +} + +.mkt-chat-list { + width: 300px; + border-right: 1px solid var(--mkt-border); + display: flex; + flex-direction: column; + overflow-y: auto; +} + +.mkt-chat-main { + flex: 1; + display: flex; + flex-direction: column; +} + +.mkt-chat-info { + width: 280px; + border-left: 1px solid var(--mkt-border); + display: flex; + flex-direction: column; + overflow-y: auto; +} + +.mkt-chat-item { + display: flex; + gap: 10px; + padding: 12px 16px; + cursor: pointer; + border-bottom: 1px solid var(--mkt-border); + transition: background 0.2s; +} + +.mkt-chat-item:hover, +.mkt-chat-item--active { + background: var(--mkt-yellow-dim); +} + +/* ── MOBILE RESPONSIVE ── */ +.mkt-sidebar-overlay { + display: none; +} + +@media (max-width: 768px) { + .mkt-sidebar { + position: fixed; + left: -260px; + top: 0; + bottom: 0; + z-index: 1000; + transition: left 0.3s ease; + } + + .mkt-sidebar--open { + left: 0; + } + + .mkt-sidebar-overlay { + display: block; + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 999; + } + + .mkt-kpi-row { + flex-wrap: wrap; + } + + .mkt-kpi-card { + min-width: calc(50% - 8px); + } + + .mkt-topbar { + padding: 16px; + } + + .mkt-content { + padding: 0 16px 16px; + } + + .mkt-chat-info { + display: none; + } + + .mkt-chat-list { + width: 100%; + } +} diff --git a/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/index.html b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/index.html index ec69d35e..6b79692f 100644 --- a/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/index.html +++ b/apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/index.html @@ -32,6 +32,7 @@ +