mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2026-05-08 09:38:55 -04:00
ba14526fc5
Migrations: - drizzle.config.ts: add out: './drizzle' so drizzle-kit generate writes SQL migration files instead of only supporting push - drizzle/0000_glossy_magneto.sql: initial migration capturing all 9 current tables (users, accounts, sessions, verification_tokens, sso_providers, clients, businesses, invoices, invoice_items) - src/server/db/migrate.ts: programmatic runner using drizzle-orm's migrate() — tracks applied migrations in __drizzle_migrations, safe to run on every deploy - package.json: db:migrate now runs the programmatic runner instead of drizzle-kit migrate (CLI requires devDeps at runtime) - start.sh: replace drizzle-kit push with bun src/server/db/migrate.ts - Dockerfile: copy drizzle/ folder into the runner image so migrations are available at container startup Mobile fixes: - data-table.tsx: pagination buttons grow from 32px to 40px on mobile (h-10 w-10 md:h-8 md:w-8) to meet 44px touch-target guidelines - floating-action-bar.tsx: stack left-content + action buttons to column layout on narrow screens (flex-col sm:flex-row), reduce padding on mobile (p-3 sm:p-4) - revenue-chart.tsx: responsive chart height (h-48 md:h-64) so the chart doesn't consume too much vertical space on small screens https://claude.ai/code/session_012sqEgNQpx676isepeoX4Mi
93 lines
3.1 KiB
TypeScript
93 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import React, { useEffect, useState } from "react";
|
|
import { cn } from "~/lib/utils";
|
|
import { Card, CardContent } from "~/components/ui/card";
|
|
|
|
interface FloatingActionBarProps {
|
|
/** Content to display on the left side */
|
|
leftContent?: React.ReactNode;
|
|
/** Action buttons to display on the right */
|
|
children: React.ReactNode;
|
|
/** Additional className for styling */
|
|
className?: string;
|
|
}
|
|
|
|
import { useSidebar } from "~/components/layout/sidebar-provider";
|
|
|
|
export function FloatingActionBar({
|
|
leftContent,
|
|
children,
|
|
className,
|
|
}: FloatingActionBarProps) {
|
|
const [isDocked, setIsDocked] = useState(false);
|
|
const { isCollapsed } = useSidebar();
|
|
|
|
useEffect(() => {
|
|
const handleScroll = () => {
|
|
// Check if we're truly at the bottom of the page
|
|
const scrollHeight = document.documentElement.scrollHeight;
|
|
const scrollTop = document.documentElement.scrollTop;
|
|
const clientHeight = document.documentElement.clientHeight;
|
|
const distanceFromBottom = scrollHeight - (scrollTop + clientHeight);
|
|
|
|
// Only dock when we're within 50px of the actual bottom AND there's content to scroll
|
|
const hasScrollableContent = scrollHeight > clientHeight;
|
|
const shouldDock = hasScrollableContent && distanceFromBottom <= 50;
|
|
|
|
// If content is too small, keep it at bottom of viewport
|
|
const contentTooSmall = scrollHeight <= clientHeight + 200;
|
|
|
|
setIsDocked(shouldDock && !contentTooSmall);
|
|
};
|
|
|
|
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
handleScroll(); // Check initial state
|
|
|
|
return () => window.removeEventListener("scroll", handleScroll);
|
|
}, []);
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
// Base positioning - always at bottom
|
|
"fixed right-0 z-50 transition-all duration-300 ease-in-out",
|
|
// Safe area and sidebar adjustments
|
|
"pb-safe-area-inset-bottom left-0",
|
|
isCollapsed ? "md:left-24" : "md:left-[18rem]",
|
|
// Conditional centering based on dock state
|
|
isDocked ? "flex justify-center" : "",
|
|
// Dynamic bottom positioning
|
|
isDocked ? "bottom-4" : "bottom-0",
|
|
// Add entrance animation
|
|
"animate-slide-in-bottom",
|
|
className,
|
|
)}
|
|
>
|
|
{/* Content container - full width when floating, content width when docked */}
|
|
<div
|
|
className={cn(
|
|
"w-full transition-transform duration-300",
|
|
isDocked ? "mx-auto mb-0 px-4" : "mb-4 px-4",
|
|
)}
|
|
>
|
|
<Card className="hover-lift bg-card border-border border shadow-lg">
|
|
<CardContent className="flex flex-col gap-3 p-3 sm:flex-row sm:items-center sm:justify-between sm:p-4">
|
|
{/* Left content */}
|
|
{leftContent && (
|
|
<div className="text-card-foreground animate-fade-in flex flex-1 items-center gap-3">
|
|
{leftContent}
|
|
</div>
|
|
)}
|
|
|
|
{/* Right actions */}
|
|
<div className="animate-fade-in animate-delay-100 flex items-center gap-2 sm:gap-3">
|
|
{children}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|