Add Resolve Items flow for unmatched Etsy sync titles

Reuses the missing-products modal on the Settings page: each unmatched
listing title can be aliased to an existing product or created with a
printing cost, then orders re-sync automatically to fill in costs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
dlawler489 2026-06-13 11:37:12 +10:00
parent e09f082420
commit acce14c000

View file

@ -2,7 +2,8 @@ import { useState, useEffect } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { DataManager } from '../utils/dataManager'; import { DataManager } from '../utils/dataManager';
import { setOrders } from '../store/slices/orderSlice'; import { setOrders } from '../store/slices/orderSlice';
import { Trash2, Download, RefreshCw, AlertTriangle, Database, Store, Link2, Unlink } from 'lucide-react'; import { MissingProductsModal } from '../components/MissingProductsModal';
import { Trash2, Download, RefreshCw, AlertTriangle, Database, Store, Link2, Unlink, Wrench } from 'lucide-react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import api from '../utils/api'; import api from '../utils/api';
@ -40,6 +41,7 @@ const Settings = () => {
const [isConnecting, setIsConnecting] = useState(false); const [isConnecting, setIsConnecting] = useState(false);
const [isSyncing, setIsSyncing] = useState(false); const [isSyncing, setIsSyncing] = useState(false);
const [unmatchedItems, setUnmatchedItems] = useState<string[]>([]); const [unmatchedItems, setUnmatchedItems] = useState<string[]>([]);
const [showResolveModal, setShowResolveModal] = useState(false);
useEffect(() => { useEffect(() => {
updateStorageSummary(); updateStorageSummary();
@ -230,17 +232,27 @@ const Settings = () => {
{unmatchedItems.length > 0 && ( {unmatchedItems.length > 0 && (
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4"> <div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<p className="text-yellow-800 font-medium text-sm mb-2"> <div className="flex items-start justify-between gap-4 mb-2">
{unmatchedItems.length} item title(s) couldn't be matched to your product catalog <p className="text-yellow-800 font-medium text-sm">
(their orders were synced without cost data): {unmatchedItems.length} item title(s) couldn't be matched to your product catalog
</p> (their orders were synced without cost data):
</p>
<button
onClick={() => setShowResolveModal(true)}
className="flex items-center gap-2 px-3 py-2 bg-yellow-600 text-white rounded-lg hover:bg-yellow-700 text-sm whitespace-nowrap"
>
<Wrench className="w-4 h-4" />
Resolve Items
</button>
</div>
<ul className="text-yellow-700 text-sm list-disc list-inside space-y-1"> <ul className="text-yellow-700 text-sm list-disc list-inside space-y-1">
{unmatchedItems.map((title, idx) => ( {unmatchedItems.map((title, idx) => (
<li key={idx}>{title}</li> <li key={idx}>{title}</li>
))} ))}
</ul> </ul>
<p className="text-yellow-700 text-xs mt-2"> <p className="text-yellow-700 text-xs mt-2">
Add these as products (or aliases on existing products) and sync again to fill in costs. Click Resolve Items to match each title to an existing product (saved as an alias)
or create it with a printing cost then orders re-sync with costs automatically.
</p> </p>
</div> </div>
)} )}
@ -456,6 +468,20 @@ const Settings = () => {
<h2 className="text-xl font-semibold text-gray-900 mb-4">Application Settings</h2> <h2 className="text-xl font-semibold text-gray-900 mb-4">Application Settings</h2>
<p className="text-gray-600">Additional application settings will be available here in future updates.</p> <p className="text-gray-600">Additional application settings will be available here in future updates.</p>
</div> </div>
{/* Resolve unmatched Etsy items: map to existing products (alias) or create new */}
{showResolveModal && (
<MissingProductsModal
missingProducts={unmatchedItems.map(title => ({ title, quantity: 1 }))}
onClose={() => setShowResolveModal(false)}
onComplete={() => {
setShowResolveModal(false);
setUnmatchedItems([]);
toast.success('Products resolved — re-syncing orders with costs…');
handleEtsySync();
}}
/>
)}
</div> </div>
); );
}; };