mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-09 07:25:44 +02:00
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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user