fix tax log

This commit is contained in:
2026-02-19 16:05:59 +03:00
parent 2c820e103a
commit c2a4c8062a
38 changed files with 4414 additions and 40 deletions

View File

@@ -0,0 +1,337 @@
<!-- [DEF:TopNavbar:Component] -->
<script>
/**
* @TIER: CRITICAL
* @PURPOSE: Unified top navigation bar with Logo, Search, Activity, and User menu
* @LAYER: UI
* @RELATION: BINDS_TO -> activityStore, authStore
* @SEMANTICS: Navigation, UserSession
* @INVARIANT: Always visible on non-login pages
*
* @UX_STATE: Idle -> Navbar showing current state
* @UX_STATE: SearchFocused -> Search input expands
* @UX_FEEDBACK: Activity badge shows count of running tasks
* @UX_RECOVERY: Click outside closes dropdowns
*/
import { createEventDispatcher } from "svelte";
import { page } from "$app/stores";
import { activityStore } from "$lib/stores/activity.js";
import {
taskDrawerStore,
openDrawerForTask,
openDrawer,
} from "$lib/stores/taskDrawer.js";
import { sidebarStore, toggleMobileSidebar } from "$lib/stores/sidebar.js";
import { t } from "$lib/i18n";
import { auth } from "$lib/auth/store.js";
const dispatch = createEventDispatcher();
let showUserMenu = false;
let isSearchFocused = false;
// Subscribe to sidebar store for responsive layout
$: isExpanded = $sidebarStore?.isExpanded ?? true;
// Subscribe to activity store
$: activeCount = $activityStore?.activeCount || 0;
$: recentTasks = $activityStore?.recentTasks || [];
// Get user from auth store
$: user = $auth?.user || null;
// Toggle user menu
function toggleUserMenu(event) {
event.stopPropagation();
showUserMenu = !showUserMenu;
console.log(`[TopNavbar][Action] Toggle user menu: ${showUserMenu}`);
}
// Close user menu
function closeUserMenu() {
showUserMenu = false;
}
// Handle logout
function handleLogout() {
console.log("[TopNavbar][Action] Logout");
auth.logout();
closeUserMenu();
// Navigate to login
window.location.href = "/login";
}
// Handle activity indicator click - open Task Drawer with most recent task
function handleActivityClick() {
console.log("[TopNavbar][Action] Activity indicator clicked");
// Open drawer with the most recent running task, or list mode
const runningTask = recentTasks.find((t) => t.status === "RUNNING");
if (runningTask) {
openDrawerForTask(runningTask.taskId);
} else if (recentTasks.length > 0) {
openDrawerForTask(recentTasks[recentTasks.length - 1].taskId);
} else {
// No tracked tasks — open in list mode to show recent tasks from API
openDrawer();
}
dispatch("activityClick");
}
// Handle search focus
function handleSearchFocus() {
isSearchFocused = true;
}
function handleSearchBlur() {
isSearchFocused = false;
}
// Close dropdowns when clicking outside
function handleDocumentClick(event) {
if (!event.target.closest(".user-menu-container")) {
closeUserMenu();
}
}
// Listen for document clicks
if (typeof document !== "undefined") {
document.addEventListener("click", handleDocumentClick);
}
// Handle hamburger menu click for mobile
function handleHamburgerClick(event) {
event.stopPropagation();
console.log("[TopNavbar][Action] Toggle mobile sidebar");
toggleMobileSidebar();
}
</script>
<nav
class="navbar {isExpanded ? 'with-sidebar' : 'with-collapsed-sidebar'} mobile"
>
<!-- Left section: Hamburger (mobile) + Logo -->
<div class="flex items-center gap-2">
<!-- Hamburger Menu (mobile only) -->
<button
class="hamburger-btn"
on:click={handleHamburgerClick}
aria-label="Toggle menu"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<!-- Logo/Brand -->
<a href="/" class="logo-link">
<svg
class="logo-icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<span>Superset Tools</span>
</a>
</div>
<!-- Search placeholder (non-functional for now) -->
<div class="search-container">
<input
type="text"
class="search-input {isSearchFocused ? 'focused' : ''}"
placeholder={$t.common.search || "Search..."}
on:focus={handleSearchFocus}
on:blur={handleSearchBlur}
/>
</div>
<!-- Nav Actions -->
<div class="nav-actions">
<!-- Activity Indicator -->
<div
class="activity-indicator"
on:click={handleActivityClick}
on:keydown={(e) =>
(e.key === "Enter" || e.key === " ") && handleActivityClick()}
role="button"
tabindex="0"
aria-label="Activity"
>
<svg
class="activity-icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"
/>
</svg>
{#if activeCount > 0}
<span class="activity-badge">{activeCount}</span>
{/if}
</div>
<!-- User Menu -->
<div class="user-menu-container">
<div
class="user-avatar"
on:click={toggleUserMenu}
on:keydown={(e) =>
(e.key === "Enter" || e.key === " ") && toggleUserMenu(e)}
role="button"
tabindex="0"
aria-label="User menu"
>
{#if user}
<span
>{user.username ? user.username.charAt(0).toUpperCase() : "U"}</span
>
{:else}
<span>U</span>
{/if}
</div>
<!-- User Dropdown -->
<div class="user-dropdown {showUserMenu ? '' : 'hidden'}">
<div class="dropdown-item">
<strong>{user?.username || "User"}</strong>
</div>
<div class="dropdown-divider"></div>
<div
class="dropdown-item"
on:click={() => {
window.location.href = "/settings";
}}
on:keydown={(e) =>
(e.key === "Enter" || e.key === " ") &&
(window.location.href = "/settings")}
role="button"
tabindex="0"
>
{$t.nav?.settings || "Settings"}
</div>
<div
class="dropdown-item danger"
on:click={handleLogout}
on:keydown={(e) =>
(e.key === "Enter" || e.key === " ") && handleLogout()}
role="button"
tabindex="0"
>
{$t.common?.logout || "Logout"}
</div>
</div>
</div>
</div>
</nav>
<!-- [/DEF:TopNavbar:Component] -->
<style>
.navbar {
@apply bg-white border-b border-gray-200 fixed top-0 right-0 left-0 h-16 flex items-center justify-between px-4 z-40;
}
.navbar.with-sidebar {
@apply md:left-64;
}
.navbar.with-collapsed-sidebar {
@apply md:left-16;
}
.navbar.mobile {
@apply left-0;
}
.logo-link {
@apply flex items-center text-xl font-bold text-gray-800 hover:text-blue-600 transition-colors;
}
.logo-icon {
@apply w-8 h-8 mr-2 text-blue-600;
}
.search-container {
@apply flex-1 max-w-xl mx-4;
}
.search-input {
@apply w-full px-4 py-2 bg-gray-100 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all;
}
.search-input.focused {
@apply bg-white border border-blue-500;
}
.nav-actions {
@apply flex items-center space-x-4;
}
.hamburger-btn {
@apply p-2 rounded-lg hover:bg-gray-100 text-gray-600 md:hidden;
}
.activity-indicator {
@apply relative cursor-pointer p-2 rounded-lg hover:bg-gray-100 transition-colors;
}
.activity-badge {
@apply absolute -top-1 -right-1 bg-red-500 text-white text-xs font-bold rounded-full w-5 h-5 flex items-center justify-center;
}
.activity-icon {
@apply w-6 h-6 text-gray-600;
}
.user-menu-container {
@apply relative;
}
.user-avatar {
@apply w-8 h-8 rounded-full bg-blue-600 text-white flex items-center justify-center cursor-pointer hover:bg-blue-700 transition-colors;
}
.user-dropdown {
@apply absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg border border-gray-200 py-1 z-50;
}
.user-dropdown.hidden {
display: none;
}
.dropdown-item {
@apply px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 cursor-pointer;
}
.dropdown-item.danger {
@apply text-red-600 hover:bg-red-50;
}
.dropdown-divider {
@apply border-t border-gray-200 my-1;
}
/* Mobile responsive */
@media (max-width: 768px) {
.search-container {
display: none;
}
}
</style>