Spaces:
Running
Running
Add 1 files
Browse files- index.html +437 -318
index.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>
|
| 7 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
| 9 |
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
|
@@ -11,53 +11,47 @@
|
|
| 11 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 12 |
<style>
|
| 13 |
:root {
|
| 14 |
-
--primary: #
|
| 15 |
-
--secondary: #
|
|
|
|
| 16 |
}
|
| 17 |
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
}
|
| 23 |
|
| 24 |
-
.
|
| 25 |
-
|
| 26 |
-
}
|
| 27 |
-
|
| 28 |
-
.gradient-text {
|
| 29 |
-
background: linear-gradient(90deg, var(--primary), var(--secondary));
|
| 30 |
-
-webkit-background-clip: text;
|
| 31 |
-
background-clip: text;
|
| 32 |
-
color: transparent;
|
| 33 |
}
|
| 34 |
|
| 35 |
-
.
|
| 36 |
-
|
| 37 |
-
transform: translateY(10px);
|
| 38 |
}
|
| 39 |
|
| 40 |
-
.
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
transition: all 300ms ease-out;
|
| 44 |
}
|
| 45 |
|
| 46 |
.sidebar-transition {
|
| 47 |
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 48 |
}
|
| 49 |
|
| 50 |
-
.
|
| 51 |
-
|
| 52 |
}
|
| 53 |
|
| 54 |
-
|
| 55 |
-
|
|
|
|
| 56 |
}
|
| 57 |
|
| 58 |
-
.
|
| 59 |
-
|
| 60 |
-
|
|
|
|
| 61 |
}
|
| 62 |
</style>
|
| 63 |
</head>
|
|
@@ -67,53 +61,143 @@
|
|
| 67 |
<script type="text/babel">
|
| 68 |
const { useState, useEffect, useRef } = React;
|
| 69 |
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
return (
|
| 72 |
-
<header className="bg-white border-b border-gray-200 px-6 py-
|
| 73 |
<div className="flex items-center space-x-4">
|
| 74 |
<button
|
| 75 |
onClick={toggleSidebar}
|
| 76 |
-
className="
|
| 77 |
>
|
| 78 |
<i className="fas fa-bars text-xl"></i>
|
| 79 |
</button>
|
| 80 |
<div className="flex items-center">
|
| 81 |
-
<div className="w-8 h-8 rounded-full bg-gradient-to-r from-
|
| 82 |
-
<i className="fas fa-
|
| 83 |
</div>
|
| 84 |
-
<h1 className="ml-3 text-xl font-bold
|
| 85 |
</div>
|
| 86 |
</div>
|
| 87 |
<div className="flex items-center space-x-4">
|
| 88 |
-
<
|
| 89 |
-
<
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
</div>
|
| 95 |
</header>
|
| 96 |
);
|
| 97 |
};
|
| 98 |
|
| 99 |
-
const Sidebar = ({ isOpen, closeSidebar }) => {
|
| 100 |
-
const [
|
|
|
|
| 101 |
|
| 102 |
-
const
|
| 103 |
-
{ id: '
|
| 104 |
-
{
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
];
|
| 109 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
return (
|
| 111 |
-
<aside className={`fixed
|
| 112 |
<div className="p-4 border-b border-gray-200 flex items-center justify-between">
|
| 113 |
-
<h2 className="text-lg font-semibold text-gray-800">
|
| 114 |
<button
|
| 115 |
onClick={closeSidebar}
|
| 116 |
-
className="
|
| 117 |
>
|
| 118 |
<i className="fas fa-times"></i>
|
| 119 |
</button>
|
|
@@ -122,39 +206,62 @@
|
|
| 122 |
<div className="flex-1 overflow-y-auto custom-scrollbar">
|
| 123 |
<nav className="p-4">
|
| 124 |
<ul className="space-y-1">
|
| 125 |
-
{
|
| 126 |
-
<li key={
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
</li>
|
| 135 |
))}
|
| 136 |
</ul>
|
| 137 |
</nav>
|
| 138 |
-
|
| 139 |
-
<div className="px-4 pb-4">
|
| 140 |
-
<div className="bg-indigo-50 rounded-xl p-4">
|
| 141 |
-
<h3 className="text-sm font-medium text-indigo-800 mb-2">Daily Challenge</h3>
|
| 142 |
-
<p className="text-xs text-indigo-600 mb-3">Complete 3 journal entries to unlock premium features</p>
|
| 143 |
-
<div className="w-full bg-indigo-100 rounded-full h-2">
|
| 144 |
-
<div className="bg-indigo-500 h-2 rounded-full" style={{ width: '33%' }}></div>
|
| 145 |
-
</div>
|
| 146 |
-
</div>
|
| 147 |
-
</div>
|
| 148 |
</div>
|
| 149 |
|
| 150 |
<div className="p-4 border-t border-gray-200">
|
| 151 |
-
<div className="
|
| 152 |
-
<div className="
|
| 153 |
-
<
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
<
|
| 157 |
-
|
|
|
|
|
|
|
| 158 |
</div>
|
| 159 |
</div>
|
| 160 |
</div>
|
|
@@ -162,235 +269,246 @@
|
|
| 162 |
);
|
| 163 |
};
|
| 164 |
|
| 165 |
-
const
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
|
| 179 |
return (
|
| 180 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
<div className="p-4 border-b border-gray-200 flex items-center justify-between">
|
| 182 |
-
<
|
| 183 |
-
<button
|
| 184 |
-
|
| 185 |
-
className="md:hidden text-gray-500 hover:text-gray-700 focus:outline-none"
|
| 186 |
-
>
|
| 187 |
-
<i className="fas fa-times"></i>
|
| 188 |
</button>
|
| 189 |
</div>
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
<
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
>
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
))}
|
| 206 |
-
</
|
| 207 |
-
</
|
| 208 |
-
|
| 209 |
-
<div className="mb-8">
|
| 210 |
-
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3">Recent Activities</h3>
|
| 211 |
-
<div className="space-y-3">
|
| 212 |
-
{recentActivities.map((activity, index) => (
|
| 213 |
-
<div key={index} className="flex items-start">
|
| 214 |
-
<div className="mt-1 mr-3">
|
| 215 |
-
<div className="w-8 h-8 rounded-full bg-gray-100 flex items-center justify-center text-gray-500">
|
| 216 |
-
<i className={`fas ${activity.icon} text-xs`}></i>
|
| 217 |
-
</div>
|
| 218 |
-
</div>
|
| 219 |
-
<div className="flex-1">
|
| 220 |
-
<p className="text-sm font-medium text-gray-800">{activity.label}</p>
|
| 221 |
-
<p className="text-xs text-gray-500">{activity.time}</p>
|
| 222 |
-
</div>
|
| 223 |
-
</div>
|
| 224 |
-
))}
|
| 225 |
-
</div>
|
| 226 |
-
</div>
|
| 227 |
-
|
| 228 |
-
<div>
|
| 229 |
-
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3">Personalized Suggestions</h3>
|
| 230 |
-
<div className="bg-gradient-to-r from-indigo-50 to-purple-50 rounded-xl p-4">
|
| 231 |
-
<p className="text-sm font-medium text-gray-800 mb-2">Based on your patterns:</p>
|
| 232 |
-
<ul className="text-xs text-gray-600 space-y-2">
|
| 233 |
-
<li className="flex items-start">
|
| 234 |
-
<i className="fas fa-check-circle text-indigo-400 mr-2 mt-0.5"></i>
|
| 235 |
-
<span>Schedule a weekly review at 4pm Fridays</span>
|
| 236 |
-
</li>
|
| 237 |
-
<li className="flex items-start">
|
| 238 |
-
<i className="fas fa-check-circle text-indigo-400 mr-2 mt-0.5"></i>
|
| 239 |
-
<span>Automate your morning routine</span>
|
| 240 |
-
</li>
|
| 241 |
-
<li className="flex items-start">
|
| 242 |
-
<i className="fas fa-check-circle text-indigo-400 mr-2 mt-0.5"></i>
|
| 243 |
-
<span>Try the 5-minute journal prompt</span>
|
| 244 |
-
</li>
|
| 245 |
-
</ul>
|
| 246 |
-
</div>
|
| 247 |
-
</div>
|
| 248 |
</div>
|
| 249 |
-
</
|
| 250 |
);
|
| 251 |
};
|
| 252 |
|
| 253 |
-
const
|
| 254 |
return (
|
| 255 |
-
<div className=
|
| 256 |
-
<div className=
|
| 257 |
-
<
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
</div>
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
className={`text-xs px-3 py-1 rounded-full ${isUser ? 'bg-indigo-600 text-white' : 'bg-gray-100 text-gray-700'} hover:opacity-90 transition-opacity`}
|
| 273 |
-
>
|
| 274 |
-
{action}
|
| 275 |
-
</button>
|
| 276 |
-
))}
|
| 277 |
</div>
|
| 278 |
-
|
| 279 |
</div>
|
| 280 |
-
|
| 281 |
-
<p className={`text-xs mt-2 ${isUser ? 'text-indigo-200' : 'text-gray-500'}`}>
|
| 282 |
-
{new Date(message.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
| 283 |
-
</p>
|
| 284 |
</div>
|
| 285 |
</div>
|
| 286 |
);
|
| 287 |
};
|
| 288 |
|
| 289 |
-
const
|
| 290 |
-
const [
|
| 291 |
-
const inputRef = useRef(null);
|
| 292 |
-
|
| 293 |
-
const handleSubmit = (e) => {
|
| 294 |
-
e.preventDefault();
|
| 295 |
-
if (input.trim()) {
|
| 296 |
-
onSendMessage(input);
|
| 297 |
-
setInput('');
|
| 298 |
-
}
|
| 299 |
-
};
|
| 300 |
|
| 301 |
-
const
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
};
|
| 306 |
|
| 307 |
-
|
| 308 |
-
inputRef.current.focus();
|
| 309 |
-
}, []);
|
| 310 |
-
|
| 311 |
return (
|
| 312 |
-
<
|
| 313 |
-
<div className="
|
| 314 |
-
<
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
<button
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
className={`absolute right-3 bottom-3 w-8 h-8 rounded-full flex items-center justify-center ${input.trim() ? 'bg-indigo-500 text-white' : 'bg-gray-200 text-gray-400'}`}
|
| 328 |
>
|
| 329 |
-
|
| 330 |
</button>
|
| 331 |
</div>
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 343 |
</div>
|
| 344 |
-
<p className="text-xs text-gray-500">
|
| 345 |
-
NOVA v2.1 · <span className="text-indigo-500">Learning active</span>
|
| 346 |
-
</p>
|
| 347 |
</div>
|
| 348 |
</div>
|
| 349 |
-
</
|
| 350 |
);
|
| 351 |
};
|
| 352 |
|
| 353 |
-
const
|
| 354 |
-
const
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
|
|
|
| 359 |
|
| 360 |
return (
|
| 361 |
-
<div className="
|
| 362 |
-
<
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
<
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
<
|
| 370 |
-
<
|
| 371 |
-
|
| 372 |
-
<button className="bg-indigo-50 hover:bg-indigo-100 text-indigo-600 text-xs px-3 py-2 rounded-lg">
|
| 373 |
-
Journal prompt
|
| 374 |
-
</button>
|
| 375 |
-
<button className="bg-indigo-50 hover:bg-indigo-100 text-indigo-600 text-xs px-3 py-2 rounded-lg">
|
| 376 |
-
Task automation
|
| 377 |
-
</button>
|
| 378 |
-
<button className="bg-indigo-50 hover:bg-indigo-100 text-indigo-600 text-xs px-3 py-2 rounded-lg">
|
| 379 |
-
Resource tips
|
| 380 |
-
</button>
|
| 381 |
-
<button className="bg-indigo-50 hover:bg-indigo-100 text-indigo-600 text-xs px-3 py-2 rounded-lg">
|
| 382 |
-
Energy check
|
| 383 |
-
</button>
|
| 384 |
-
</div>
|
| 385 |
-
</div>
|
| 386 |
-
</div>
|
| 387 |
-
|
| 388 |
-
{/* Chat messages */}
|
| 389 |
-
{messages.map((message, index) => (
|
| 390 |
-
<Message key={index} message={message} isUser={message.isUser} />
|
| 391 |
))}
|
| 392 |
-
|
| 393 |
-
<div ref={messagesEndRef} />
|
| 394 |
</div>
|
| 395 |
</div>
|
| 396 |
);
|
|
@@ -398,47 +516,30 @@
|
|
| 398 |
|
| 399 |
const App = () => {
|
| 400 |
const [sidebarOpen, setSidebarOpen] = useState(false);
|
| 401 |
-
const [panelOpen, setPanelOpen] = useState(false);
|
| 402 |
-
const [messages, setMessages] = useState([
|
| 403 |
-
{
|
| 404 |
-
text: "Welcome back! I've analyzed your patterns and have some personalized suggestions for you today.",
|
| 405 |
-
timestamp: new Date(),
|
| 406 |
-
isUser: false,
|
| 407 |
-
actions: ["View suggestions", "Dismiss"]
|
| 408 |
-
}
|
| 409 |
-
]);
|
| 410 |
|
| 411 |
-
const
|
| 412 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 413 |
|
| 414 |
-
const
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
}
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
];
|
| 431 |
-
|
| 432 |
-
const botMessage = {
|
| 433 |
-
text: responses[Math.floor(Math.random() * responses.length)],
|
| 434 |
-
timestamp: new Date(),
|
| 435 |
-
isUser: false,
|
| 436 |
-
actions: ["Yes please", "Not now", "More options"]
|
| 437 |
-
};
|
| 438 |
-
|
| 439 |
-
setMessages(prev => [...prev, botMessage]);
|
| 440 |
-
}, 1000 + Math.random() * 2000);
|
| 441 |
-
};
|
| 442 |
|
| 443 |
return (
|
| 444 |
<div className="flex h-screen overflow-hidden bg-gray-50">
|
|
@@ -447,14 +548,32 @@
|
|
| 447 |
<div className="flex-1 flex flex-col overflow-hidden">
|
| 448 |
<Navbar toggleSidebar={toggleSidebar} />
|
| 449 |
|
| 450 |
-
<
|
| 451 |
-
<
|
| 452 |
-
|
| 453 |
-
<
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 458 |
</div>
|
| 459 |
</div>
|
| 460 |
);
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>FinEdge | Accounting System</title>
|
| 7 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
| 9 |
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
|
|
|
| 11 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 12 |
<style>
|
| 13 |
:root {
|
| 14 |
+
--primary: #3b82f6;
|
| 15 |
+
--secondary: #10b981;
|
| 16 |
+
--accent: #6366f1;
|
| 17 |
}
|
| 18 |
|
| 19 |
+
.grid-stack {
|
| 20 |
+
display: grid;
|
| 21 |
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
| 22 |
+
gap: 1.5rem;
|
| 23 |
}
|
| 24 |
|
| 25 |
+
.custom-scrollbar::-webkit-scrollbar {
|
| 26 |
+
width: 6px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
}
|
| 28 |
|
| 29 |
+
.custom-scrollbar::-webkit-scrollbar-track {
|
| 30 |
+
background: rgba(0, 0, 0, 0.05);
|
|
|
|
| 31 |
}
|
| 32 |
|
| 33 |
+
.custom-scrollbar::-webkit-scrollbar-thumb {
|
| 34 |
+
background: rgba(0, 0, 0, 0.2);
|
| 35 |
+
border-radius: 3px;
|
|
|
|
| 36 |
}
|
| 37 |
|
| 38 |
.sidebar-transition {
|
| 39 |
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 40 |
}
|
| 41 |
|
| 42 |
+
.fade-in {
|
| 43 |
+
animation: fadeIn 0.3s ease-in;
|
| 44 |
}
|
| 45 |
|
| 46 |
+
@keyframes fadeIn {
|
| 47 |
+
from { opacity: 0; }
|
| 48 |
+
to { opacity: 1; }
|
| 49 |
}
|
| 50 |
|
| 51 |
+
.progress-ring__circle {
|
| 52 |
+
transition: stroke-dashoffset 0.35s;
|
| 53 |
+
transform: rotate(-90deg);
|
| 54 |
+
transform-origin: 50% 50%;
|
| 55 |
}
|
| 56 |
</style>
|
| 57 |
</head>
|
|
|
|
| 61 |
<script type="text/babel">
|
| 62 |
const { useState, useEffect, useRef } = React;
|
| 63 |
|
| 64 |
+
// TypeScript type definitions
|
| 65 |
+
type MenuItem = {
|
| 66 |
+
id: string;
|
| 67 |
+
icon: string;
|
| 68 |
+
label: string;
|
| 69 |
+
subItems?: MenuItem[];
|
| 70 |
+
};
|
| 71 |
+
|
| 72 |
+
type FinancialCard = {
|
| 73 |
+
id: string;
|
| 74 |
+
title: string;
|
| 75 |
+
value: string;
|
| 76 |
+
change: number;
|
| 77 |
+
icon: string;
|
| 78 |
+
color: string;
|
| 79 |
+
};
|
| 80 |
+
|
| 81 |
+
type Transaction = {
|
| 82 |
+
id: string;
|
| 83 |
+
date: string;
|
| 84 |
+
description: string;
|
| 85 |
+
amount: number;
|
| 86 |
+
type: 'income' | 'expense';
|
| 87 |
+
category: string;
|
| 88 |
+
status: 'completed' | 'pending' | 'rejected';
|
| 89 |
+
};
|
| 90 |
+
|
| 91 |
+
type Invoice = {
|
| 92 |
+
id: string;
|
| 93 |
+
client: string;
|
| 94 |
+
amount: number;
|
| 95 |
+
dueDate: string;
|
| 96 |
+
status: 'paid' | 'overdue' | 'pending';
|
| 97 |
+
};
|
| 98 |
+
|
| 99 |
+
type ChartData = {
|
| 100 |
+
labels: string[];
|
| 101 |
+
datasets: {
|
| 102 |
+
label: string;
|
| 103 |
+
data: number[];
|
| 104 |
+
backgroundColor: string[];
|
| 105 |
+
borderColor: string[];
|
| 106 |
+
borderWidth: number;
|
| 107 |
+
}[];
|
| 108 |
+
};
|
| 109 |
+
|
| 110 |
+
// Components
|
| 111 |
+
const Navbar = ({ toggleSidebar }: { toggleSidebar: () => void }) => {
|
| 112 |
return (
|
| 113 |
+
<header className="bg-white border-b border-gray-200 px-6 py-3 flex items-center justify-between sticky top-0 z-10 shadow-sm">
|
| 114 |
<div className="flex items-center space-x-4">
|
| 115 |
<button
|
| 116 |
onClick={toggleSidebar}
|
| 117 |
+
className="lg:hidden text-gray-500 hover:text-gray-700 focus:outline-none"
|
| 118 |
>
|
| 119 |
<i className="fas fa-bars text-xl"></i>
|
| 120 |
</button>
|
| 121 |
<div className="flex items-center">
|
| 122 |
+
<div className="w-8 h-8 rounded-full bg-gradient-to-r from-blue-500 to-emerald-400 flex items-center justify-center text-white">
|
| 123 |
+
<i className="fas fa-calculator"></i>
|
| 124 |
</div>
|
| 125 |
+
<h1 className="ml-3 text-xl font-bold text-blue-600">FinEdge</h1>
|
| 126 |
</div>
|
| 127 |
</div>
|
| 128 |
<div className="flex items-center space-x-4">
|
| 129 |
+
<div className="relative hidden md:block">
|
| 130 |
+
<input
|
| 131 |
+
type="text"
|
| 132 |
+
placeholder="Search transactions, reports..."
|
| 133 |
+
className="pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm w-64"
|
| 134 |
+
/>
|
| 135 |
+
<i className="fas fa-search absolute left-3 top-3 text-gray-400"></i>
|
| 136 |
+
</div>
|
| 137 |
+
<button className="text-gray-500 hover:text-gray-700 focus:outline-none relative">
|
| 138 |
+
<i className="fas fa-bell"></i>
|
| 139 |
+
<span className="absolute top-0 right-0 w-2 h-2 bg-red-500 rounded-full"></span>
|
| 140 |
</button>
|
| 141 |
+
<div className="flex items-center space-x-2">
|
| 142 |
+
<div className="w-8 h-8 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center">
|
| 143 |
+
<i className="fas fa-user"></i>
|
| 144 |
+
</div>
|
| 145 |
+
<span className="hidden md:inline text-sm font-medium">Admin</span>
|
| 146 |
+
</div>
|
| 147 |
</div>
|
| 148 |
</header>
|
| 149 |
);
|
| 150 |
};
|
| 151 |
|
| 152 |
+
const Sidebar = ({ isOpen, closeSidebar }: { isOpen: boolean, closeSidebar: () => void }) => {
|
| 153 |
+
const [activeMenu, setActiveMenu] = useState('dashboard');
|
| 154 |
+
const [expandedMenus, setExpandedMenus] = useState<Record<string, boolean>>({});
|
| 155 |
|
| 156 |
+
const menuItems: MenuItem[] = [
|
| 157 |
+
{ id: 'dashboard', icon: 'fa-tachometer-alt', label: 'Dashboard' },
|
| 158 |
+
{
|
| 159 |
+
id: 'accounting',
|
| 160 |
+
icon: 'fa-calculator',
|
| 161 |
+
label: 'Accounting',
|
| 162 |
+
subItems: [
|
| 163 |
+
{ id: 'general-ledger', icon: 'fa-book', label: 'General Ledger' },
|
| 164 |
+
{ id: 'accounts-payable', icon: 'fa-file-invoice-dollar', label: 'Accounts Payable' },
|
| 165 |
+
{ id: 'accounts-receivable', icon: 'fa-hand-holding-usd', label: 'Accounts Receivable' },
|
| 166 |
+
{ id: 'banking', icon: 'fa-university', label: 'Banking' },
|
| 167 |
+
]
|
| 168 |
+
},
|
| 169 |
+
{
|
| 170 |
+
id: 'financial-reports',
|
| 171 |
+
icon: 'fa-chart-bar',
|
| 172 |
+
label: 'Financial Reports',
|
| 173 |
+
subItems: [
|
| 174 |
+
{ id: 'balance-sheet', icon: 'fa-balance-scale', label: 'Balance Sheet' },
|
| 175 |
+
{ id: 'income-statement', icon: 'fa-file-alt', label: 'Income Statement' },
|
| 176 |
+
{ id: 'cash-flow', icon: 'fa-exchange-alt', label: 'Cash Flow' },
|
| 177 |
+
]
|
| 178 |
+
},
|
| 179 |
+
{ id: 'invoicing', icon: 'fa-file-invoice', label: 'Invoicing' },
|
| 180 |
+
{ id: 'expenses', icon: 'fa-receipt', label: 'Expenses' },
|
| 181 |
+
{ id: 'payroll', icon: 'fa-users', label: 'Payroll' },
|
| 182 |
+
{ id: 'tax', icon: 'fa-file-contract', label: 'Tax' },
|
| 183 |
+
{ id: 'inventory', icon: 'fa-boxes', label: 'Inventory' },
|
| 184 |
+
{ id: 'settings', icon: 'fa-cog', label: 'Settings' },
|
| 185 |
];
|
| 186 |
|
| 187 |
+
const toggleSubMenu = (menuId: string) => {
|
| 188 |
+
setExpandedMenus(prev => ({
|
| 189 |
+
...prev,
|
| 190 |
+
[menuId]: !prev[menuId]
|
| 191 |
+
}));
|
| 192 |
+
};
|
| 193 |
+
|
| 194 |
return (
|
| 195 |
+
<aside className={`fixed lg:relative inset-y-0 left-0 w-64 bg-white border-r border-gray-200 flex flex-col z-20 sidebar-transition ${isOpen ? 'translate-x-0' : '-translate-x-full'} lg:translate-x-0`}>
|
| 196 |
<div className="p-4 border-b border-gray-200 flex items-center justify-between">
|
| 197 |
+
<h2 className="text-lg font-semibold text-gray-800">Menu</h2>
|
| 198 |
<button
|
| 199 |
onClick={closeSidebar}
|
| 200 |
+
className="lg:hidden text-gray-500 hover:text-gray-700 focus:outline-none"
|
| 201 |
>
|
| 202 |
<i className="fas fa-times"></i>
|
| 203 |
</button>
|
|
|
|
| 206 |
<div className="flex-1 overflow-y-auto custom-scrollbar">
|
| 207 |
<nav className="p-4">
|
| 208 |
<ul className="space-y-1">
|
| 209 |
+
{menuItems.map(item => (
|
| 210 |
+
<li key={item.id}>
|
| 211 |
+
{item.subItems ? (
|
| 212 |
+
<div>
|
| 213 |
+
<button
|
| 214 |
+
onClick={() => toggleSubMenu(item.id)}
|
| 215 |
+
className={`w-full text-left px-4 py-3 rounded-lg flex items-center justify-between transition-colors ${activeMenu === item.id ? 'bg-blue-50 text-blue-600' : 'hover:bg-gray-50 text-gray-700'}`}
|
| 216 |
+
>
|
| 217 |
+
<div className="flex items-center">
|
| 218 |
+
<i className={`fas ${item.icon} mr-3 ${activeMenu === item.id ? 'text-blue-500' : 'text-gray-500'}`}></i>
|
| 219 |
+
<span className="font-medium">{item.label}</span>
|
| 220 |
+
</div>
|
| 221 |
+
<i className={`fas fa-chevron-down text-xs transition-transform ${expandedMenus[item.id] ? 'transform rotate-180' : ''}`}></i>
|
| 222 |
+
</button>
|
| 223 |
+
|
| 224 |
+
{expandedMenus[item.id] && (
|
| 225 |
+
<ul className="ml-8 mt-1 space-y-1">
|
| 226 |
+
{item.subItems.map(subItem => (
|
| 227 |
+
<li key={subItem.id}>
|
| 228 |
+
<button
|
| 229 |
+
onClick={() => setActiveMenu(subItem.id)}
|
| 230 |
+
className={`w-full text-left px-4 py-2 rounded-lg flex items-center text-sm ${activeMenu === subItem.id ? 'bg-blue-50 text-blue-600' : 'hover:bg-gray-50 text-gray-700'}`}
|
| 231 |
+
>
|
| 232 |
+
<i className={`fas ${subItem.icon} mr-3 ${activeMenu === subItem.id ? 'text-blue-500' : 'text-gray-500'}`}></i>
|
| 233 |
+
{subItem.label}
|
| 234 |
+
</button>
|
| 235 |
+
</li>
|
| 236 |
+
))}
|
| 237 |
+
</ul>
|
| 238 |
+
)}
|
| 239 |
+
</div>
|
| 240 |
+
) : (
|
| 241 |
+
<button
|
| 242 |
+
onClick={() => setActiveMenu(item.id)}
|
| 243 |
+
className={`w-full text-left px-4 py-3 rounded-lg flex items-center transition-colors ${activeMenu === item.id ? 'bg-blue-50 text-blue-600' : 'hover:bg-gray-50 text-gray-700'}`}
|
| 244 |
+
>
|
| 245 |
+
<i className={`fas ${item.icon} mr-3 ${activeMenu === item.id ? 'text-blue-500' : 'text-gray-500'}`}></i>
|
| 246 |
+
<span className="font-medium">{item.label}</span>
|
| 247 |
+
</button>
|
| 248 |
+
)}
|
| 249 |
</li>
|
| 250 |
))}
|
| 251 |
</ul>
|
| 252 |
</nav>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
</div>
|
| 254 |
|
| 255 |
<div className="p-4 border-t border-gray-200">
|
| 256 |
+
<div className="bg-blue-50 rounded-lg p-3">
|
| 257 |
+
<div className="flex items-center">
|
| 258 |
+
<div className="w-10 h-10 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center">
|
| 259 |
+
<i className="fas fa-question-circle"></i>
|
| 260 |
+
</div>
|
| 261 |
+
<div className="ml-3">
|
| 262 |
+
<p className="text-sm font-medium text-gray-800">Need help?</p>
|
| 263 |
+
<button className="text-xs text-blue-600 hover:underline">Contact support</button>
|
| 264 |
+
</div>
|
| 265 |
</div>
|
| 266 |
</div>
|
| 267 |
</div>
|
|
|
|
| 269 |
);
|
| 270 |
};
|
| 271 |
|
| 272 |
+
const FinancialCard = ({ title, value, change, icon, color }: FinancialCard) => {
|
| 273 |
+
return (
|
| 274 |
+
<div className="bg-white rounded-xl shadow-sm p-5 border border-gray-200 hover:shadow-md transition-shadow">
|
| 275 |
+
<div className="flex justify-between items-start">
|
| 276 |
+
<div>
|
| 277 |
+
<p className="text-sm font-medium text-gray-500 mb-1">{title}</p>
|
| 278 |
+
<p className="text-2xl font-bold text-gray-800">{value}</p>
|
| 279 |
+
<div className={`flex items-center mt-2 text-sm ${change >= 0 ? 'text-green-500' : 'text-red-500'}`}>
|
| 280 |
+
{change >= 0 ? (
|
| 281 |
+
<i className="fas fa-arrow-up mr-1"></i>
|
| 282 |
+
) : (
|
| 283 |
+
<i className="fas fa-arrow-down mr-1"></i>
|
| 284 |
+
)}
|
| 285 |
+
<span>{Math.abs(change)}% from last month</span>
|
| 286 |
+
</div>
|
| 287 |
+
</div>
|
| 288 |
+
<div className={`w-12 h-12 rounded-full ${color} bg-opacity-10 flex items-center justify-center`}>
|
| 289 |
+
<i className={`fas ${icon} ${color} text-xl`}></i>
|
| 290 |
+
</div>
|
| 291 |
+
</div>
|
| 292 |
+
</div>
|
| 293 |
+
);
|
| 294 |
+
};
|
| 295 |
+
|
| 296 |
+
const ProgressRing = ({ radius, stroke, progress }: { radius: number, stroke: number, progress: number }) => {
|
| 297 |
+
const normalizedRadius = radius - stroke * 2;
|
| 298 |
+
const circumference = normalizedRadius * 2 * Math.PI;
|
| 299 |
+
const strokeDashoffset = circumference - progress / 100 * circumference;
|
| 300 |
|
| 301 |
return (
|
| 302 |
+
<svg height={radius * 2} width={radius * 2} className="transform -rotate-90">
|
| 303 |
+
<circle
|
| 304 |
+
stroke="currentColor"
|
| 305 |
+
fill="transparent"
|
| 306 |
+
strokeWidth={stroke}
|
| 307 |
+
strokeDasharray={circumference + ' ' + circumference}
|
| 308 |
+
style={{ strokeDashoffset }}
|
| 309 |
+
r={normalizedRadius}
|
| 310 |
+
cx={radius}
|
| 311 |
+
cy={radius}
|
| 312 |
+
className="text-blue-100"
|
| 313 |
+
/>
|
| 314 |
+
<circle
|
| 315 |
+
stroke="currentColor"
|
| 316 |
+
fill="transparent"
|
| 317 |
+
strokeWidth={stroke}
|
| 318 |
+
strokeDasharray={circumference + ' ' + circumference}
|
| 319 |
+
style={{ strokeDashoffset }}
|
| 320 |
+
r={normalizedRadius}
|
| 321 |
+
cx={radius}
|
| 322 |
+
cy={radius}
|
| 323 |
+
className="text-blue-500"
|
| 324 |
+
/>
|
| 325 |
+
</svg>
|
| 326 |
+
);
|
| 327 |
+
};
|
| 328 |
+
|
| 329 |
+
const TransactionsTable = ({ transactions }: { transactions: Transaction[] }) => {
|
| 330 |
+
return (
|
| 331 |
+
<div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
| 332 |
<div className="p-4 border-b border-gray-200 flex items-center justify-between">
|
| 333 |
+
<h3 className="font-medium text-gray-800">Recent Transactions</h3>
|
| 334 |
+
<button className="text-blue-600 text-sm font-medium hover:underline">
|
| 335 |
+
View all
|
|
|
|
|
|
|
|
|
|
| 336 |
</button>
|
| 337 |
</div>
|
| 338 |
+
<div className="overflow-x-auto">
|
| 339 |
+
<table className="min-w-full divide-y divide-gray-200">
|
| 340 |
+
<thead className="bg-gray-50">
|
| 341 |
+
<tr>
|
| 342 |
+
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
|
| 343 |
+
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Description</th>
|
| 344 |
+
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Category</th>
|
| 345 |
+
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Amount</th>
|
| 346 |
+
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
|
| 347 |
+
<th scope="col" className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
| 348 |
+
</tr>
|
| 349 |
+
</thead>
|
| 350 |
+
<tbody className="bg-white divide-y divide-gray-200">
|
| 351 |
+
{transactions.map((transaction) => (
|
| 352 |
+
<tr key={transaction.id} className="hover:bg-gray-50">
|
| 353 |
+
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{transaction.date}</td>
|
| 354 |
+
<td className="px-6 py-4 whitespace-nowrap">
|
| 355 |
+
<div className="text-sm font-medium text-gray-900">{transaction.description}</div>
|
| 356 |
+
</td>
|
| 357 |
+
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{transaction.category}</td>
|
| 358 |
+
<td className={`px-6 py-4 whitespace-nowrap text-sm font-medium ${transaction.type === 'income' ? 'text-green-600' : 'text-red-600'}`}>
|
| 359 |
+
{transaction.type === 'income' ? '+' : '-'}${Math.abs(transaction.amount).toFixed(2)}
|
| 360 |
+
</td>
|
| 361 |
+
<td className="px-6 py-4 whitespace-nowrap">
|
| 362 |
+
<span className={`px-2 py-1 text-xs rounded-full ${
|
| 363 |
+
transaction.status === 'completed' ? 'bg-green-100 text-green-800' :
|
| 364 |
+
transaction.status === 'pending' ? 'bg-yellow-100 text-yellow-800' :
|
| 365 |
+
'bg-red-100 text-red-800'
|
| 366 |
+
}`}>
|
| 367 |
+
{transaction.status.charAt(0).toUpperCase() + transaction.status.slice(1)}
|
| 368 |
+
</span>
|
| 369 |
+
</td>
|
| 370 |
+
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
| 371 |
+
<button className="text-blue-600 hover:text-blue-900 mr-3">Edit</button>
|
| 372 |
+
<button className="text-gray-600 hover:text-gray-900">View</button>
|
| 373 |
+
</td>
|
| 374 |
+
</tr>
|
| 375 |
))}
|
| 376 |
+
</tbody>
|
| 377 |
+
</table>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 378 |
</div>
|
| 379 |
+
</div>
|
| 380 |
);
|
| 381 |
};
|
| 382 |
|
| 383 |
+
const InvoicesList = ({ invoices }: { invoices: Invoice[] }) => {
|
| 384 |
return (
|
| 385 |
+
<div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
| 386 |
+
<div className="p-4 border-b border-gray-200 flex items-center justify-between">
|
| 387 |
+
<h3 className="font-medium text-gray-800">Recent Invoices</h3>
|
| 388 |
+
<button className="text-blue-600 text-sm font-medium hover:underline">
|
| 389 |
+
View all
|
| 390 |
+
</button>
|
| 391 |
+
</div>
|
| 392 |
+
<div className="divide-y divide-gray-200">
|
| 393 |
+
{invoices.map((invoice) => (
|
| 394 |
+
<div key={invoice.id} className="p-4 hover:bg-gray-50">
|
| 395 |
+
<div className="flex items-center justify-between">
|
| 396 |
+
<div>
|
| 397 |
+
<p className="font-medium text-gray-900">{invoice.client}</p>
|
| 398 |
+
<p className="text-sm text-gray-500">Due {invoice.dueDate}</p>
|
| 399 |
</div>
|
| 400 |
+
<div className="text-right">
|
| 401 |
+
<p className="font-medium text-gray-900">${invoice.amount.toFixed(2)}</p>
|
| 402 |
+
<span className={`text-xs px-2 py-1 rounded-full ${
|
| 403 |
+
invoice.status === 'paid' ? 'bg-green-100 text-green-800' :
|
| 404 |
+
invoice.status === 'overdue' ? 'bg-red-100 text-red-800' :
|
| 405 |
+
'bg-yellow-100 text-yellow-800'
|
| 406 |
+
}`}>
|
| 407 |
+
{invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}
|
| 408 |
+
</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 409 |
</div>
|
| 410 |
+
</div>
|
| 411 |
</div>
|
| 412 |
+
))}
|
|
|
|
|
|
|
|
|
|
| 413 |
</div>
|
| 414 |
</div>
|
| 415 |
);
|
| 416 |
};
|
| 417 |
|
| 418 |
+
const FinancialChart = () => {
|
| 419 |
+
const [timeRange, setTimeRange] = useState('month');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 420 |
|
| 421 |
+
const data: ChartData = {
|
| 422 |
+
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'],
|
| 423 |
+
datasets: [
|
| 424 |
+
{
|
| 425 |
+
label: 'Income',
|
| 426 |
+
data: [12000, 19000, 15000, 18000, 21000, 19000, 23000],
|
| 427 |
+
backgroundColor: ['rgba(59, 130, 246, 0.2)'],
|
| 428 |
+
borderColor: ['rgba(59, 130, 246, 1)'],
|
| 429 |
+
borderWidth: 2
|
| 430 |
+
},
|
| 431 |
+
{
|
| 432 |
+
label: 'Expenses',
|
| 433 |
+
data: [8000, 12000, 10000, 9000, 11000, 13000, 10000],
|
| 434 |
+
backgroundColor: ['rgba(239, 68, 68, 0.2)'],
|
| 435 |
+
borderColor: ['rgba(239, 68, 68, 1)'],
|
| 436 |
+
borderWidth: 2
|
| 437 |
+
}
|
| 438 |
+
]
|
| 439 |
};
|
| 440 |
|
| 441 |
+
// This is a simplified representation - in a real app you would use a charting library
|
|
|
|
|
|
|
|
|
|
| 442 |
return (
|
| 443 |
+
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-4">
|
| 444 |
+
<div className="flex items-center justify-between mb-4">
|
| 445 |
+
<h3 className="font-medium text-gray-800">Financial Overview</h3>
|
| 446 |
+
<div className="flex space-x-2">
|
| 447 |
+
<button
|
| 448 |
+
onClick={() => setTimeRange('week')}
|
| 449 |
+
className={`px-3 py-1 text-xs rounded-lg ${timeRange === 'week' ? 'bg-blue-100 text-blue-600' : 'bg-gray-100 text-gray-600'}`}
|
| 450 |
+
>
|
| 451 |
+
Week
|
| 452 |
+
</button>
|
| 453 |
+
<button
|
| 454 |
+
onClick={() => setTimeRange('month')}
|
| 455 |
+
className={`px-3 py-1 text-xs rounded-lg ${timeRange === 'month' ? 'bg-blue-100 text-blue-600' : 'bg-gray-100 text-gray-600'}`}
|
| 456 |
+
>
|
| 457 |
+
Month
|
| 458 |
+
</button>
|
| 459 |
<button
|
| 460 |
+
onClick={() => setTimeRange('year')}
|
| 461 |
+
className={`px-3 py-1 text-xs rounded-lg ${timeRange === 'year' ? 'bg-blue-100 text-blue-600' : 'bg-gray-100 text-gray-600'}`}
|
|
|
|
| 462 |
>
|
| 463 |
+
Year
|
| 464 |
</button>
|
| 465 |
</div>
|
| 466 |
+
</div>
|
| 467 |
+
|
| 468 |
+
<div className="relative h-64">
|
| 469 |
+
{/* This would be replaced with a real chart component */}
|
| 470 |
+
<div className="absolute inset-0 flex items-center justify-center text-gray-300">
|
| 471 |
+
<i className="fas fa-chart-line text-5xl"></i>
|
| 472 |
+
<p className="ml-4">Chart visualization would appear here</p>
|
| 473 |
+
</div>
|
| 474 |
+
|
| 475 |
+
{/* Legend */}
|
| 476 |
+
<div className="absolute bottom-0 left-0 right-0 flex justify-center space-x-4">
|
| 477 |
+
<div className="flex items-center">
|
| 478 |
+
<div className="w-3 h-3 bg-blue-500 rounded-full mr-2"></div>
|
| 479 |
+
<span className="text-xs text-gray-600">Income</span>
|
| 480 |
+
</div>
|
| 481 |
+
<div className="flex items-center">
|
| 482 |
+
<div className="w-3 h-3 bg-red-500 rounded-full mr-2"></div>
|
| 483 |
+
<span className="text-xs text-gray-600">Expenses</span>
|
| 484 |
</div>
|
|
|
|
|
|
|
|
|
|
| 485 |
</div>
|
| 486 |
</div>
|
| 487 |
+
</div>
|
| 488 |
);
|
| 489 |
};
|
| 490 |
|
| 491 |
+
const QuickActions = () => {
|
| 492 |
+
const actions = [
|
| 493 |
+
{ icon: 'fa-file-invoice', label: 'Create Invoice', color: 'bg-blue-100 text-blue-600' },
|
| 494 |
+
{ icon: 'fa-receipt', label: 'Record Expense', color: 'bg-purple-100 text-purple-600' },
|
| 495 |
+
{ icon: 'fa-exchange-alt', label: 'Bank Transfer', color: 'bg-green-100 text-green-600' },
|
| 496 |
+
{ icon: 'fa-file-export', label: 'Export Report', color: 'bg-yellow-100 text-yellow-600' },
|
| 497 |
+
];
|
| 498 |
|
| 499 |
return (
|
| 500 |
+
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-4">
|
| 501 |
+
<h3 className="font-medium text-gray-800 mb-4">Quick Actions</h3>
|
| 502 |
+
<div className="grid grid-cols-2 gap-3">
|
| 503 |
+
{actions.map((action, index) => (
|
| 504 |
+
<button
|
| 505 |
+
key={index}
|
| 506 |
+
className={`flex flex-col items-center justify-center p-3 rounded-lg hover:shadow-md transition-shadow ${action.color}`}
|
| 507 |
+
>
|
| 508 |
+
<i className={`fas ${action.icon} text-xl mb-2`}></i>
|
| 509 |
+
<span className="text-xs font-medium">{action.label}</span>
|
| 510 |
+
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 511 |
))}
|
|
|
|
|
|
|
| 512 |
</div>
|
| 513 |
</div>
|
| 514 |
);
|
|
|
|
| 516 |
|
| 517 |
const App = () => {
|
| 518 |
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 519 |
|
| 520 |
+
const financialCards: FinancialCard[] = [
|
| 521 |
+
{ id: '1', title: 'Total Revenue', value: '$48,526', change: 12.5, icon: 'fa-dollar-sign', color: 'text-green-500' },
|
| 522 |
+
{ id: '2', title: 'Total Expenses', value: '$26,143', change: -4.3, icon: 'fa-receipt', color: 'text-red-500' },
|
| 523 |
+
{ id: '3', title: 'Profit', value: '$22,383', change: 8.2, icon: 'fa-chart-line', color: 'text-blue-500' },
|
| 524 |
+
{ id: '4', title: 'Cash Flow', value: '$15,927', change: 5.7, icon: 'fa-exchange-alt', color: 'text-purple-500' },
|
| 525 |
+
];
|
| 526 |
|
| 527 |
+
const transactions: Transaction[] = [
|
| 528 |
+
{ id: '1', date: '2023-06-15', description: 'Website Development', amount: 4500, type: 'income', category: 'Services', status: 'completed' },
|
| 529 |
+
{ id: '2', date: '2023-06-14', description: 'Office Rent', amount: 1200, type: 'expense', category: 'Rent', status: 'completed' },
|
| 530 |
+
{ id: '3', date: '2023-06-13', description: 'Software Subscription', amount: 299, type: 'expense', category: 'Software', status: 'pending' },
|
| 531 |
+
{ id: '4', date: '2023-06-12', description: 'Consulting Fee', amount: 1800, type: 'income', category: 'Services', status: 'completed' },
|
| 532 |
+
{ id: '5', date: '2023-06-11', description: 'Marketing Campaign', amount: 750, type: 'expense', category: 'Marketing', status: 'rejected' },
|
| 533 |
+
];
|
| 534 |
+
|
| 535 |
+
const invoices: Invoice[] = [
|
| 536 |
+
{ id: '1', client: 'Acme Corp', amount: 5200, dueDate: '2023-06-20', status: 'pending' },
|
| 537 |
+
{ id: '2', client: 'Beta LLC', amount: 3200, dueDate: '2023-06-15', status: 'paid' },
|
| 538 |
+
{ id: '3', client: 'Gamma Inc', amount: 2800, dueDate: '2023-06-10', status: 'overdue' },
|
| 539 |
+
{ id: '4', client: 'Delta Co', amount: 4100, dueDate: '2023-06-25', status: 'pending' },
|
| 540 |
+
];
|
| 541 |
+
|
| 542 |
+
const toggleSidebar = () => setSidebarOpen(!sidebarOpen);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 543 |
|
| 544 |
return (
|
| 545 |
<div className="flex h-screen overflow-hidden bg-gray-50">
|
|
|
|
| 548 |
<div className="flex-1 flex flex-col overflow-hidden">
|
| 549 |
<Navbar toggleSidebar={toggleSidebar} />
|
| 550 |
|
| 551 |
+
<main className="flex-1 overflow-y-auto custom-scrollbar p-6">
|
| 552 |
+
<div className="max-w-7xl mx-auto">
|
| 553 |
+
{/* Financial Overview Cards */}
|
| 554 |
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
|
| 555 |
+
{financialCards.map(card => (
|
| 556 |
+
<FinancialCard key={card.id} {...card} />
|
| 557 |
+
))}
|
| 558 |
+
</div>
|
| 559 |
+
|
| 560 |
+
{/* Main Content Area */}
|
| 561 |
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
| 562 |
+
<div className="lg:col-span-2">
|
| 563 |
+
<FinancialChart />
|
| 564 |
+
</div>
|
| 565 |
+
<div>
|
| 566 |
+
<QuickActions />
|
| 567 |
+
</div>
|
| 568 |
+
</div>
|
| 569 |
+
|
| 570 |
+
{/* Bottom Section */}
|
| 571 |
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
| 572 |
+
<TransactionsTable transactions={transactions} />
|
| 573 |
+
<InvoicesList invoices={invoices} />
|
| 574 |
+
</div>
|
| 575 |
+
</div>
|
| 576 |
+
</main>
|
| 577 |
</div>
|
| 578 |
</div>
|
| 579 |
);
|