compassmock/src/components/files/file-toolbar.tsx
Nicholai d30decf723
feat(ui): add mobile support and dashboard improvements (#30)
* feat(schema): add auth, people, and financial tables

Add users, organizations, teams, groups, and project
members tables. Extend customers/vendors with netsuite
fields. Add netsuite schema for invoices, bills,
payments, and credit memos. Include all migrations,
seeds, new UI primitives, and config updates.

* feat(auth): add WorkOS authentication system

Add login, signup, password reset, email verification,
and invitation flows via WorkOS AuthKit. Includes auth
middleware, permission helpers, dev mode fallbacks,
and auth page components.

* feat(people): add people management system

Add user, team, group, and organization management
with CRUD actions, dashboard pages, invite dialog,
user drawer, and role-based filtering. Includes
WorkOS invitation integration.

* feat(netsuite): add NetSuite integration and financials

Add bidirectional NetSuite REST API integration with
OAuth 2.0, rate limiting, sync engine, and conflict
resolution. Includes invoices, vendor bills, payments,
credit memos CRUD, customer/vendor management pages,
and financial dashboard with tabbed views.

* feat(ui): add mobile support and dashboard improvements

Add mobile bottom nav, FAB, filter bar, search, project
switcher, pull-to-refresh, and schedule mobile view.
Update sidebar with new nav items, settings modal with
integrations tab, responsive dialogs, improved schedule
and file components, PWA manifest, and service worker.

* ci: retrigger build

* ci: retrigger build

---------

Co-authored-by: Nicholai <nicholaivogelfilms@gmail.com>
2026-02-04 16:39:39 -07:00

154 lines
4.9 KiB
TypeScript
Executable File

"use client"
import { useState } from "react"
import {
IconLayoutGrid,
IconList,
IconPlus,
IconUpload,
IconSearch,
IconSortAscending,
IconSortDescending,
IconFolder,
IconFileText,
IconTable,
IconPresentation,
} from "@tabler/icons-react"
import { useFiles, type SortField, type ViewMode } from "@/hooks/use-files"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export type NewFileType = "folder" | "document" | "spreadsheet" | "presentation"
export function FileToolbar({
onNew,
onUpload,
}: {
onNew: (type: NewFileType) => void
onUpload: () => void
}) {
const { state, dispatch } = useFiles()
const [searchFocused, setSearchFocused] = useState(false)
const sortLabels: Record<SortField, string> = {
name: "Name",
modified: "Modified",
size: "Size",
type: "Type",
}
const handleSort = (field: SortField) => {
const direction =
state.sortBy === field && state.sortDirection === "asc" ? "desc" : "asc"
dispatch({ type: "SET_SORT", payload: { field, direction } })
}
return (
<div className="flex flex-col sm:flex-row gap-2">
<div className="flex items-center gap-2 flex-1">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="sm" variant="outline" className="h-10 sm:h-9">
<IconPlus size={18} className="sm:size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem onClick={() => onNew("folder")}>
<IconFolder size={16} />
Folder
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => onNew("document")}>
<IconFileText size={16} />
Document
</DropdownMenuItem>
<DropdownMenuItem onClick={() => onNew("spreadsheet")}>
<IconTable size={16} />
Spreadsheet
</DropdownMenuItem>
<DropdownMenuItem onClick={() => onNew("presentation")}>
<IconPresentation size={16} />
Presentation
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={onUpload}>
<IconUpload size={16} />
File upload
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<div className="relative flex-1">
<IconSearch
size={16}
className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground"
/>
<Input
placeholder="Search files..."
value={state.searchQuery}
onChange={(e) =>
dispatch({ type: "SET_SEARCH", payload: e.target.value })
}
onFocus={() => setSearchFocused(true)}
onBlur={() => setSearchFocused(false)}
className="h-10 sm:h-9 pl-9 text-sm"
/>
</div>
</div>
<div className="flex items-center gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="sm" variant="ghost" className="h-10 sm:h-9">
{state.sortDirection === "asc" ? (
<IconSortAscending size={18} className="sm:size-4" />
) : (
<IconSortDescending size={18} className="sm:size-4" />
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{(Object.keys(sortLabels) as SortField[]).map((field) => (
<DropdownMenuItem key={field} onClick={() => handleSort(field)}>
{sortLabels[field]}
{state.sortBy === field && (
<span className="ml-auto text-xs text-muted-foreground">
{state.sortDirection === "asc" ? "↑" : "↓"}
</span>
)}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
<ToggleGroup
type="single"
value={state.viewMode}
onValueChange={(v) => {
if (v) dispatch({ type: "SET_VIEW_MODE", payload: v as ViewMode })
}}
variant="outline"
size="sm"
className="h-10 sm:h-9"
>
<ToggleGroupItem value="grid" aria-label="Grid view" className="h-10 w-10 sm:h-9 sm:w-9">
<IconLayoutGrid size={18} className="sm:size-4" />
</ToggleGroupItem>
<ToggleGroupItem value="list" aria-label="List view" className="h-10 w-10 sm:h-9 sm:w-9">
<IconList size={18} className="sm:size-4" />
</ToggleGroupItem>
</ToggleGroup>
</div>
</div>
)
}