mirror of
https://github.com/sakaljurgis/best-choice.git
synced 2026-07-08 21:47:40 +00:00
attribute sorting
This commit is contained in:
@@ -218,35 +218,67 @@ const normalizeArrayMembership = (entries: AttributeEntry[]): AttributeEntry[] =
|
||||
});
|
||||
};
|
||||
|
||||
const computeTrackedKeySet = (attributes: string[]): Set<string> =>
|
||||
new Set(
|
||||
attributes
|
||||
.map((attribute) => attribute.trim().toLowerCase())
|
||||
.filter((attribute) => attribute.length)
|
||||
);
|
||||
interface TrackedKeyMetadata {
|
||||
set: Set<string>;
|
||||
order: Map<string, number>;
|
||||
}
|
||||
|
||||
const isTrackedKey = (key: string, trackedKeys: Set<string>): boolean => {
|
||||
const computeTrackedKeyMetadata = (attributes: string[]): TrackedKeyMetadata => {
|
||||
const order = new Map<string, number>();
|
||||
attributes
|
||||
.map((attribute) => attribute.trim())
|
||||
.filter((attribute) => attribute.length)
|
||||
.forEach((attribute, index) => {
|
||||
const normalized = attribute.toLowerCase();
|
||||
if (!order.has(normalized)) {
|
||||
order.set(normalized, index);
|
||||
}
|
||||
});
|
||||
return {
|
||||
set: new Set(order.keys()),
|
||||
order
|
||||
};
|
||||
};
|
||||
|
||||
const isTrackedKey = (key: string, metadata: TrackedKeyMetadata): boolean => {
|
||||
if (!key) {
|
||||
return false;
|
||||
}
|
||||
return trackedKeys.has(key.trim().toLowerCase());
|
||||
return metadata.set.has(key.trim().toLowerCase());
|
||||
};
|
||||
|
||||
const sortAttributeEntries = (
|
||||
entries: AttributeEntry[],
|
||||
trackedKeys: Set<string>
|
||||
trackedMetadata: TrackedKeyMetadata
|
||||
): AttributeEntry[] => {
|
||||
const decorated = entries.map((entry, index) => ({
|
||||
entry,
|
||||
index,
|
||||
isTracked: isTrackedKey(entry.key, trackedKeys)
|
||||
isTracked: isTrackedKey(entry.key, trackedMetadata)
|
||||
}));
|
||||
|
||||
decorated.sort((a, b) => {
|
||||
if (a.isTracked === b.isTracked) {
|
||||
if (a.isTracked && b.isTracked) {
|
||||
const keyA = a.entry.key.trim().toLowerCase();
|
||||
const keyB = b.entry.key.trim().toLowerCase();
|
||||
const orderA = trackedMetadata.order.get(keyA);
|
||||
const orderB = trackedMetadata.order.get(keyB);
|
||||
if (orderA !== undefined && orderB !== undefined && orderA !== orderB) {
|
||||
return orderA - orderB;
|
||||
}
|
||||
const keyComparison = keyA.localeCompare(keyB);
|
||||
if (keyComparison !== 0) {
|
||||
return keyComparison;
|
||||
}
|
||||
return a.index - b.index;
|
||||
}
|
||||
return a.isTracked ? -1 : 1;
|
||||
if (a.isTracked) {
|
||||
return -1;
|
||||
}
|
||||
if (b.isTracked) {
|
||||
return 1;
|
||||
}
|
||||
return a.index - b.index;
|
||||
});
|
||||
|
||||
return decorated.map(({ entry }) => entry);
|
||||
@@ -254,8 +286,9 @@ const sortAttributeEntries = (
|
||||
|
||||
const normalizeAndSortEntries = (
|
||||
entries: AttributeEntry[],
|
||||
trackedKeys: Set<string>
|
||||
): AttributeEntry[] => sortAttributeEntries(normalizeArrayMembership(entries), trackedKeys);
|
||||
trackedMetadata: TrackedKeyMetadata
|
||||
): AttributeEntry[] =>
|
||||
sortAttributeEntries(normalizeArrayMembership(entries), trackedMetadata);
|
||||
|
||||
export function ItemFormModal({
|
||||
isOpen,
|
||||
@@ -277,18 +310,18 @@ export function ItemFormModal({
|
||||
const [attributeEntries, setAttributeEntries] = useState<AttributeEntry[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const isFormDisabled = isSubmitting || isInitialDataLoading;
|
||||
const trackedAttributeSet = useMemo(
|
||||
() => computeTrackedKeySet(projectAttributes),
|
||||
const trackedAttributeMetadata = useMemo(
|
||||
() => computeTrackedKeyMetadata(projectAttributes),
|
||||
[projectAttributes]
|
||||
);
|
||||
|
||||
const updateAttributeEntries = useCallback(
|
||||
(updater: (previous: AttributeEntry[]) => AttributeEntry[]) => {
|
||||
setAttributeEntries((previous) =>
|
||||
normalizeAndSortEntries(updater(previous), trackedAttributeSet)
|
||||
normalizeAndSortEntries(updater(previous), trackedAttributeMetadata)
|
||||
);
|
||||
},
|
||||
[trackedAttributeSet]
|
||||
[trackedAttributeMetadata]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -336,9 +369,12 @@ export function ItemFormModal({
|
||||
});
|
||||
|
||||
setAttributeEntries(
|
||||
normalizeAndSortEntries([...trackedEntries, ...additionalEntries], trackedAttributeSet)
|
||||
normalizeAndSortEntries(
|
||||
[...trackedEntries, ...additionalEntries],
|
||||
trackedAttributeMetadata
|
||||
)
|
||||
);
|
||||
}, [isOpen, initialUrl, initialData, projectAttributes, trackedAttributeSet]);
|
||||
}, [isOpen, initialUrl, initialData, projectAttributes, trackedAttributeMetadata]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
@@ -623,7 +659,7 @@ export function ItemFormModal({
|
||||
trimmedValue === inference.canonicalText
|
||||
? null
|
||||
: inference.canonicalText;
|
||||
const isTracked = isTrackedKey(attribute.key, trackedAttributeSet);
|
||||
const isTracked = isTrackedKey(attribute.key, trackedAttributeMetadata);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
const normalizeSelection = (attributes: string[]): string[] =>
|
||||
Array.from(
|
||||
new Set(
|
||||
attributes
|
||||
.map((attribute) => attribute.trim())
|
||||
.filter((attribute) => attribute.length > 0)
|
||||
)
|
||||
).sort((a, b) => a.localeCompare(b));
|
||||
|
||||
export interface TrackedAttributesModalProps {
|
||||
isOpen: boolean;
|
||||
availableAttributes: string[];
|
||||
@@ -19,13 +28,15 @@ export function TrackedAttributesModal({
|
||||
isSubmitting,
|
||||
errorMessage
|
||||
}: TrackedAttributesModalProps) {
|
||||
const [selectedAttributes, setSelectedAttributes] = useState<string[]>(initialSelection);
|
||||
const [selectedAttributes, setSelectedAttributes] = useState<string[]>(() =>
|
||||
normalizeSelection(initialSelection)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
setSelectedAttributes(initialSelection);
|
||||
setSelectedAttributes(normalizeSelection(initialSelection));
|
||||
}, [isOpen, initialSelection]);
|
||||
|
||||
const sortedAttributes = useMemo(
|
||||
@@ -38,21 +49,16 @@ export function TrackedAttributesModal({
|
||||
}
|
||||
|
||||
const toggleAttribute = (attribute: string) => {
|
||||
setSelectedAttributes((current) =>
|
||||
current.includes(attribute)
|
||||
? current.filter((value) => value !== attribute)
|
||||
: [...current, attribute]
|
||||
);
|
||||
setSelectedAttributes((current) => {
|
||||
if (current.includes(attribute)) {
|
||||
return normalizeSelection(current.filter((value) => value !== attribute));
|
||||
}
|
||||
return normalizeSelection([...current, attribute]);
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const normalized = Array.from(
|
||||
new Set(
|
||||
selectedAttributes
|
||||
.map((attribute) => attribute.trim())
|
||||
.filter((attribute) => attribute.length > 0)
|
||||
)
|
||||
);
|
||||
const normalized = normalizeSelection(selectedAttributes);
|
||||
await onSubmit(normalized);
|
||||
};
|
||||
|
||||
|
||||
@@ -41,14 +41,20 @@ export function ProjectDetailPage() {
|
||||
|
||||
const project = projectQuery.data;
|
||||
const items = useMemo(() => itemsQuery.data?.data ?? [], [itemsQuery.data]);
|
||||
const projectAttributes = useMemo(() => project?.attributes ?? [], [project]);
|
||||
const projectAttributes = useMemo(() => {
|
||||
if (!project?.attributes) {
|
||||
return [];
|
||||
}
|
||||
const normalized = project.attributes
|
||||
.map((attribute) => attribute.trim())
|
||||
.filter((attribute) => attribute.length);
|
||||
return Array.from(new Set(normalized)).sort((a, b) => a.localeCompare(b));
|
||||
}, [project]);
|
||||
const availableAttributes = useMemo(() => {
|
||||
const attributeSet = new Set<string>();
|
||||
|
||||
projectAttributes.forEach((attribute) => {
|
||||
if (attribute.trim().length) {
|
||||
attributeSet.add(attribute.trim());
|
||||
}
|
||||
attributeSet.add(attribute);
|
||||
});
|
||||
|
||||
items.forEach((item) => {
|
||||
@@ -61,7 +67,7 @@ export function ProjectDetailPage() {
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(attributeSet);
|
||||
return Array.from(attributeSet).sort((a, b) => a.localeCompare(b));
|
||||
}, [projectAttributes, items]);
|
||||
|
||||
const openItemModal = (mode: 'url' | 'manual' | 'edit', initialUrl: string | null) => {
|
||||
|
||||
Reference in New Issue
Block a user