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:
parent
e09f082420
commit
acce14c000
1 changed files with 32 additions and 6 deletions
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue