popsite-ui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/App.jsx +95 -0
  2. package/README.md +92 -0
  3. package/components/layout/PortalHeader.jsx +18 -0
  4. package/components/layout/SystemSidebar.jsx +33 -0
  5. package/components/modules/AnalyticsDashboardModule.jsx +17 -0
  6. package/components/modules/ChatMessagingModule.jsx +17 -0
  7. package/components/modules/EcommerceStoreModule.jsx +17 -0
  8. package/components/modules/EventTicketBookingModule.jsx +17 -0
  9. package/components/modules/FlightBookingModule.jsx +17 -0
  10. package/components/modules/FoodOrderingModule.jsx +17 -0
  11. package/components/modules/HospitalAppointmentModule.jsx +17 -0
  12. package/components/modules/HotelBookingModule.jsx +17 -0
  13. package/components/modules/InvoiceBillingModule.jsx +17 -0
  14. package/components/modules/LibraryManagementModule.jsx +17 -0
  15. package/components/modules/ModuleContentDeck.jsx +44 -0
  16. package/components/modules/MovieBookingModule.jsx +17 -0
  17. package/components/modules/QuizExamModule.jsx +17 -0
  18. package/components/modules/StudentRegistrationModule.jsx +17 -0
  19. package/components/modules/SystemModuleRenderer.jsx +19 -0
  20. package/components/modules/SystemModuleTemplate.jsx +62 -0
  21. package/components/modules/SystemVisualWidget.jsx +123 -0
  22. package/components/modules/moduleContentMap.js +238 -0
  23. package/components/modules/moduleEnhancementsMap.js +439 -0
  24. package/components/modules/systemModuleMap.js +31 -0
  25. package/components/system/DynamicSystemForm.jsx +154 -0
  26. package/components/system/SystemHero.jsx +21 -0
  27. package/components/system/SystemSummaryCard.jsx +53 -0
  28. package/data/systems/analyticsDashboard.js +48 -0
  29. package/data/systems/chatMessaging.js +43 -0
  30. package/data/systems/ecommerceStore.js +50 -0
  31. package/data/systems/eventTicketBooking.js +50 -0
  32. package/data/systems/flightBooking.js +38 -0
  33. package/data/systems/foodOrdering.js +48 -0
  34. package/data/systems/hospitalAppointment.js +50 -0
  35. package/data/systems/hotelBooking.js +38 -0
  36. package/data/systems/index.js +31 -0
  37. package/data/systems/invoiceBilling.js +50 -0
  38. package/data/systems/libraryManagement.js +43 -0
  39. package/data/systems/movieBooking.js +48 -0
  40. package/data/systems/quizExam.js +38 -0
  41. package/data/systems/studentRegistration.js +43 -0
  42. package/dist/popsite-ui.es.js +4368 -0
  43. package/dist/popsite-ui.umd.js +60 -0
  44. package/dist/style.css +1 -0
  45. package/index.html +13 -0
  46. package/library/index.js +20 -0
  47. package/main.jsx +15 -0
  48. package/package.json +40 -0
  49. package/src/App.jsx +12 -0
  50. package/src/components/modules/AnalyticsDashboardModule.jsx +224 -0
  51. package/src/components/modules/ChatMessagingModule.jsx +294 -0
  52. package/src/components/modules/EcommerceStoreModule.jsx +405 -0
  53. package/src/components/modules/EventTicketBookingModule.jsx +253 -0
  54. package/src/components/modules/FlightBookingModule.jsx +399 -0
  55. package/src/components/modules/FoodOrderingModule.jsx +316 -0
  56. package/src/components/modules/HospitalAppointmentModule.jsx +267 -0
  57. package/src/components/modules/HotelBookingModule.jsx +317 -0
  58. package/src/components/modules/InvoiceBillingModule.jsx +302 -0
  59. package/src/components/modules/LandingPageModule.jsx +185 -0
  60. package/src/components/modules/LibraryManagementModule.jsx +189 -0
  61. package/src/components/modules/MovieBookingModule.jsx +337 -0
  62. package/src/components/modules/QuizExamModule.jsx +255 -0
  63. package/src/components/modules/StudentRegistrationModule.jsx +292 -0
  64. package/src/components/system/SystemHero.jsx +44 -0
  65. package/src/components/system/SystemSummaryCard.jsx +29 -0
  66. package/src/components/system/Toast.jsx +69 -0
  67. package/src/data/systems/analyticsDashboard.js +32 -0
  68. package/src/data/systems/chatMessaging.js +59 -0
  69. package/src/data/systems/ecommerceStore.js +84 -0
  70. package/src/data/systems/eventBooking.js +33 -0
  71. package/src/data/systems/flightBooking.js +59 -0
  72. package/src/data/systems/foodOrdering.js +48 -0
  73. package/src/data/systems/hospitalAppointment.js +48 -0
  74. package/src/data/systems/hotelBooking.js +59 -0
  75. package/src/data/systems/invoiceBilling.js +19 -0
  76. package/src/data/systems/landingPage.js +29 -0
  77. package/src/data/systems/libraryManagement.js +17 -0
  78. package/src/data/systems/movieBooking.js +49 -0
  79. package/src/data/systems/quizExam.js +31 -0
  80. package/src/data/systems/studentRegistration.js +9 -0
  81. package/src/index.js +22 -0
  82. package/src/main.jsx +10 -0
  83. package/src/styles.css +296 -0
  84. package/styles.css +820 -0
  85. package/utils/systemEngine.js +128 -0
  86. package/vite.config.js +8 -0
  87. package/vite.lib.config.js +27 -0
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ :root{--pk-primary: #6366f1;--pk-primary-hover: #4f46e5;--pk-primary-light: #e0e7ff;--pk-secondary: #0ea5e9;--pk-accent: #f43f5e;--pk-bg-main: #f8fafc;--pk-bg-card: #ffffff;--pk-bg-glass: rgba(255, 255, 255, .7);--pk-text-main: #0f172a;--pk-text-muted: #64748b;--pk-text-inverse: #ffffff;--pk-border: #e2e8f0;--pk-success: #10b981;--pk-warning: #f59e0b;--pk-danger: #ef4444;--pk-font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;--pk-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, .05);--pk-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, .1), 0 2px 4px -1px rgba(0, 0, 0, .06);--pk-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -2px rgba(0, 0, 0, .05);--pk-shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, .1), 0 10px 10px -5px rgba(0, 0, 0, .04);--pk-shadow-hover: 0 20px 40px -5px rgba(99, 102, 241, .15), 0 10px 15px -5px rgba(0, 0, 0, .05);--pk-radius-sm: 8px;--pk-radius-md: 12px;--pk-radius-lg: 16px;--pk-radius-full: 9999px;--pk-transition-fast: .15s cubic-bezier(.4, 0, .2, 1);--pk-transition-normal: .3s cubic-bezier(.4, 0, .2, 1)}*{box-sizing:border-box;margin:0;padding:0}body{font-family:var(--pk-font-family);background-color:var(--pk-bg-main);color:var(--pk-text-main);line-height:1.5;-webkit-font-smoothing:antialiased}.pk-glass{background:var(--pk-bg-glass);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.4)}.pk-heading-xl{font-size:3rem;font-weight:800;letter-spacing:-.025em;line-height:1.1}.pk-heading-lg{font-size:2.25rem;font-weight:700;letter-spacing:-.025em;line-height:1.2}.pk-heading-md{font-size:1.5rem;font-weight:600;line-height:1.3}.pk-text-body{font-size:1rem;color:var(--pk-text-muted)}.pk-text-sm{font-size:.875rem;color:var(--pk-text-muted)}.pk-gradient-text{background:linear-gradient(135deg,var(--pk-primary),var(--pk-secondary));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.pk-card{background-color:var(--pk-bg-card);border-radius:var(--pk-radius-lg);box-shadow:var(--pk-shadow-sm);transition:transform var(--pk-transition-normal),box-shadow var(--pk-transition-normal);overflow:hidden;border:1px solid var(--pk-border)}.pk-card-interactive:hover{transform:translateY(-4px);box-shadow:var(--pk-shadow-hover)}.pk-card-header{padding:1.5rem 1.5rem 0}.pk-card-body{padding:1.5rem}.pk-card-footer{padding:1rem 1.5rem;border-top:1px solid var(--pk-border);background-color:#f8fafc80}.pk-btn{display:inline-flex;align-items:center;justify-content:center;padding:.75rem 1.5rem;border-radius:var(--pk-radius-md);font-weight:600;font-size:.95rem;cursor:pointer;transition:all var(--pk-transition-fast);border:none;gap:.5rem}.pk-btn-primary{background:linear-gradient(135deg,var(--pk-primary),var(--pk-primary-hover));color:var(--pk-text-inverse);box-shadow:0 4px 10px #6366f14d}.pk-btn-primary:hover{transform:translateY(-2px);box-shadow:0 6px 15px #6366f166}.pk-btn-primary:active{transform:translateY(0)}.pk-btn-secondary{background-color:var(--pk-primary-light);color:var(--pk-primary-hover)}.pk-btn-secondary:hover{background-color:#c7d2fe}.pk-btn-outline{background-color:transparent;border:1px solid var(--pk-border);color:var(--pk-text-main)}.pk-btn-outline:hover{border-color:var(--pk-primary);color:var(--pk-primary)}.pk-input-group{display:flex;flex-direction:column;gap:.5rem;margin-bottom:1rem}.pk-label{font-size:.875rem;font-weight:600;color:var(--pk-text-main)}.pk-input{padding:.75rem 1rem;border-radius:var(--pk-radius-md);border:1px solid var(--pk-border);background-color:var(--pk-bg-main);font-family:inherit;font-size:1rem;transition:border-color var(--pk-transition-fast),box-shadow var(--pk-transition-fast)}.pk-input:focus{outline:none;border-color:var(--pk-primary);box-shadow:0 0 0 3px var(--pk-primary-light)}.pk-container{max-width:1200px;margin:0 auto;padding:2rem}.pk-grid{display:grid;gap:2rem}.pk-grid-responsive{grid-template-columns:repeat(auto-fill,minmax(300px,1fr))}.pk-flex{display:flex}.pk-flex-col{flex-direction:column}.pk-items-center{align-items:center}.pk-justify-between{justify-content:space-between}.pk-justify-center{justify-content:center}.pk-gap-4{gap:1rem}.pk-gap-6{gap:1.5rem}.pk-toast-container{position:fixed;bottom:2rem;right:2rem;display:flex;flex-direction:column;gap:1rem;z-index:50;pointer-events:none}.pk-toast{background-color:var(--pk-bg-card);border-radius:var(--pk-radius-md);box-shadow:var(--pk-shadow-xl);padding:1rem 1.5rem;display:flex;align-items:center;gap:.75rem;border-left:4px solid var(--pk-primary);animation:pk-slide-up .3s ease-out forwards;pointer-events:auto}.pk-toast.success{border-color:var(--pk-success)}.pk-toast.error{border-color:var(--pk-danger)}@keyframes pk-slide-up{0%{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}}.pk-skeleton{background:linear-gradient(90deg,#e2e8f0 25%,#f1f5f9,#e2e8f0 75%);background-size:200% 100%;animation:pk-skeleton-loading 1.5s infinite;border-radius:var(--pk-radius-sm)}@keyframes pk-skeleton-loading{0%{background-position:200% 0}to{background-position:-200% 0}}.pk-badge{display:inline-flex;align-items:center;padding:.25rem .75rem;border-radius:var(--pk-radius-full);font-size:.75rem;font-weight:600;background-color:var(--pk-primary-light);color:var(--pk-primary-hover)}.pk-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:4rem 2rem;text-align:center;background-color:#f8fafc80;border-radius:var(--pk-radius-lg);border:2px dashed var(--pk-border)}.pk-empty-icon{font-size:3rem;margin-bottom:1rem;opacity:.7}.pk-nav{position:sticky;top:0;z-index:40;display:flex;justify-content:space-between;align-items:center;padding:1rem 2rem;border-bottom:1px solid rgba(226,232,240,.5)}.pk-text-center{text-align:center}.pk-w-full{width:100%}.pk-h-full{height:100%}.pk-object-cover{object-fit:cover}
package/index.html ADDED
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="description" content="PopSite UI - Zero config React component library" />
7
+ <title>PopSite UI Demo</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.jsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,20 @@
1
+ export { default as App } from '../App';
2
+ export { default as MultiDomainBookingPortal } from '../App';
3
+
4
+ export { default as systems } from '../data/systems';
5
+
6
+ export { default as PortalHeader } from '../components/layout/PortalHeader';
7
+ export { default as SystemSidebar } from '../components/layout/SystemSidebar';
8
+
9
+ export { default as SystemHero } from '../components/system/SystemHero';
10
+ export { default as DynamicSystemForm } from '../components/system/DynamicSystemForm';
11
+ export { default as SystemSummaryCard } from '../components/system/SystemSummaryCard';
12
+
13
+ export { default as SystemModuleRenderer } from '../components/modules/SystemModuleRenderer';
14
+ export { default as systemModuleMap } from '../components/modules/systemModuleMap';
15
+ export { default as ModuleContentDeck } from '../components/modules/ModuleContentDeck';
16
+ export { default as moduleContentMap } from '../components/modules/moduleContentMap';
17
+ export { default as moduleEnhancementsMap } from '../components/modules/moduleEnhancementsMap';
18
+ export { default as SystemVisualWidget } from '../components/modules/SystemVisualWidget';
19
+
20
+ export * from '../utils/systemEngine';
package/main.jsx ADDED
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import App from './App';
4
+
5
+ const rootElement = document.getElementById('root');
6
+
7
+ if (!rootElement) {
8
+ throw new Error('Root element not found. Ensure index.html includes a #root element.');
9
+ }
10
+
11
+ createRoot(rootElement).render(
12
+ <React.StrictMode>
13
+ <App />
14
+ </React.StrictMode>
15
+ );
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "popsite-ui",
3
+ "version": "1.0.0",
4
+ "description": "Zero-config, plug-and-play React component library for multi-view applications.",
5
+ "type": "module",
6
+ "main": "dist/popsite-ui.umd.js",
7
+ "module": "dist/popsite-ui.es.js",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/popsite-ui.es.js",
11
+ "require": "./dist/popsite-ui.umd.js"
12
+ },
13
+ "./style.css": "./dist/style.css"
14
+ },
15
+ "scripts": {
16
+ "dev": "vite",
17
+ "build": "vite build --config vite.lib.config.js",
18
+ "preview": "vite preview"
19
+ },
20
+ "peerDependencies": {
21
+ "react": "^18.2.0 || ^19.0.0",
22
+ "react-dom": "^18.2.0 || ^19.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "@vitejs/plugin-react": "^4.2.1",
26
+ "vite": "^5.2.0",
27
+ "react": "^18.2.0",
28
+ "react-dom": "^18.2.0"
29
+ },
30
+ "keywords": [
31
+ "react",
32
+ "components",
33
+ "ui",
34
+ "library",
35
+ "zero-config",
36
+ "frontend"
37
+ ],
38
+ "author": "Elite React Architect",
39
+ "license": "MIT"
40
+ }
package/src/App.jsx ADDED
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import { EcommerceStoreModule } from './components/modules/EcommerceStoreModule';
3
+
4
+ export function App() {
5
+ return (
6
+ <div>
7
+ <EcommerceStoreModule />
8
+ </div>
9
+ );
10
+ }
11
+
12
+ export default App;
@@ -0,0 +1,224 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { analyticsDashboardMockData } from '../../data/systems/analyticsDashboard';
3
+ import { SystemSummaryCard } from '../system/SystemSummaryCard';
4
+ import { useToast } from '../system/Toast';
5
+
6
+ export function AnalyticsDashboardModule() {
7
+ const [currentView, setCurrentView] = useState('summary'); // summary, financial, logs
8
+ const [searchQuery, setSearchQuery] = useState('');
9
+ const [isLoading, setIsLoading] = useState(false);
10
+ const { showToast, ToastContainer } = useToast();
11
+
12
+ const withDelay = (callback, delay = 1000) => {
13
+ setIsLoading(true);
14
+ setTimeout(() => {
15
+ setIsLoading(false);
16
+ callback();
17
+ }, delay);
18
+ };
19
+
20
+ const handleExportCSV = (dataArray, filename) => {
21
+ withDelay(() => {
22
+ if (!dataArray || dataArray.length === 0) {
23
+ showToast('No data to export', 'error');
24
+ return;
25
+ }
26
+ const headers = Object.keys(dataArray[0]).join(',');
27
+ const rows = dataArray.map(obj =>
28
+ Object.values(obj).map(v => typeof v === 'string' ? `"${v}"` : v).join(',')
29
+ ).join('\n');
30
+
31
+ const blob = new Blob([`${headers}\n${rows}`], { type: 'text/csv' });
32
+ const url = URL.createObjectURL(blob);
33
+ const a = document.createElement('a');
34
+ a.href = url;
35
+ a.download = `${filename}_${Date.now()}.csv`;
36
+ a.click();
37
+ showToast('Export successful', 'success');
38
+ });
39
+ };
40
+
41
+ const renderNav = () => (
42
+ <div className="pk-card" style={{ marginBottom: '2rem', padding: '1rem', display: 'flex', gap: '1rem', overflowX: 'auto' }}>
43
+ <button
44
+ className={currentView === 'summary' ? 'pk-btn pk-btn-primary' : 'pk-btn pk-btn-outline'}
45
+ onClick={() => setCurrentView('summary')}
46
+ >
47
+ Executive Summary
48
+ </button>
49
+ <button
50
+ className={currentView === 'financial' ? 'pk-btn pk-btn-primary' : 'pk-btn pk-btn-outline'}
51
+ onClick={() => setCurrentView('financial')}
52
+ >
53
+ Financial Reports
54
+ </button>
55
+ <button
56
+ className={currentView === 'logs' ? 'pk-btn pk-btn-primary' : 'pk-btn pk-btn-outline'}
57
+ onClick={() => setCurrentView('logs')}
58
+ >
59
+ System Logs
60
+ </button>
61
+ </div>
62
+ );
63
+
64
+ const renderSummary = () => {
65
+ const kpis = analyticsDashboardMockData.kpis;
66
+ const maxVal = Math.max(...analyticsDashboardMockData.chartData.map(d => d.value));
67
+
68
+ return (
69
+ <div className="animate-fade-in">
70
+ <h2 className="pk-heading-lg" style={{ marginBottom: '2rem' }}>Executive Overview</h2>
71
+
72
+ <div className="pk-grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', marginBottom: '2rem' }}>
73
+ <SystemSummaryCard title="Total Revenue" value={kpis.revenue.value} trend={kpis.revenue.trend} icon="💰" />
74
+ <SystemSummaryCard title="Active Users" value={kpis.users.value} trend={kpis.users.trend} icon="👥" />
75
+ <SystemSummaryCard title="Conversion Rate" value={kpis.conversionRate.value} trend={kpis.conversionRate.trend} icon="🔥" />
76
+ <SystemSummaryCard title="Bounce Rate" value={kpis.bounceRate.value} trend={kpis.bounceRate.trend} icon="📉" />
77
+ </div>
78
+
79
+ <div className="pk-card" style={{ padding: '2rem' }}>
80
+ <h3 className="pk-heading-md" style={{ marginBottom: '2rem' }}>Weekly Traffic Activity</h3>
81
+
82
+ {/* CSS-only Bar Chart implementation, NO Tailwind */}
83
+ <div style={{ display: 'flex', alignItems: 'flex-end', height: '250px', gap: '2%', paddingBottom: '2rem', borderBottom: '1px solid var(--pk-border)' }}>
84
+ {analyticsDashboardMockData.chartData.map((data, i) => {
85
+ const heightPercent = (data.value / maxVal) * 100;
86
+ return (
87
+ <div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', height: '100%', justifyContent: 'flex-end' }}>
88
+ <div style={{ opacity: 0, transition: 'opacity 0.2s', fontSize: '0.75rem', marginBottom: '0.25rem' }} className="chart-tooltip">{data.value}k</div>
89
+ <div
90
+ style={{
91
+ width: '100%',
92
+ height: `${heightPercent}%`,
93
+ background: 'linear-gradient(to top, var(--pk-primary), var(--pk-secondary))',
94
+ borderRadius: '4px 4px 0 0',
95
+ transition: 'height 1s cubic-bezier(0.1, 0.8, 0.2, 1)'
96
+ }}
97
+ className="chart-bar"
98
+ ></div>
99
+ <span style={{ marginTop: '1rem', fontSize: '0.875rem', color: 'var(--pk-text-muted)' }}>{data.label}</span>
100
+ </div>
101
+ );
102
+ })}
103
+ </div>
104
+ </div>
105
+
106
+ {/* Helper style for hover effect without utility classes */}
107
+ <style>{`
108
+ .chart-bar:hover { filter: brightness(1.2); cursor: pointer; }
109
+ .chart-bar:hover + span { font-weight: bold; color: var(--pk-text-main); }
110
+ .chart-bar:hover ~ .chart-tooltip, .chart-tooltip:focus { opacity: 1 !important; }
111
+ `}</style>
112
+ </div>
113
+ );
114
+ };
115
+
116
+ const renderFinancial = () => (
117
+ <div className="animate-fade-in">
118
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem' }}>
119
+ <h2 className="pk-heading-lg">Financial Reports</h2>
120
+ <button className="pk-btn pk-btn-primary" onClick={() => handleExportCSV(analyticsDashboardMockData.financialReports, 'Financial_Report')}>
121
+ ⬇️ Export CSV
122
+ </button>
123
+ </div>
124
+
125
+ <div className="pk-card" style={{ overflowX: 'auto' }}>
126
+ <table style={{ width: '100%', borderCollapse: 'collapse', textAlign: 'left' }}>
127
+ <thead>
128
+ <tr style={{ background: 'var(--pk-bg-main)', borderBottom: '1px solid var(--pk-border)' }}>
129
+ <th style={{ padding: '1rem 1.5rem', color: 'var(--pk-text-muted)', fontWeight: 600 }}>Period</th>
130
+ <th style={{ padding: '1rem 1.5rem', color: 'var(--pk-text-muted)', fontWeight: 600 }}>Revenue</th>
131
+ <th style={{ padding: '1rem 1.5rem', color: 'var(--pk-text-muted)', fontWeight: 600 }}>Expenses</th>
132
+ <th style={{ padding: '1rem 1.5rem', color: 'var(--pk-text-muted)', fontWeight: 600 }}>Net Profit</th>
133
+ <th style={{ padding: '1rem 1.5rem', color: 'var(--pk-text-muted)', fontWeight: 600 }}>Status</th>
134
+ </tr>
135
+ </thead>
136
+ <tbody>
137
+ {analyticsDashboardMockData.financialReports.map((row, i) => (
138
+ <tr key={i} style={{ borderBottom: '1px solid var(--pk-border)', transition: 'background 0.2s' }}>
139
+ <td style={{ padding: '1rem 1.5rem', fontWeight: 600 }}>{row.period}</td>
140
+ <td style={{ padding: '1rem 1.5rem', color: 'var(--pk-success)' }}>+${row.revenue.toLocaleString()}</td>
141
+ <td style={{ padding: '1rem 1.5rem', color: 'var(--pk-danger)' }}>-${row.expenses.toLocaleString()}</td>
142
+ <td style={{ padding: '1rem 1.5rem', fontWeight: 'bold' }}>${row.net.toLocaleString()}</td>
143
+ <td style={{ padding: '1rem 1.5rem' }}>
144
+ <span className="pk-badge">{row.status}</span>
145
+ </td>
146
+ </tr>
147
+ ))}
148
+ </tbody>
149
+ </table>
150
+ </div>
151
+ </div>
152
+ );
153
+
154
+ const renderLogs = () => {
155
+ const filtered = analyticsDashboardMockData.systemLogs.filter(log =>
156
+ log.message.toLowerCase().includes(searchQuery.toLowerCase()) ||
157
+ log.service.toLowerCase().includes(searchQuery.toLowerCase())
158
+ );
159
+
160
+ return (
161
+ <div className="animate-fade-in">
162
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem', flexWrap: 'wrap', gap: '1rem' }}>
163
+ <h2 className="pk-heading-lg">System Logs</h2>
164
+ <div style={{ display: 'flex', gap: '1rem' }}>
165
+ <input
166
+ type="text"
167
+ className="pk-input"
168
+ placeholder="Search logs..."
169
+ value={searchQuery}
170
+ onChange={e => setSearchQuery(e.target.value)}
171
+ />
172
+ <button className="pk-btn pk-btn-outline" onClick={() => handleExportCSV(filtered, 'System_Logs')}>
173
+ ⬇️ Export
174
+ </button>
175
+ </div>
176
+ </div>
177
+
178
+ <div className="pk-card" style={{ background: '#0f172a', color: '#e2e8f0', padding: '1rem', fontFamily: 'monospace', borderRadius: 'var(--pk-radius-lg)', overflowX: 'auto', maxHeight: '600px', overflowY: 'auto' }}>
179
+ {filtered.length === 0 ? (
180
+ <div style={{ padding: '2rem', textAlign: 'center' }}>No logs found matching query.</div>
181
+ ) : (
182
+ filtered.map(log => {
183
+ const color = log.level === 'ERROR' ? '#ef4444' : log.level === 'WARN' ? '#f59e0b' : '#38bdf8';
184
+ return (
185
+ <div key={log.id} style={{ display: 'flex', gap: '1rem', padding: '0.75rem', borderBottom: '1px solid #1e293b' }}>
186
+ <span style={{ color: '#64748b', whiteSpace: 'nowrap' }}>[{log.timestamp}]</span>
187
+ <span style={{ color: color, fontWeight: 'bold', width: '60px' }}>{log.level}</span>
188
+ <span style={{ color: '#cbd5e1', width: '120px' }}>[{log.service}]</span>
189
+ <span style={{ flex: 1 }}>{log.message}</span>
190
+ </div>
191
+ )
192
+ })
193
+ )}
194
+ </div>
195
+ </div>
196
+ );
197
+ };
198
+
199
+ return (
200
+ <div className="pk-container" style={{ minHeight: '100vh' }}>
201
+ {isLoading && (
202
+ <div style={{ position: 'fixed', inset: 0, backgroundColor: 'rgba(255,255,255,0.8)', zIndex: 100, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', backdropFilter: 'blur(4px)' }}>
203
+ <div className="pk-skeleton" style={{ width: '80px', height: '80px', borderRadius: '50%', marginBottom: '1rem' }}></div>
204
+ <h3 className="pk-heading-md">Processing Data...</h3>
205
+ </div>
206
+ )}
207
+
208
+ <div style={{ marginBottom: '2rem' }}>
209
+ <h1 className="pk-heading-xl">{analyticsDashboardMockData.organizationName} Engine</h1>
210
+ <p className="pk-text-body">Real-time metrics and system health monitoring.</p>
211
+ </div>
212
+
213
+ {renderNav()}
214
+
215
+ <div style={{ position: 'relative', minHeight: '500px' }}>
216
+ {currentView === 'summary' && renderSummary()}
217
+ {currentView === 'financial' && renderFinancial()}
218
+ {currentView === 'logs' && renderLogs()}
219
+ </div>
220
+
221
+ <ToastContainer />
222
+ </div>
223
+ );
224
+ }
@@ -0,0 +1,294 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { chatMessagingMockData } from '../../data/systems/chatMessaging';
3
+ import { useToast } from '../system/Toast';
4
+
5
+ export function ChatMessagingModule() {
6
+ const [conversations, setConversations] = useState(chatMessagingMockData.conversations);
7
+ const [activeChatId, setActiveChatId] = useState(chatMessagingMockData.conversations[0]?.id || null);
8
+ const [newMessage, setNewMessage] = useState('');
9
+ const [searchQuery, setSearchQuery] = useState('');
10
+ const [isTyping, setIsTyping] = useState(false);
11
+ const [showProfile, setShowProfile] = useState(false);
12
+ const [isLoading, setIsLoading] = useState(false);
13
+
14
+ const messagesEndRef = useRef(null);
15
+ const { showToast, ToastContainer } = useToast();
16
+
17
+ const activeChat = conversations.find(c => c.id === activeChatId);
18
+
19
+ // Restore state hook simulation
20
+ useEffect(() => {
21
+ const saved = localStorage.getItem('popsite_chats');
22
+ if (saved) {
23
+ try { setConversations(JSON.parse(saved)); } catch (e) {}
24
+ }
25
+ }, []);
26
+
27
+ useEffect(() => {
28
+ localStorage.setItem('popsite_chats', JSON.stringify(conversations));
29
+ }, [conversations]);
30
+
31
+ // Auto-scroll
32
+ useEffect(() => {
33
+ if (messagesEndRef.current) {
34
+ messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
35
+ }
36
+ }, [activeChat?.messages, isTyping]);
37
+
38
+ const withDelay = (callback, delay = 1500) => {
39
+ setIsLoading(true);
40
+ setTimeout(() => {
41
+ setIsLoading(false);
42
+ callback();
43
+ }, delay);
44
+ };
45
+
46
+ const handleSendMessage = (e) => {
47
+ e.preventDefault();
48
+ if (!newMessage.trim() || !activeChat) return;
49
+
50
+ const msg = {
51
+ id: `msg-${Date.now()}`,
52
+ senderId: chatMessagingMockData.currentUser.id,
53
+ text: newMessage.trim(),
54
+ timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
55
+ };
56
+
57
+ const updatedConversations = conversations.map(c =>
58
+ c.id === activeChatId ? { ...c, messages: [...c.messages, msg], unread: 0 } : c
59
+ );
60
+ setConversations(updatedConversations);
61
+ setNewMessage('');
62
+
63
+ // Simulate AI / Mock Response delay
64
+ setIsTyping(true);
65
+ setTimeout(() => {
66
+ setIsTyping(false);
67
+ const replyPool = chatMessagingMockData.autoReplies;
68
+ const randomReply = replyPool[Math.floor(Math.random() * replyPool.length)];
69
+
70
+ const botMsg = {
71
+ id: `msg-bot-${Date.now()}`,
72
+ senderId: activeChat.contact.id,
73
+ text: randomReply,
74
+ timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
75
+ };
76
+
77
+ setConversations(prev => prev.map(c =>
78
+ c.id === activeChatId ? { ...c, messages: [...c.messages, botMsg] } : c
79
+ ));
80
+
81
+ // Randomly occasionally toast a notification if app was 'backgrounded'
82
+ if (Math.random() > 0.5) showToast(`New message from ${activeChat.contact.name}`, 'info');
83
+
84
+ }, 2000);
85
+ };
86
+
87
+ const markAsRead = (id) => {
88
+ setConversations(prev => prev.map(c => c.id === id ? { ...c, unread: 0 } : c));
89
+ setActiveChatId(id);
90
+ setShowProfile(false);
91
+ };
92
+
93
+ const handleSaveProfile = () => {
94
+ withDelay(() => {
95
+ showToast('Profile updated successfully', 'success');
96
+ setShowProfile(false);
97
+ });
98
+ };
99
+
100
+ const filteredConversations = conversations.filter(c =>
101
+ c.contact.name.toLowerCase().includes(searchQuery.toLowerCase())
102
+ );
103
+
104
+ return (
105
+ <div className="pk-flex animate-fade-in" style={{ height: '100vh', backgroundColor: 'var(--pk-bg-main)', overflow: 'hidden' }}>
106
+ {isLoading && (
107
+ <div style={{ position: 'fixed', inset: 0, backgroundColor: 'rgba(255,255,255,0.7)', zIndex: 100, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', backdropFilter: 'blur(4px)' }}>
108
+ <div className="pk-skeleton" style={{ width: '80px', height: '80px', borderRadius: '50%', marginBottom: '1rem' }}></div>
109
+ <h3 className="pk-heading-md">Processing...</h3>
110
+ </div>
111
+ )}
112
+
113
+ {/* Sidebar */}
114
+ <div className="pk-card" style={{ width: '350px', height: '100%', borderRadius: 0, borderRight: '1px solid var(--pk-border)', display: 'flex', flexDirection: 'column', zIndex: 10 }}>
115
+ <div className="pk-card-header pk-flex pk-justify-between pk-items-center" style={{ padding: '1.5rem', background: 'var(--pk-bg-glass)', backdropFilter: 'blur(10px)' }}>
116
+ <div className="pk-flex pk-items-center pk-gap-4" style={{ cursor: 'pointer' }} onClick={() => setShowProfile(true)}>
117
+ <img
118
+ src={chatMessagingMockData.currentUser.avatar}
119
+ alt="Me"
120
+ style={{ width: '40px', height: '40px', borderRadius: '50%', objectFit: 'cover' }}
121
+ onError={(e)=>{ e.target.style.display='none'; e.target.nextSibling.style.display='flex'}}
122
+ />
123
+ <div style={{ display: 'none', width: '40px', height: '40px', background: 'var(--pk-primary)', color: 'white', alignItems: 'center', justifyContent: 'center', borderRadius: '50%', fontWeight: 'bold' }}>
124
+ ME
125
+ </div>
126
+ <div>
127
+ <h3 className="pk-label">{chatMessagingMockData.currentUser.name}</h3>
128
+ <span className="pk-text-sm" style={{ color: 'var(--pk-success)' }}>● Online</span>
129
+ </div>
130
+ </div>
131
+ <button className="pk-btn pk-btn-outline" style={{ padding: '0.5rem', borderRadius: '50%' }}>+ 💬</button>
132
+ </div>
133
+
134
+ <div style={{ padding: '1rem' }}>
135
+ <input
136
+ type="text"
137
+ className="pk-input pk-w-full"
138
+ placeholder="Search conversations..."
139
+ value={searchQuery}
140
+ onChange={e => setSearchQuery(e.target.value)}
141
+ />
142
+ </div>
143
+
144
+ <div style={{ flex: 1, overflowY: 'auto' }}>
145
+ {filteredConversations.length === 0 ? (
146
+ <div className="pk-empty-state" style={{ padding: '2rem', border: 'none' }}>
147
+ <div className="pk-text-sm">No chats found.</div>
148
+ </div>
149
+ ) : (
150
+ filteredConversations.map(conv => (
151
+ <div
152
+ key={conv.id}
153
+ className={`pk-flex pk-items-center pk-gap-4 ${activeChatId === conv.id ? 'pk-bg-glass' : ''}`}
154
+ style={{ padding: '1rem 1.5rem', cursor: 'pointer', borderBottom: '1px solid var(--pk-border)', transition: 'background 0.2s', background: activeChatId === conv.id ? 'var(--pk-primary-light)' : 'transparent' }}
155
+ onClick={() => markAsRead(conv.id)}
156
+ >
157
+ <div style={{ position: 'relative' }}>
158
+ <img src={conv.contact.avatar} alt="" style={{ width: '50px', height: '50px', borderRadius: '50%', objectFit: 'cover' }}
159
+ onError={(e)=>{ e.target.style.display='none'; e.target.nextSibling.style.display='flex'}} />
160
+ <div style={{ display: 'none', width: '50px', height: '50px', background: 'var(--pk-secondary)', color: 'white', alignItems: 'center', justifyContent: 'center', borderRadius: '50%', fontWeight: 'bold' }}>
161
+ {conv.contact.name[0]}
162
+ </div>
163
+ <div style={{ position: 'absolute', bottom: 2, right: 2, width: 12, height: 12, borderRadius: '50%', background: conv.contact.status === 'online' ? 'var(--pk-success)' : 'var(--pk-text-muted)', border: '2px solid white' }}></div>
164
+ </div>
165
+ <div style={{ flex: 1, overflow: 'hidden' }}>
166
+ <div className="pk-flex pk-justify-between pk-items-center">
167
+ <h4 className="pk-label" style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{conv.contact.name}</h4>
168
+ {conv.messages.length > 0 && <span className="pk-text-sm" style={{ fontSize: '0.75rem' }}>{conv.messages[conv.messages.length - 1].timestamp}</span>}
169
+ </div>
170
+ <div className="pk-flex pk-justify-between pk-items-center">
171
+ <p className="pk-text-sm" style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', fontWeight: conv.unread > 0 ? 600 : 400, color: conv.unread > 0 ? 'var(--pk-text-main)' : 'var(--pk-text-muted)' }}>
172
+ {conv.messages[conv.messages.length - 1]?.text || 'No messages yet.'}
173
+ </p>
174
+ {conv.unread > 0 && <span className="pk-badge" style={{ background: 'var(--pk-primary)', color: 'white', minWidth: '20px', textAlign: 'center' }}>{conv.unread}</span>}
175
+ </div>
176
+ </div>
177
+ </div>
178
+ ))
179
+ )}
180
+ </div>
181
+ </div>
182
+
183
+ {/* Main Content Area */}
184
+ <div style={{ flex: 1, display: 'flex', flexDirection: 'column', backgroundColor: 'var(--pk-bg-main)', position: 'relative' }}>
185
+ {showProfile ? (
186
+ <div className="pk-container" style={{ flex: 1, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
187
+ <div className="pk-card" style={{ width: '100%', maxWidth: '500px', padding: '3rem' }}>
188
+ <h2 className="pk-heading-lg" style={{ marginBottom: '2rem', textAlign: 'center' }}>My Profile</h2>
189
+ <div className="pk-flex pk-justify-center" style={{ marginBottom: '2rem' }}>
190
+ <img src={chatMessagingMockData.currentUser.avatar} alt="Profile" style={{ width: '120px', height: '120px', borderRadius: '50%', boxShadow: 'var(--pk-shadow-md)' }} />
191
+ </div>
192
+ <div className="pk-input-group">
193
+ <label className="pk-label">Display Name</label>
194
+ <input type="text" className="pk-input" defaultValue={chatMessagingMockData.currentUser.name} />
195
+ </div>
196
+ <div className="pk-input-group">
197
+ <label className="pk-label">Status Message</label>
198
+ <input type="text" className="pk-input" defaultValue="Building great UIs! 💻" />
199
+ </div>
200
+ <div className="pk-flex pk-justify-between" style={{ marginTop: '2rem' }}>
201
+ <button className="pk-btn pk-btn-outline" onClick={() => setShowProfile(false)}>Cancel</button>
202
+ <button className="pk-btn pk-btn-primary" onClick={handleSaveProfile}>Save Changes</button>
203
+ </div>
204
+ </div>
205
+ </div>
206
+ ) : activeChat ? (
207
+ <>
208
+ <div className="pk-card" style={{ borderRadius: 0, padding: '1rem 2rem', display: 'flex', alignItems: 'center', gap: '1rem', borderBottom: '1px solid var(--pk-border)', zIndex: 5 }}>
209
+ <img src={activeChat.contact.avatar} alt="" style={{ width: '40px', height: '40px', borderRadius: '50%' }} />
210
+ <div>
211
+ <h3 className="pk-heading-md" style={{ fontSize: '1.25rem' }}>{activeChat.contact.name}</h3>
212
+ <span className="pk-text-sm">{activeChat.contact.status === 'online' ? 'Active Now' : 'Offline'}</span>
213
+ </div>
214
+ </div>
215
+
216
+ <div style={{ flex: 1, padding: '2rem', overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: '1.5rem', background: 'linear-gradient(rgba(248, 250, 252, 0.9), rgba(248, 250, 252, 0.9)), url("https://www.transparenttextures.com/patterns/cubes.png")' }}>
217
+ {activeChat.messages.length === 0 && (
218
+ <div className="pk-empty-state" style={{ margin: 'auto', border: 'none', background: 'transparent' }}>
219
+ Say Hello 👋
220
+ </div>
221
+ )}
222
+
223
+ {activeChat.messages.map(msg => {
224
+ const isMe = msg.senderId === chatMessagingMockData.currentUser.id;
225
+ return (
226
+ <div key={msg.id} className="pk-flex" style={{ justifyContent: isMe ? 'flex-end' : 'flex-start' }}>
227
+ {!isMe && <img src={activeChat.contact.avatar} alt="" style={{ width: '32px', height: '32px', borderRadius: '50%', marginRight: '0.75rem', alignSelf: 'flex-end' }} />}
228
+ <div style={{ maxWidth: '70%' }}>
229
+ <div style={{
230
+ padding: '1rem 1.25rem',
231
+ background: isMe ? 'linear-gradient(135deg, var(--pk-primary), var(--pk-primary-hover))' : 'var(--pk-bg-card)',
232
+ color: isMe ? 'white' : 'var(--pk-text-main)',
233
+ borderRadius: isMe ? '16px 16px 4px 16px' : '16px 16px 16px 4px',
234
+ boxShadow: 'var(--pk-shadow-md)'
235
+ }}>
236
+ {msg.text}
237
+ </div>
238
+ <div className="pk-text-sm" style={{ marginTop: '0.25rem', textAlign: isMe ? 'right' : 'left', fontSize: '0.75rem' }}>
239
+ {msg.timestamp}
240
+ </div>
241
+ </div>
242
+ </div>
243
+ )
244
+ })}
245
+
246
+ {isTyping && (
247
+ <div className="pk-flex" style={{ justifyContent: 'flex-start' }}>
248
+ <img src={activeChat.contact.avatar} alt="" style={{ width: '32px', height: '32px', borderRadius: '50%', marginRight: '0.75rem', alignSelf: 'flex-end' }} />
249
+ <div style={{ padding: '1rem', background: 'var(--pk-bg-card)', borderRadius: '16px 16px 16px 4px', boxShadow: 'var(--pk-shadow-sm)', display: 'flex', gap: '4px' }}>
250
+ <span className="typing-dot"></span>
251
+ <span className="typing-dot" style={{ animationDelay: '0.2s' }}></span>
252
+ <span className="typing-dot" style={{ animationDelay: '0.4s' }}></span>
253
+ </div>
254
+ </div>
255
+ )}
256
+ <div ref={messagesEndRef} />
257
+ </div>
258
+
259
+ <div className="pk-card" style={{ borderRadius: 0, padding: '1.5rem', borderTop: '1px solid var(--pk-border)' }}>
260
+ <form onSubmit={handleSendMessage} className="pk-flex pk-gap-4 pk-items-center">
261
+ <button type="button" className="pk-btn pk-btn-outline" style={{ borderRadius: '50%', padding: '0.75rem' }}>📎</button>
262
+ <input
263
+ type="text"
264
+ className="pk-input"
265
+ style={{ flex: 1, borderRadius: 'var(--pk-radius-full)', padding: '1rem 1.5rem' }}
266
+ placeholder="Type your message..."
267
+ value={newMessage}
268
+ onChange={e => setNewMessage(e.target.value)}
269
+ />
270
+ <button type="submit" className="pk-btn pk-btn-primary" style={{ borderRadius: '50%', padding: '0.75rem', aspectRatio: '1', width: '50px' }}>
271
+
272
+ </button>
273
+ </form>
274
+ </div>
275
+ </>
276
+ ) : (
277
+ <div className="pk-flex pk-items-center pk-justify-center" style={{ flex: 1 }}>
278
+ <div className="pk-empty-state" style={{ border: 'none', background: 'transparent' }}>
279
+ <div className="pk-empty-icon" style={{ fontSize: '5rem' }}>💬</div>
280
+ <h3 className="pk-heading-md">Welcome to PopSite Messages</h3>
281
+ <p className="pk-text-body">Select a conversation from the sidebar to begin.</p>
282
+ </div>
283
+ </div>
284
+ )}
285
+ </div>
286
+
287
+ <style>{`
288
+ .typing-dot { width: 8px; height: 8px; background-color: var(--pk-text-muted); border-radius: 50%; opacity: 0.5; animation: typingBounce 1.4s infinite ease-in-out; }
289
+ @keyframes typingBounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-4px); } }
290
+ `}</style>
291
+ <ToastContainer />
292
+ </div>
293
+ );
294
+ }