mirror of
https://github.com/sakaljurgis/best-choice.git
synced 2026-07-08 21:47:40 +00:00
Add active-only filter for project items
This commit is contained in:
@@ -1,27 +1,35 @@
|
|||||||
interface ProjectItemsHeaderProps {
|
interface ProjectItemsHeaderProps {
|
||||||
itemCount: number;
|
itemCount: number;
|
||||||
|
totalItemCount: number;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
showDifferencesOnly: boolean;
|
showDifferencesOnly: boolean;
|
||||||
hasAttributeDifferences: boolean;
|
hasAttributeDifferences: boolean;
|
||||||
onToggleDifferences: (value: boolean) => void;
|
onToggleDifferences: (value: boolean) => void;
|
||||||
showLargeImages: boolean;
|
showLargeImages: boolean;
|
||||||
onToggleLargeImages: (value: boolean) => void;
|
onToggleLargeImages: (value: boolean) => void;
|
||||||
|
showActiveOnly: boolean;
|
||||||
|
onToggleActiveOnly: (value: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ProjectItemsHeader({
|
export function ProjectItemsHeader({
|
||||||
itemCount,
|
itemCount,
|
||||||
|
totalItemCount,
|
||||||
isLoading,
|
isLoading,
|
||||||
showDifferencesOnly,
|
showDifferencesOnly,
|
||||||
hasAttributeDifferences,
|
hasAttributeDifferences,
|
||||||
onToggleDifferences,
|
onToggleDifferences,
|
||||||
showLargeImages,
|
showLargeImages,
|
||||||
onToggleLargeImages
|
onToggleLargeImages,
|
||||||
|
showActiveOnly,
|
||||||
|
onToggleActiveOnly
|
||||||
}: ProjectItemsHeaderProps) {
|
}: ProjectItemsHeaderProps) {
|
||||||
const subtitle = isLoading
|
const subtitle = isLoading
|
||||||
? 'Loading items…'
|
? 'Loading items…'
|
||||||
: itemCount
|
: totalItemCount === 0
|
||||||
? `${itemCount} item${itemCount === 1 ? '' : 's'} tracked.`
|
? 'No items yet – add your first one below.'
|
||||||
: 'No items yet – add your first one below.';
|
: showActiveOnly && itemCount !== totalItemCount
|
||||||
|
? `${itemCount} of ${totalItemCount} active item${itemCount === 1 ? '' : 's'} shown.`
|
||||||
|
: `${totalItemCount} item${totalItemCount === 1 ? '' : 's'} tracked.`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="mb-6 flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
<header className="mb-6 flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||||
@@ -30,6 +38,15 @@ export function ProjectItemsHeader({
|
|||||||
<p className="text-sm text-slate-500">{subtitle}</p>
|
<p className="text-sm text-slate-500">{subtitle}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-3 text-xs font-medium uppercase tracking-wide text-slate-500 md:flex-row md:items-center md:gap-6">
|
<div className="flex flex-col gap-3 text-xs font-medium uppercase tracking-wide text-slate-500 md:flex-row md:items-center md:gap-6">
|
||||||
|
<label className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500"
|
||||||
|
checked={showActiveOnly}
|
||||||
|
onChange={(event) => onToggleActiveOnly(event.target.checked)}
|
||||||
|
/>
|
||||||
|
Active only
|
||||||
|
</label>
|
||||||
<label className="flex items-center gap-2">
|
<label className="flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|||||||
@@ -42,11 +42,19 @@ export function ProjectItemsSection({
|
|||||||
const [showDifferencesOnly, setShowDifferencesOnly] = useState(false);
|
const [showDifferencesOnly, setShowDifferencesOnly] = useState(false);
|
||||||
const [expandedItemId, setExpandedItemId] = useState<string | null>(null);
|
const [expandedItemId, setExpandedItemId] = useState<string | null>(null);
|
||||||
const [showLargeImages, setShowLargeImages] = useState(false);
|
const [showLargeImages, setShowLargeImages] = useState(false);
|
||||||
|
const [showActiveOnly, setShowActiveOnly] = useState(false);
|
||||||
|
|
||||||
|
const displayItems = useMemo(() => {
|
||||||
|
if (!showActiveOnly) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
return items.filter((item) => item.status === 'active');
|
||||||
|
}, [items, showActiveOnly]);
|
||||||
|
|
||||||
const mismatchedAttributes = useMemo(() => {
|
const mismatchedAttributes = useMemo(() => {
|
||||||
const result = new Set<string>();
|
const result = new Set<string>();
|
||||||
|
|
||||||
if (items.length <= 1 || projectAttributes.length === 0) {
|
if (displayItems.length <= 1 || projectAttributes.length === 0) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +67,7 @@ export function ProjectItemsSection({
|
|||||||
let referenceValue: unknown = null;
|
let referenceValue: unknown = null;
|
||||||
let hasReference = false;
|
let hasReference = false;
|
||||||
|
|
||||||
for (const item of items) {
|
for (const item of displayItems) {
|
||||||
const attributes = (item.attributes ?? {}) as Record<string, unknown>;
|
const attributes = (item.attributes ?? {}) as Record<string, unknown>;
|
||||||
const currentValue = attributes[attribute];
|
const currentValue = attributes[attribute];
|
||||||
|
|
||||||
@@ -77,7 +85,7 @@ export function ProjectItemsSection({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}, [items, projectAttributes]);
|
}, [displayItems, projectAttributes]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showDifferencesOnly && mismatchedAttributes.size === 0) {
|
if (showDifferencesOnly && mismatchedAttributes.size === 0) {
|
||||||
@@ -89,11 +97,11 @@ export function ProjectItemsSection({
|
|||||||
if (!expandedItemId) {
|
if (!expandedItemId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const itemStillPresent = items.some((item) => item.id === expandedItemId);
|
const itemStillPresent = displayItems.some((item) => item.id === expandedItemId);
|
||||||
if (!itemStillPresent) {
|
if (!itemStillPresent) {
|
||||||
setExpandedItemId(null);
|
setExpandedItemId(null);
|
||||||
}
|
}
|
||||||
}, [items, expandedItemId]);
|
}, [displayItems, expandedItemId]);
|
||||||
|
|
||||||
const visibleAttributes = useMemo(() => {
|
const visibleAttributes = useMemo(() => {
|
||||||
if (!showDifferencesOnly) {
|
if (!showDifferencesOnly) {
|
||||||
@@ -105,7 +113,7 @@ export function ProjectItemsSection({
|
|||||||
|
|
||||||
const hasAttributeDifferences = mismatchedAttributes.size > 0;
|
const hasAttributeDifferences = mismatchedAttributes.size > 0;
|
||||||
const showNoDifferencesMessage =
|
const showNoDifferencesMessage =
|
||||||
!hasAttributeDifferences && projectAttributes.length > 0 && items.length > 1;
|
!hasAttributeDifferences && projectAttributes.length > 0 && displayItems.length > 1;
|
||||||
|
|
||||||
const handleTogglePrices = (itemId: string) => {
|
const handleTogglePrices = (itemId: string) => {
|
||||||
setExpandedItemId((current) => (current === itemId ? null : itemId));
|
setExpandedItemId((current) => (current === itemId ? null : itemId));
|
||||||
@@ -115,13 +123,29 @@ export function ProjectItemsSection({
|
|||||||
<>
|
<>
|
||||||
<section className="rounded-2xl border border-slate-200 bg-white p-8 shadow-sm">
|
<section className="rounded-2xl border border-slate-200 bg-white p-8 shadow-sm">
|
||||||
<ProjectItemsHeader
|
<ProjectItemsHeader
|
||||||
itemCount={items.length}
|
itemCount={displayItems.length}
|
||||||
|
totalItemCount={items.length}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
showDifferencesOnly={showDifferencesOnly}
|
showDifferencesOnly={showDifferencesOnly}
|
||||||
hasAttributeDifferences={hasAttributeDifferences}
|
hasAttributeDifferences={hasAttributeDifferences}
|
||||||
onToggleDifferences={(value) => setShowDifferencesOnly(value)}
|
onToggleDifferences={(value) => setShowDifferencesOnly(value)}
|
||||||
showLargeImages={showLargeImages}
|
showLargeImages={showLargeImages}
|
||||||
onToggleLargeImages={(value) => setShowLargeImages(value)}
|
onToggleLargeImages={(value) => setShowLargeImages(value)}
|
||||||
|
showActiveOnly={showActiveOnly}
|
||||||
|
onToggleActiveOnly={(value) => {
|
||||||
|
setShowActiveOnly(value);
|
||||||
|
if (value) {
|
||||||
|
setExpandedItemId((current) => {
|
||||||
|
if (!current) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
const stillVisible = items.some(
|
||||||
|
(item) => item.id === current && item.status === 'active'
|
||||||
|
);
|
||||||
|
return stillVisible ? current : null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{error ? (
|
{error ? (
|
||||||
@@ -135,7 +159,7 @@ export function ProjectItemsSection({
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<ProjectItemsDesktopTable
|
<ProjectItemsDesktopTable
|
||||||
items={items}
|
items={displayItems}
|
||||||
visibleAttributes={visibleAttributes}
|
visibleAttributes={visibleAttributes}
|
||||||
expandedItemId={expandedItemId}
|
expandedItemId={expandedItemId}
|
||||||
onTogglePrices={handleTogglePrices}
|
onTogglePrices={handleTogglePrices}
|
||||||
@@ -147,7 +171,7 @@ export function ProjectItemsSection({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<ProjectItemsMobileList
|
<ProjectItemsMobileList
|
||||||
items={items}
|
items={displayItems}
|
||||||
visibleAttributes={visibleAttributes}
|
visibleAttributes={visibleAttributes}
|
||||||
expandedItemId={expandedItemId}
|
expandedItemId={expandedItemId}
|
||||||
onTogglePrices={handleTogglePrices}
|
onTogglePrices={handleTogglePrices}
|
||||||
|
|||||||
Reference in New Issue
Block a user