Archive improvements: list view actions, object count, cross-view highlighting

Added:
  - Archive list view: edit/delete buttons and context menu with full feature parity
  - Archive object count display on cards (extracted from 3MF metadata)
  - Cross-view archive highlighting: click in calendar/project to highlight in card view
  - Context menu button (⋮) on cards and list rows for easy access
  - Spoolman: clear location when spools are removed from AMS

  Fixed:
  - QR code endpoint 500 error (added qrcode[pil] dependency)
This commit is contained in:
maziggy
2026-01-09 08:24:14 +01:00
parent b4bc046684
commit e2b2877965
12 changed files with 1203 additions and 541 deletions
+25 -6
View File
@@ -6,6 +6,7 @@ import { api } from '../api/client';
interface CalendarViewProps {
archives: Archive[];
onArchiveClick?: (archive: Archive) => void;
highlightedArchiveId?: number | null;
}
function getDaysInMonth(year: number, month: number): number {
@@ -23,11 +24,12 @@ const MONTH_NAMES = [
const DAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
export function CalendarView({ archives, onArchiveClick }: CalendarViewProps) {
export function CalendarView({ archives, onArchiveClick, highlightedArchiveId }: CalendarViewProps) {
const today = new Date();
const [currentMonth, setCurrentMonth] = useState(today.getMonth());
const [currentYear, setCurrentYear] = useState(today.getFullYear());
const [selectedDate, setSelectedDate] = useState<string | null>(null);
const [selectedArchiveId, setSelectedArchiveId] = useState<number | null>(null);
// Group archives by date
const archivesByDate = useMemo(() => {
@@ -79,6 +81,14 @@ export function CalendarView({ archives, onArchiveClick }: CalendarViewProps) {
const selectedArchives = selectedDate ? archivesByDate.get(selectedDate) || [] : [];
// Clear selected archive when date changes
const handleDateSelect = (dateKey: string | null) => {
if (dateKey !== selectedDate) {
setSelectedArchiveId(null);
}
setSelectedDate(dateKey);
};
return (
<div className="flex flex-col lg:flex-row gap-6">
{/* Calendar */}
@@ -137,7 +147,7 @@ export function CalendarView({ archives, onArchiveClick }: CalendarViewProps) {
return (
<button
key={day}
onClick={() => setSelectedDate(isSelected ? null : dateKey)}
onClick={() => handleDateSelect(isSelected ? null : dateKey)}
className={`aspect-square rounded-lg p-1 flex flex-col items-center justify-center transition-colors relative ${
isSelected
? 'bg-bambu-green text-white'
@@ -216,11 +226,19 @@ export function CalendarView({ archives, onArchiveClick }: CalendarViewProps) {
</h3>
{selectedArchives.length > 0 ? (
<div className="space-y-2 max-h-96 overflow-y-auto">
{selectedArchives.map(archive => (
{selectedArchives.map(archive => {
const isHighlighted = archive.id === selectedArchiveId || archive.id === highlightedArchiveId;
return (
<button
key={archive.id}
onClick={() => onArchiveClick?.(archive)}
className="w-full flex items-center gap-3 p-2 rounded-lg hover:bg-bambu-dark-tertiary transition-colors text-left"
onClick={() => {
setSelectedArchiveId(archive.id);
onArchiveClick?.(archive);
}}
className={`w-full flex items-center gap-3 p-2 rounded-lg transition-colors text-left ${
!isHighlighted ? 'hover:bg-bambu-dark-tertiary' : ''
}`}
style={isHighlighted ? { outline: '4px solid #facc15', outlineOffset: '2px' } : undefined}
>
{archive.thumbnail_path ? (
<img
@@ -255,7 +273,8 @@ export function CalendarView({ archives, onArchiveClick }: CalendarViewProps) {
</div>
</div>
</button>
))}
);
})}
</div>
) : (
<p className="text-sm text-bambu-gray">No prints on this day</p>
+4 -3
View File
@@ -1,18 +1,19 @@
import type { ReactNode, MouseEvent } from 'react';
import type { ReactNode, MouseEvent, HTMLAttributes } from 'react';
interface CardProps {
interface CardProps extends HTMLAttributes<HTMLDivElement> {
children: ReactNode;
className?: string;
onClick?: (e: MouseEvent) => void;
onContextMenu?: (e: MouseEvent) => void;
}
export function Card({ children, className = '', onClick, onContextMenu }: CardProps) {
export function Card({ children, className = '', onClick, onContextMenu, ...rest }: CardProps) {
return (
<div
className={`bg-bambu-dark-secondary rounded-xl border border-bambu-dark-tertiary card-shadow ${className}`}
onClick={onClick}
onContextMenu={onContextMenu}
{...rest}
>
{children}
</div>