From 1eb3e9d3f69b2ec6e3b6d4b0344fed885806771d Mon Sep 17 00:00:00 2001 From: Denis Shatskiy Date: Tue, 8 Jul 2025 23:02:32 +0700 Subject: [PATCH] feat: Integrate @tabler/core for UI enhancements and refactor App component with new sidebar and dashboard features --- frontend/package-lock.json | 37 ++ frontend/package.json | 1 + frontend/src/App.css | 965 ++--------------------------------- frontend/src/App.jsx | 344 ++++++++++--- frontend/src/DataManager.jsx | 278 ++++------ frontend/src/main.jsx | 1 + 6 files changed, 466 insertions(+), 1160 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 25a1e88..ff62c5a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "frontend", "version": "0.0.0", "dependencies": { + "@tabler/core": "^1.3.2", "@tabler/icons-react": "^3.34.0", "axios": "^1.10.0", "bootstrap": "^5.3.7", @@ -642,6 +643,42 @@ "tslib": "^2.8.0" } }, + "node_modules/@tabler/core": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@tabler/core/-/core-1.3.2.tgz", + "integrity": "sha512-QDVJbv48YJrahBLdxYkLi6NutQv4jGbkUWyzxE2NcNJ3s3GGpRx98JmbAoN92NZKNmf26vZdW6k2Q5haVKlS4A==", + "license": "MIT", + "dependencies": { + "@popperjs/core": "^2.11.8", + "bootstrap": "5.3.6" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + } + }, + "node_modules/@tabler/core/node_modules/bootstrap": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.6.tgz", + "integrity": "sha512-jX0GAcRzvdwISuvArXn3m7KZscWWFAf1MKBcnzaN02qWMb3jpMoUX4/qgeiGzqyIb4ojulRzs89UCUmGcFSzTA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, "node_modules/@tabler/icons": { "version": "3.34.0", "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.34.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 8093a2c..ed7ac8b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@tabler/core": "^1.3.2", "@tabler/icons-react": "^3.34.0", "axios": "^1.10.0", "bootstrap": "^5.3.7", diff --git a/frontend/src/App.css b/frontend/src/App.css index 2e8e033..5ceeec8 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,696 +1,16 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} +/* Custom styles for S3 Lists Manager */ +/* Most styles are now provided by @tabler/core */ -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} - -/* Tabler CSS Variables */ -:root { - --tblr-primary: #206bc4; - --tblr-primary-rgb: 32, 107, 196; - --tblr-secondary: #6c757d; - --tblr-secondary-rgb: 108, 117, 125; - --tblr-success: #2fb344; - --tblr-success-rgb: 47, 179, 68; - --tblr-info: #467fd0; - --tblr-info-rgb: 70, 127, 208; - --tblr-warning: #f59f00; - --tblr-warning-rgb: 245, 159, 0; - --tblr-danger: #d63939; - --tblr-danger-rgb: 214, 57, 57; - --tblr-light: #f8f9fa; - --tblr-light-rgb: 248, 249, 250; - --tblr-dark: #1e293b; - --tblr-dark-rgb: 30, 41, 59; - --tblr-body-bg: #f5f6f7; - --tblr-body-color: #1e293b; - --tblr-border-color: #e9ecef; - --tblr-border-radius: 0.375rem; - --tblr-border-radius-sm: 0.25rem; - --tblr-border-radius-lg: 0.5rem; - --tblr-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); - --tblr-box-shadow-sm: 0 0.0625rem 0.125rem rgba(0, 0, 0, 0.075); - --tblr-box-shadow-lg: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); - --tblr-font-family-sans-serif: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; - --tblr-font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - --tblr-font-size-base: 0.875rem; - --tblr-line-height-base: 1.5; - --tblr-heading-font-weight: 600; - --tblr-btn-font-weight: 500; - --tblr-btn-border-radius: 0.375rem; - --tblr-btn-padding-x: 0.75rem; - --tblr-btn-padding-y: 0.375rem; - --tblr-input-border-radius: 0.375rem; - --tblr-input-padding-x: 0.75rem; - --tblr-input-padding-y: 0.375rem; - --tblr-card-border-radius: 0.5rem; - --tblr-card-border-width: 0; - --tblr-card-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); - --tblr-navbar-height: 3.5rem; - --tblr-sidebar-width: 16rem; - --tblr-sidebar-width-md: 12rem; - --tblr-sidebar-width-sm: 4rem; -} - -/* Base Styles */ -* { - box-sizing: border-box; -} - -body { - font-family: var(--tblr-font-family-sans-serif); - font-size: var(--tblr-font-size-base); - line-height: var(--tblr-line-height-base); - color: var(--tblr-body-color); - background-color: var(--tblr-body-bg); - margin: 0; - padding: 0; -} - -/* Page Layout */ -.page { - min-height: 100vh; - display: flex; - flex-direction: column; -} - -.page-wrapper { - flex: 1; - display: flex; - flex-direction: column; -} - -.page-body { - flex: 1; - padding: 1.5rem 0; -} - -.page-header { - padding: 1.5rem 0; - border-bottom: 1px solid var(--tblr-border-color); - margin-bottom: 1.5rem; -} - -.page-title { - font-size: 1.5rem; - font-weight: var(--tblr-heading-font-weight); - margin: 0; - color: var(--tblr-dark); -} - -/* Navbar */ -.navbar { - background-color: #fff; - border-bottom: 1px solid var(--tblr-border-color); - box-shadow: var(--tblr-box-shadow-sm); - height: var(--tblr-navbar-height); - padding: 0; -} - -.navbar-brand { - font-weight: var(--tblr-heading-font-weight); - font-size: 1.25rem; - color: var(--tblr-dark); - text-decoration: none; - display: flex; - align-items: center; -} - -.navbar-brand:hover { - color: var(--tblr-primary); -} - -.navbar-brand-icon { - width: 1.5rem; - height: 1.5rem; -} - -.navbar-toggler { - border: none; - background: none; - padding: 0.5rem; - color: var(--tblr-secondary); - cursor: pointer; -} - -.navbar-toggler:hover { - color: var(--tblr-dark); -} - -.navbar-nav { - list-style: none; - margin: 0; - padding: 0; - display: flex; - align-items: center; -} - -.nav-item { - margin: 0; -} - -.nav-link { - display: flex; - align-items: center; - padding: 0.75rem 1rem; - color: var(--tblr-secondary); - text-decoration: none; - border-radius: var(--tblr-border-radius); - transition: all 0.15s ease-in-out; -} - -.nav-link:hover { - color: var(--tblr-dark); - background-color: var(--tblr-light); -} - -.nav-link.active { - color: var(--tblr-primary); - background-color: rgba(var(--tblr-primary-rgb), 0.1); -} - -.nav-link-icon { - width: 1.25rem; - height: 1.25rem; - margin-right: 0.5rem; -} - -.nav-link-title { - font-weight: 500; -} - -/* Avatar */ +/* Custom avatar background */ .avatar { - display: inline-block; - width: 2rem; - height: 2rem; - border-radius: 50%; - background-size: cover; - background-position: center; - background-repeat: no-repeat; + background-image: url('data:image/svg+xml,'); } -.avatar-sm { - width: 1.5rem; - height: 1.5rem; -} - -/* Cards */ -.card { - background-color: #fff; - border: var(--tblr-card-border-width) solid var(--tblr-border-color); - border-radius: var(--tblr-card-border-radius); - box-shadow: var(--tblr-card-box-shadow); - margin-bottom: 1.5rem; - transition: box-shadow 0.15s ease-in-out; -} - -.card:hover { - box-shadow: var(--tblr-box-shadow-lg); -} - -.card-md { - max-width: 28rem; -} - -.card-header { - padding: 1rem 1.5rem; - border-bottom: 1px solid var(--tblr-border-color); - background-color: transparent; -} - -.card-title { - font-size: 1.125rem; - font-weight: var(--tblr-heading-font-weight); - margin: 0; - color: var(--tblr-dark); - display: flex; - align-items: center; -} - -.card-body { - padding: 1.5rem; -} - -.card-actions { - display: flex; - align-items: center; - gap: 0.5rem; -} - -/* Stats Cards */ -.h3 { - font-size: 1.75rem; - font-weight: var(--tblr-heading-font-weight); - margin: 0; - color: var(--tblr-dark); -} - -.mb-1 { - margin-bottom: 0.25rem !important; -} - -/* Buttons */ -.btn { - display: inline-flex; - align-items: center; - justify-content: center; - padding: var(--tblr-btn-padding-y) var(--tblr-btn-padding-x); - font-size: var(--tblr-font-size-base); - font-weight: var(--tblr-btn-font-weight); - line-height: 1.5; - border: 1px solid transparent; - border-radius: var(--tblr-btn-border-radius); - cursor: pointer; - transition: all 0.15s ease-in-out; - text-decoration: none; - white-space: nowrap; -} - -.btn:hover { - text-decoration: none; - transform: translateY(-1px); - box-shadow: var(--tblr-box-shadow); -} - -.btn:disabled { - opacity: 0.65; - cursor: not-allowed; - transform: none; - box-shadow: none; -} - -.btn-primary { - background-color: var(--tblr-primary); - border-color: var(--tblr-primary); - color: #fff; -} - -.btn-primary:hover { - background-color: #1a5aa3; - border-color: #1a5aa3; - color: #fff; -} - -.btn-secondary { - background-color: var(--tblr-secondary); - border-color: var(--tblr-secondary); - color: #fff; -} - -.btn-secondary:hover { - background-color: #5a6268; - border-color: #5a6268; - color: #fff; -} - -.btn-success { - background-color: var(--tblr-success); - border-color: var(--tblr-success); - color: #fff; -} - -.btn-success:hover { - background-color: #28a745; - border-color: #28a745; - color: #fff; -} - -.btn-warning { - background-color: var(--tblr-warning); - border-color: var(--tblr-warning); - color: #fff; -} - -.btn-warning:hover { - background-color: #e0a800; - border-color: #e0a800; - color: #fff; -} - -.btn-danger { - background-color: var(--tblr-danger); - border-color: var(--tblr-danger); - color: #fff; -} - -.btn-danger:hover { - background-color: #c82333; - border-color: #c82333; - color: #fff; -} - -.btn-outline-primary { - background-color: transparent; - border-color: var(--tblr-primary); - color: var(--tblr-primary); -} - -.btn-outline-primary:hover { - background-color: var(--tblr-primary); - border-color: var(--tblr-primary); - color: #fff; -} - -.btn-outline-secondary { - background-color: transparent; - border-color: var(--tblr-secondary); - color: var(--tblr-secondary); -} - -.btn-outline-secondary:hover { - background-color: var(--tblr-secondary); - border-color: var(--tblr-secondary); - color: #fff; -} - -.btn-outline-danger { - background-color: transparent; - border-color: var(--tblr-danger); - color: var(--tblr-danger); -} - -.btn-outline-danger:hover { - background-color: var(--tblr-danger); - border-color: var(--tblr-danger); - color: #fff; -} - -.btn-sm { - padding: 0.25rem 0.5rem; - font-size: 0.75rem; -} - -.btn-list { - display: flex; - gap: 0.25rem; -} - -/* Forms */ -.form-label { - font-weight: 500; - color: var(--tblr-dark); - margin-bottom: 0.5rem; -} - -.form-control { - display: block; - width: 100%; - padding: var(--tblr-input-padding-y) var(--tblr-input-padding-x); - font-size: var(--tblr-font-size-base); - line-height: var(--tblr-line-height-base); - color: var(--tblr-body-color); - background-color: #fff; - border: 1px solid var(--tblr-border-color); - border-radius: var(--tblr-input-border-radius); - transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; -} - -.form-control:focus { - border-color: var(--tblr-primary); - outline: 0; - box-shadow: 0 0 0 0.2rem rgba(var(--tblr-primary-rgb), 0.25); -} - -.form-control-sm { - padding: 0.25rem 0.5rem; - font-size: 0.75rem; -} - -.form-footer { - margin-top: 1rem; - padding-top: 1rem; - border-top: 1px solid var(--tblr-border-color); -} - -/* Input Icon */ -.input-icon { - position: relative; -} - -.input-icon .icon { - position: absolute; - left: 0.75rem; - top: 50%; - transform: translateY(-50%); - color: var(--tblr-secondary); - z-index: 1; -} - -.input-icon .form-control { - padding-left: 2.5rem; -} - -/* Tables */ -.table { - width: 100%; - margin-bottom: 1rem; - color: var(--tblr-body-color); - border-collapse: collapse; -} - -.table th, -.table td { - padding: 0.75rem; - vertical-align: top; - border-top: 1px solid var(--tblr-border-color); -} - -.table thead th { - vertical-align: bottom; - border-bottom: 2px solid var(--tblr-border-color); - font-weight: 600; - color: var(--tblr-dark); - background-color: var(--tblr-light); -} - -.table tbody tr:hover { - background-color: rgba(var(--tblr-primary-rgb), 0.05); -} - -.table-vcenter td { - vertical-align: middle; -} - -.card-table { - margin-bottom: 0; -} - -.card-table th:first-child, -.card-table td:first-child { - padding-left: 1.5rem; -} - -.card-table th:last-child, -.card-table td:last-child { - padding-right: 1.5rem; -} - -/* Badges */ -.badge { - display: inline-block; - padding: 0.25rem 0.5rem; - font-size: 0.75rem; - font-weight: 500; - line-height: 1; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - border-radius: var(--tblr-border-radius-sm); -} - -.bg-blue-lt { - background-color: rgba(var(--tblr-primary-rgb), 0.1); - color: var(--tblr-primary); -} - -/* Alerts */ -.alert { - position: relative; - padding: 1rem 1.5rem; - margin-bottom: 1rem; - border: 1px solid transparent; - border-radius: var(--tblr-border-radius); -} - -.alert-danger { - color: #721c24; - background-color: #f8d7da; - border-color: #f5c6cb; -} - -.alert-success { - color: #155724; - background-color: #d4edda; - border-color: #c3e6cb; -} - -.alert-dismissible { - padding-right: 3rem; -} - -.btn-close { - position: absolute; - top: 0; - right: 0; - z-index: 2; - padding: 1rem 1.5rem; - background: transparent; - border: 0; - cursor: pointer; -} - -.btn-close::before { - content: "×"; - font-size: 1.5rem; - font-weight: 700; - line-height: 1; - color: #000; - opacity: 0.5; -} - -.btn-close:hover::before { - opacity: 0.75; -} - -.alert-icon { - width: 1.25rem; - height: 1.25rem; - margin-right: 0.5rem; -} - -/* Empty States */ -.empty { - text-align: center; - padding: 3rem 1rem; -} - -.empty-icon { - margin-bottom: 1rem; -} - -.empty-icon .icon { - width: 3rem; - height: 3rem; - color: var(--tblr-secondary); -} - -.empty-title { - font-size: 1.25rem; - font-weight: var(--tblr-heading-font-weight); - color: var(--tblr-dark); - margin: 0 0 0.5rem 0; -} - -.empty-subtitle { - font-size: var(--tblr-font-size-base); - margin: 0; -} - -/* Icons */ -.icon { - width: 1rem; - height: 1rem; - display: inline-block; - vertical-align: middle; -} - -/* Modal */ -.modal-backdrop { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.5); - z-index: 1050; - display: flex; - align-items: center; - justify-content: center; - backdrop-filter: blur(2px); -} - -.modal-dialog { - max-width: 500px; - width: 90%; - margin: 1.75rem auto; - animation: modalFadeIn 0.3s ease-out; -} - -.modal-content { - background-color: #fff; - border-radius: var(--tblr-card-border-radius); - box-shadow: var(--tblr-box-shadow-lg); - border: none; -} - -.modal-header { - padding: 1rem 1.5rem; - border-bottom: 1px solid var(--tblr-border-color); - display: flex; - align-items: center; -} - -.modal-title { - font-size: 1.125rem; - font-weight: var(--tblr-heading-font-weight); - margin: 0; - color: var(--tblr-dark); -} - -.modal-body { - padding: 1.5rem; -} - -.modal-footer { - padding: 1rem 1.5rem; - border-top: 1px solid var(--tblr-border-color); - display: flex; - gap: 0.5rem; - justify-content: flex-end; -} - -@keyframes modalFadeIn { +/* Custom animations */ +@keyframes fadeIn { from { opacity: 0; - transform: translateY(-20px); + transform: translateY(10px); } to { opacity: 1; @@ -698,227 +18,28 @@ body { } } -/* Utilities */ -.text-muted { - color: var(--tblr-secondary) !important; +.fade-in { + animation: fadeIn 0.3s ease-out; } -.text-center { - text-align: center !important; -} - -.font-weight-medium { - font-weight: 500 !important; -} - -.w-1 { - width: 1rem !important; -} - -.me-2 { - margin-right: 0.5rem !important; -} - -.mb-0 { - margin-bottom: 0 !important; -} - -.mb-3 { - margin-bottom: 1rem !important; -} - -.mt-1 { - margin-top: 0.25rem !important; -} - -.mt-3 { - margin-top: 1rem !important; -} - -.py-4 { - padding-top: 1.5rem !important; - padding-bottom: 1.5rem !important; -} - -/* Footer */ -.footer { - background-color: transparent; - border-top: 1px solid var(--tblr-border-color); - padding: 1rem 0; - margin-top: auto; -} - -.footer-transparent { - background-color: transparent; -} - -.list-inline { - list-style: none; - margin: 0; - padding: 0; -} - -.list-inline-item { - display: inline-block; -} - -.list-inline-item:not(:last-child) { - margin-right: 0.5rem; -} - -.list-inline-dots .list-inline-item:not(:last-child)::after { - content: "·"; - margin-left: 0.5rem; - color: var(--tblr-secondary); -} - -.link-secondary { - color: var(--tblr-secondary); - text-decoration: none; -} - -.link-secondary:hover { - color: var(--tblr-dark); - text-decoration: underline; -} - -/* Responsive */ -@media (max-width: 768px) { - .navbar-expand-md .navbar-collapse { - display: none; - } - - .navbar-expand-md.show .navbar-collapse { - display: block; - position: absolute; - top: 100%; - left: 0; - right: 0; - background-color: #fff; - border-top: 1px solid var(--tblr-border-color); - box-shadow: var(--tblr-box-shadow); - z-index: 1000; - } - - .navbar-expand-md .navbar-nav { - flex-direction: column; - } - - .navbar-expand-md .nav-item { - width: 100%; - } - - .navbar-expand-md .nav-link { - padding: 1rem; - border-radius: 0; - } - - .card-actions { - flex-direction: column; - gap: 0.5rem; - } - - .btn-list { - flex-direction: column; - } - - .modal-footer { - flex-direction: column; - } - - .modal-footer .btn { - width: 100%; - } -} - -/* Container */ -.container-xl { - max-width: 1200px; - margin: 0 auto; - padding: 0 1rem; -} - -/* Row and Col */ -.row { - display: flex; - flex-wrap: wrap; - margin: 0 -0.75rem; -} - -.col, -.col-lg-4, -.col-lg-8 { - padding: 0 0.75rem; -} - -.col { - flex: 1; -} - -.col-lg-4 { - flex: 0 0 33.333333%; - max-width: 33.333333%; -} - -.col-lg-8 { - flex: 0 0 66.666667%; - max-width: 66.666667%; -} - -@media (max-width: 992px) { - .col-lg-4, - .col-lg-8 { - flex: 0 0 100%; - max-width: 100%; - } -} - -/* Spinner */ -.spinner-border { - display: inline-block; - width: 2rem; - height: 2rem; - border: 0.25em solid currentColor; - border-right-color: transparent; - border-radius: 50%; - animation: spinner-border 0.75s linear infinite; -} - -@keyframes spinner-border { - to { - transform: rotate(360deg); - } -} - -.text-primary { - color: var(--tblr-primary) !important; -} - -.visually-hidden { - position: absolute !important; - width: 1px !important; - height: 1px !important; - padding: 0 !important; - margin: -1px !important; - overflow: hidden !important; - clip: rect(0, 0, 0, 0) !important; - white-space: nowrap !important; - border: 0 !important; -} - -/* Enhanced hover effects */ -.card:hover .card-title { - color: var(--tblr-primary); +/* Custom hover effects */ +.card:hover { + transform: translateY(-2px); + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); + transition: all 0.3s ease; } .btn:hover { transform: translateY(-1px); - box-shadow: var(--tblr-box-shadow); + transition: all 0.2s ease; } -.form-control:focus { - transform: translateY(-1px); - box-shadow: 0 0 0 0.2rem rgba(var(--tblr-primary-rgb), 0.25), var(--tblr-box-shadow); +/* Custom focus states */ +.btn:focus, +.form-control:focus, +.nav-link:focus { + outline: 2px solid var(--tblr-primary); + outline-offset: 2px; } /* Loading states */ @@ -927,45 +48,27 @@ body { box-shadow: none !important; } -/* Focus states for accessibility */ -.btn:focus, -.form-control:focus, -.nav-link:focus { - outline: 2px solid var(--tblr-primary); - outline-offset: 2px; +/* Custom responsive adjustments */ +@media (max-width: 992px) { + .page-wrapper { + margin-left: 0; + } } -/* Dark mode support (future enhancement) */ +/* Custom dark mode support */ @media (prefers-color-scheme: dark) { - :root { - --tblr-body-bg: #1a1a1a; - --tblr-body-color: #e9ecef; - --tblr-dark: #f8f9fa; - --tblr-border-color: #2d3748; - } - .card { - background-color: #2d3748; - border-color: #4a5568; + background-color: var(--tblr-dark); + border-color: var(--tblr-border-color); } .navbar { - background-color: #2d3748; - border-color: #4a5568; + background-color: var(--tblr-dark); + border-color: var(--tblr-border-color); } - .form-control { - background-color: #4a5568; - border-color: #718096; - color: #e9ecef; - } - - .table { - color: #e9ecef; - } - - .table thead th { - background-color: #4a5568; - color: #f8f9fa; + .navbar-vertical { + background-color: var(--tblr-dark); + border-color: var(--tblr-border-color); } } diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index f8cd04f..c210dad 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -6,36 +6,96 @@ import { IconNetwork, IconSettings, IconMenu2, - IconX + IconX, + IconHome, + IconDatabase, + IconUsers, + IconBell, + IconSearch, + IconChevronDown, + IconLogout, + IconUser, + IconDashboard, + IconFileText, + IconCloud, + IconShield, + IconActivity } from '@tabler/icons-react'; import DataManager from './DataManager'; import './App.css'; function App() { const [sidebarOpen, setSidebarOpen] = useState(false); - const [activeTab, setActiveTab] = useState('domains'); + const [activeTab, setActiveTab] = useState('dashboard'); + const [notifications] = useState([ + { id: 1, title: 'Новый домен добавлен', message: 'example.com был добавлен в список', time: '2 мин назад' }, + { id: 2, title: 'Изменения сохранены', message: 'Список AS обновлен в S3', time: '5 мин назад' } + ]); const toggleSidebar = () => setSidebarOpen(!sidebarOpen); + const sidebarItems = [ + { id: 'dashboard', title: 'Dashboard', icon: IconDashboard, badge: null }, + { id: 'domains', title: 'Домены', icon: IconWorld, badge: '12' }, + { id: 'asns', title: 'AS', icon: IconNetwork, badge: '8' }, + { id: 'files', title: 'Файлы', icon: IconFileText, badge: null }, + { id: 'cloud', title: 'Облако', icon: IconCloud, badge: null }, + { id: 'security', title: 'Безопасность', icon: IconShield, badge: null }, + { id: 'activity', title: 'Активность', icon: IconActivity, badge: '3' }, + { id: 'settings', title: 'Настройки', icon: IconSettings, badge: null } + ]; + + const renderContent = () => { + switch (activeTab) { + case 'dashboard': + return ; + case 'domains': + return ( + + ); + case 'asns': + return ( + + ); + default: + return ; + } + }; + + const getPageTitle = () => { + const item = sidebarItems.find(item => item.id === activeTab); + return item ? item.title : 'Dashboard'; + }; + return (
- {/* Header */} -
-
+ {/* Sidebar */} +
- {/* Sidebar */} -
- + {/* Main Content */}
+ {/* Page Header */}
+
+ S3 Lists Manager +

- {activeTab === 'domains' ? 'Управление доменами' : 'Управление AS'} + {getPageTitle()}

-
- {activeTab === 'domains' - ? 'Добавляйте, редактируйте и управляйте списками доменов' - : 'Добавляйте, редактируйте и управляйте списками AS' - } +
+
+ {/* Breadcrumbs */} +
- {activeTab === 'domains' && ( - - )} - {activeTab === 'asns' && ( - - )} + {renderContent()}
@@ -147,4 +288,87 @@ function App() { ); } +// Dashboard Component +function Dashboard() { + const stats = [ + { title: 'Всего доменов', value: '1,234', icon: IconWorld, color: 'blue' }, + { title: 'Всего AS', value: '567', icon: IconNetwork, color: 'green' }, + { title: 'Активные шлюзы', value: '12', icon: IconDatabase, color: 'orange' }, + { title: 'Последние изменения', value: '3', icon: IconActivity, color: 'red' } + ]; + + return ( +
+ {/* Stats Cards */} + {stats.map((stat, index) => ( +
+
+
+
+
{stat.title}
+
+
{stat.value}
+
+ +
+
+
+
+ ))} + + {/* Main Content Card */} +
+
+
+

Добро пожаловать в S3 Lists Manager

+
+
+

Выберите раздел в боковом меню для управления списками доменов и AS.

+
+
+
+
+
+
+ +
+
+
+ Управление доменами +
+
+ Добавляйте, редактируйте и управляйте списками доменов +
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ Управление AS +
+
+ Добавляйте, редактируйте и управляйте списками AS +
+
+
+
+
+
+
+
+
+
+
+ ); +} + export default App; diff --git a/frontend/src/DataManager.jsx b/frontend/src/DataManager.jsx index 82b10b1..10b0558 100644 --- a/frontend/src/DataManager.jsx +++ b/frontend/src/DataManager.jsx @@ -1,6 +1,5 @@ import { useState, useEffect } from 'react'; import axios from 'axios'; -import { Table, Button, Form, Row, Col, Card, Alert } from 'react-bootstrap'; import { IconPlus, IconSearch, @@ -199,7 +198,7 @@ function DataManager({ entityName, entityKey, placeholder }) {
{/* Bulk Update Card */} -
+

@@ -207,110 +206,100 @@ function DataManager({ entityName, entityKey, placeholder }) {

-
+
- setBulkFrom(e.target.value)} + setBulkFrom(e.target.value)} />
- setBulkTo(e.target.value)} + setBulkTo(e.target.value)} />
- {/* Stats Card */} -
+ {/* Actions Card */} +

- Статистика + Действия

-
-
-
Всего
-
{items.length}
-
-
-
Найдено
-
{filteredItems.length}
-
+
+ +
- {/* Main Data Card */} + {/* Search and Table Card */}
-
-

- - Список: {entityName} -

-
- - -
-
-
-
- {/* Search */} -
+

Список {entityName}

+
setSearchTerm(e.target.value)} />
- - {/* Table */} +
+
- @@ -318,10 +307,18 @@ function DataManager({ entityName, entityKey, placeholder }) { {filteredItems.map((item, index) => ( - - +
# {entityName} Шлюз
{index + 1}
-
{item.domain}
+ {editingDomain === item.domain ? ( + setEditingDomain(e.target.value)} + /> + ) : ( + item.domain + )}
{editingDomain === item.domain ? ( @@ -330,7 +327,6 @@ function DataManager({ entityName, entityKey, placeholder }) { className="form-control form-control-sm" value={editingValue} onChange={(e) => setEditingValue(e.target.value)} - autoFocus /> ) : ( {item.type} @@ -339,14 +335,14 @@ function DataManager({ entityName, entityKey, placeholder }) { {editingDomain === item.domain ? (
- -
) : (
- -
- - {/* Empty States */} - {items.length > 0 && filteredItems.length === 0 && ( + {filteredItems.length === 0 && (
-
- +
+
-

Ничего не найдено

+

Нет {entityName} для отображения

- По вашему запросу ничего не найдено. + Добавьте первый {entityName} используя форму слева.

)} - - {items.length === 0 && !loading && ( -
-
- -
-

Список пуст

-

- Добавьте новый элемент, чтобы начать работу. -

-
- )} - - {loading && ( -
-
- Загрузка... -
-
- )}
@@ -414,77 +388,43 @@ function DataManager({ entityName, entityKey, placeholder }) { {/* Delete Confirmation Modal */} {showDeleteModal && ( -
-
-
-
- -
Подтверждение удаления
- +
+
+
+ +
+
+ +

Удалить {entityName}?

+
+ Вы уверены, что хотите удалить "{itemToDelete?.domain}"? Это действие необратимо. +
-
-

Вы уверены, что хотите удалить {itemToDelete?.domain}?

-

Это действие нельзя отменить.

-
-
- - +
+
+
+
+ +
+
+ +
+
+
diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index fc4ee72..cacf3a6 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -2,6 +2,7 @@ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App.jsx' import './index.css' +import '@tabler/core/dist/css/tabler.min.css' import 'bootstrap/dist/css/bootstrap.min.css'; ReactDOM.createRoot(document.getElementById('root')).render(