asd
This commit is contained in:
@@ -297,25 +297,43 @@ export default function KeywordOpportunities() {
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
toast.success(`Successfully added ${result.created} keyword(s) to workflow`);
|
||||
// Show success message with created count
|
||||
if (result.created > 0) {
|
||||
toast.success(`Successfully added ${result.created} keyword(s) to workflow`);
|
||||
}
|
||||
|
||||
// Track these as recently added to preserve state during reload
|
||||
seedKeywordIds.forEach(id => {
|
||||
recentlyAddedRef.current.add(id);
|
||||
});
|
||||
// Show skipped count if any
|
||||
if (result.skipped > 0) {
|
||||
toast.warning(`${result.skipped} keyword(s) were skipped (already exist or validation failed)`);
|
||||
}
|
||||
|
||||
// Show detailed errors if any
|
||||
if (result.errors && result.errors.length > 0) {
|
||||
result.errors.forEach((error: string) => {
|
||||
toast.error(error, { duration: 8000 });
|
||||
});
|
||||
}
|
||||
|
||||
// Only track and mark as added if actually created
|
||||
if (result.created > 0) {
|
||||
// Track these as recently added to preserve state during reload
|
||||
seedKeywordIds.forEach(id => {
|
||||
recentlyAddedRef.current.add(id);
|
||||
});
|
||||
|
||||
// Immediately update state to mark keywords as added - this gives instant feedback
|
||||
setSeedKeywords(prevKeywords =>
|
||||
prevKeywords.map(kw =>
|
||||
seedKeywordIds.includes(kw.id)
|
||||
? { ...kw, isAdded: true }
|
||||
: kw
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Clear selection
|
||||
setSelectedIds([]);
|
||||
|
||||
// Immediately update state to mark keywords as added - this gives instant feedback
|
||||
setSeedKeywords(prevKeywords =>
|
||||
prevKeywords.map(kw =>
|
||||
seedKeywordIds.includes(kw.id)
|
||||
? { ...kw, isAdded: true }
|
||||
: kw
|
||||
)
|
||||
);
|
||||
|
||||
// Don't reload immediately - the state is already updated
|
||||
// The recentlyAddedRef will ensure they stay marked as added
|
||||
// Only reload if user changes filters/pagination
|
||||
@@ -546,6 +564,13 @@ export default function KeywordOpportunities() {
|
||||
],
|
||||
},
|
||||
],
|
||||
bulkActions: !activeSector ? [] : [
|
||||
{
|
||||
key: 'add_selected_to_workflow',
|
||||
label: 'Add Selected to Workflow',
|
||||
variant: 'primary' as const,
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [activeSector]);
|
||||
|
||||
@@ -555,6 +580,30 @@ export default function KeywordOpportunities() {
|
||||
title="Keyword Opportunities"
|
||||
badge={{ icon: <BoltIcon />, color: 'orange' }}
|
||||
/>
|
||||
|
||||
{/* Show info banner when no sector is selected */}
|
||||
{!activeSector && activeSite && (
|
||||
<div className="mx-6 mt-6 mb-4">
|
||||
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="w-5 h-5 text-blue-600 dark:text-blue-400 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-sm font-medium text-blue-900 dark:text-blue-200">
|
||||
Select a Sector to Add Keywords
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-blue-700 dark:text-blue-300">
|
||||
Please select a sector from the dropdown above to enable adding keywords to your workflow. Keywords must be added to a specific sector.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<TablePageTemplate
|
||||
columns={pageConfig.columns}
|
||||
data={seedKeywords}
|
||||
@@ -581,6 +630,11 @@ export default function KeywordOpportunities() {
|
||||
}}
|
||||
onRowAction={async (actionKey: string, row: SeedKeyword & { isAdded?: boolean }) => {
|
||||
if (actionKey === 'add_to_workflow') {
|
||||
// Check if sector is selected
|
||||
if (!activeSector) {
|
||||
toast.error('Please select a sector first');
|
||||
return;
|
||||
}
|
||||
// Don't allow adding already-added keywords
|
||||
if (row.isAdded) {
|
||||
toast.info('This keyword is already added to workflow');
|
||||
@@ -589,12 +643,17 @@ export default function KeywordOpportunities() {
|
||||
await handleAddToWorkflow([row.id]);
|
||||
}
|
||||
}}
|
||||
bulkActions={pageConfig.bulkActions}
|
||||
onBulkAction={async (actionKey: string, ids: string[]) => {
|
||||
if (actionKey === 'add_selected_to_workflow') {
|
||||
if (!activeSector) {
|
||||
toast.error('Please select a sector first');
|
||||
return;
|
||||
}
|
||||
await handleBulkAddSelected(ids);
|
||||
}
|
||||
}}
|
||||
onCreate={handleAddAll}
|
||||
onCreate={activeSector ? handleAddAll : undefined}
|
||||
createLabel="Add All to Workflow"
|
||||
onCreateIcon={<PlusIcon />}
|
||||
pagination={{
|
||||
|
||||
@@ -317,24 +317,42 @@ export default function IndustriesSectorsKeywords() {
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
toast.success(`Successfully added ${result.created} keyword(s) to workflow`);
|
||||
// Show success message with created count
|
||||
if (result.created > 0) {
|
||||
toast.success(`Successfully added ${result.created} keyword(s) to workflow`);
|
||||
}
|
||||
|
||||
// Track as recently added
|
||||
seedKeywordIds.forEach(id => {
|
||||
recentlyAddedRef.current.add(id);
|
||||
});
|
||||
// Show skipped count if any
|
||||
if (result.skipped > 0) {
|
||||
toast.warning(`${result.skipped} keyword(s) were skipped (already exist or validation failed)`);
|
||||
}
|
||||
|
||||
// Show detailed errors if any
|
||||
if (result.errors && result.errors.length > 0) {
|
||||
result.errors.forEach((error: string) => {
|
||||
toast.error(error, { duration: 8000 });
|
||||
});
|
||||
}
|
||||
|
||||
// Only track and mark as added if actually created
|
||||
if (result.created > 0) {
|
||||
// Track as recently added
|
||||
seedKeywordIds.forEach(id => {
|
||||
recentlyAddedRef.current.add(id);
|
||||
});
|
||||
|
||||
// Update state - mark as added
|
||||
setSeedKeywords(prevKeywords =>
|
||||
prevKeywords.map(kw =>
|
||||
seedKeywordIds.includes(kw.id)
|
||||
? { ...kw, isAdded: true }
|
||||
: kw
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Clear selection
|
||||
setSelectedIds([]);
|
||||
|
||||
// Update state immediately
|
||||
setSeedKeywords(prevKeywords =>
|
||||
prevKeywords.map(kw =>
|
||||
seedKeywordIds.includes(kw.id)
|
||||
? { ...kw, isAdded: true }
|
||||
: kw
|
||||
)
|
||||
);
|
||||
} else {
|
||||
toast.error(`Failed to add keywords: ${result.errors?.join(', ') || 'Unknown error'}`);
|
||||
}
|
||||
@@ -531,20 +549,28 @@ export default function IndustriesSectorsKeywords() {
|
||||
label: '',
|
||||
sortable: false,
|
||||
align: 'right' as const,
|
||||
render: (_value: any, row: SeedKeyword & { isAdded?: boolean }) => (
|
||||
<Button
|
||||
size="sm"
|
||||
variant={row.isAdded ? 'ghost' : 'primary'}
|
||||
disabled={row.isAdded}
|
||||
onClick={() => {
|
||||
if (!row.isAdded) {
|
||||
handleAddToWorkflow([row.id]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{row.isAdded ? 'Added' : 'Add to Workflow'}
|
||||
</Button>
|
||||
),
|
||||
render: (_value: any, row: SeedKeyword & { isAdded?: boolean }) => {
|
||||
const isDisabled = !activeSector || row.isAdded;
|
||||
const buttonText = row.isAdded ? 'Added' : 'Add to Workflow';
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant={row.isAdded ? 'ghost' : 'primary'}
|
||||
disabled={isDisabled}
|
||||
onClick={() => {
|
||||
if (!row.isAdded && activeSector) {
|
||||
handleAddToWorkflow([row.id]);
|
||||
}
|
||||
}}
|
||||
title={!activeSector ? 'Please select a sector first' : ''}
|
||||
>
|
||||
{buttonText}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
filters: [
|
||||
@@ -580,7 +606,7 @@ export default function IndustriesSectorsKeywords() {
|
||||
],
|
||||
},
|
||||
],
|
||||
bulkActions: [
|
||||
bulkActions: !activeSector ? [] : [
|
||||
{
|
||||
key: 'add_selected_to_workflow',
|
||||
label: 'Add Selected to Workflow',
|
||||
@@ -631,6 +657,29 @@ export default function IndustriesSectorsKeywords() {
|
||||
title="Add Keywords"
|
||||
badge={{ icon: <BoltIcon />, color: 'blue' }}
|
||||
/>
|
||||
{/* Show info banner when no sector is selected */}
|
||||
{!activeSector && activeSite && (
|
||||
<div className="mx-6 mt-6 mb-4">
|
||||
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="w-5 h-5 text-blue-600 dark:text-blue-400 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-sm font-medium text-blue-900 dark:text-blue-200">
|
||||
Select a Sector to Add Keywords
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-blue-700 dark:text-blue-300">
|
||||
Please select a sector from the dropdown above to enable adding keywords to your workflow. Keywords must be added to a specific sector.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<TablePageTemplate
|
||||
columns={pageConfig.columns}
|
||||
data={seedKeywords}
|
||||
|
||||
@@ -1906,10 +1906,10 @@ export async function fetchSeedKeywords(filters?: {
|
||||
/**
|
||||
* Add SeedKeywords to workflow (create Keywords records)
|
||||
*/
|
||||
export async function addSeedKeywordsToWorkflow(seedKeywordIds: number[], siteId: number, sectorId: number): Promise<{ success: boolean; created: number; errors?: string[] }> {
|
||||
export async function addSeedKeywordsToWorkflow(seedKeywordIds: number[], siteId: number, sectorId: number): Promise<{ success: boolean; created: number; skipped?: number; errors?: string[] }> {
|
||||
try {
|
||||
// fetchAPI extracts data from unified format {success: true, data: {...}}
|
||||
// So response is already the data object: {created: X, ...}
|
||||
// So response is already the data object: {created: X, skipped: X, errors: [...]}
|
||||
const response = await fetchAPI('/v1/planner/keywords/bulk_add_from_seed/', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
|
||||
Reference in New Issue
Block a user