upto phase 4 completed
This commit is contained in:
@@ -7,7 +7,10 @@
|
||||
* This provides consistent data for the WorkflowCompletionWidget
|
||||
* across all pages.
|
||||
*
|
||||
* IMPORTANT: Content table structure
|
||||
* IMPORTANT: Widget displays site-wide stats only (no sector filtering)
|
||||
* to ensure consistent counts across all pages.
|
||||
*
|
||||
* Content table structure:
|
||||
* - Tasks is separate table
|
||||
* - Content table has status field: 'draft', 'review', 'approved', 'published'
|
||||
* - Images is separate table linked to content
|
||||
@@ -31,7 +34,6 @@ import {
|
||||
fetchAPI,
|
||||
} from '../services/api';
|
||||
import { useSiteStore } from '../store/siteStore';
|
||||
import { useSectorStore } from '../store/sectorStore';
|
||||
|
||||
// Time filter options (in days)
|
||||
export type TimeFilter = 'today' | '7' | '30' | '90' | 'all';
|
||||
@@ -135,7 +137,7 @@ function getDateFilter(timeFilter: TimeFilter): string | undefined {
|
||||
export function useWorkflowStats(timeFilter: TimeFilter = 'all') {
|
||||
const [stats, setStats] = useState<WorkflowStats>(defaultStats);
|
||||
const { activeSite } = useSiteStore();
|
||||
const { activeSector } = useSectorStore();
|
||||
// Note: No sector filtering - widget shows site-wide stats for consistency
|
||||
|
||||
const loadStats = useCallback(async () => {
|
||||
// Don't load if no active site - wait for site to be set
|
||||
@@ -151,16 +153,15 @@ export function useWorkflowStats(timeFilter: TimeFilter = 'all') {
|
||||
const dateFilter = getDateFilter(timeFilter);
|
||||
const dateParam = dateFilter ? `&created_at__gte=${dateFilter.split('T')[0]}` : '';
|
||||
|
||||
// Build site/sector params for direct API calls
|
||||
// IMPORTANT: Widget should always show site-wide stats for consistency
|
||||
// Sector filtering removed to ensure widget shows same counts on all pages
|
||||
const siteParam = `&site_id=${activeSite.id}`;
|
||||
const sectorParam = activeSector?.id ? `§or_id=${activeSector.id}` : '';
|
||||
const baseParams = `${siteParam}${sectorParam}`;
|
||||
const baseParams = siteParam; // No sector filter for consistent widget display
|
||||
|
||||
// Build common filters for fetch* functions
|
||||
// Build common filters for fetch* functions (also no sector filter)
|
||||
const baseFilters = {
|
||||
page_size: 1,
|
||||
site_id: activeSite.id,
|
||||
...(activeSector?.id && { sector_id: activeSector.id }),
|
||||
};
|
||||
|
||||
// Fetch all stats in parallel for performance
|
||||
@@ -217,9 +218,10 @@ export function useWorkflowStats(timeFilter: TimeFilter = 'all') {
|
||||
? fetchAPI(`/v1/writer/images/?page_size=1${baseParams}${dateParam}`)
|
||||
: fetchImages({ ...baseFilters }),
|
||||
// Credits usage from billing summary endpoint - includes by_operation breakdown
|
||||
// Site-wide credits (no sector filter) - baseParams already has no sector
|
||||
dateFilter
|
||||
? fetchAPI(`/v1/billing/credits/usage/summary/?start_date=${dateFilter}`)
|
||||
: fetchAPI('/v1/billing/credits/usage/summary/').catch(() => ({
|
||||
? fetchAPI(`/v1/billing/credits/usage/summary/?start_date=${dateFilter}${baseParams}`)
|
||||
: fetchAPI(`/v1/billing/credits/usage/summary/?${baseParams.substring(1)}`).catch(() => ({
|
||||
data: { total_credits_used: 0, by_operation: {} }
|
||||
})),
|
||||
]);
|
||||
@@ -276,7 +278,7 @@ export function useWorkflowStats(timeFilter: TimeFilter = 'all') {
|
||||
error: error.message || 'Failed to load workflow stats',
|
||||
}));
|
||||
}
|
||||
}, [activeSite?.id, activeSector?.id, timeFilter]);
|
||||
}, [activeSite?.id, timeFilter]); // Removed activeSector - widget shows site-wide stats only
|
||||
|
||||
// Load stats on mount and when dependencies change
|
||||
useEffect(() => {
|
||||
|
||||
@@ -797,7 +797,7 @@ const AutomationPage: React.FC = () => {
|
||||
className={`
|
||||
relative rounded-xl border border-gray-200 dark:border-gray-800 p-4 transition-all bg-white dark:bg-gray-900
|
||||
border-l-[5px] ${stageBorderColor}
|
||||
${isActive
|
||||
${ isActive
|
||||
? 'shadow-lg ring-2 ring-brand-200 dark:ring-brand-800'
|
||||
: isComplete
|
||||
? ''
|
||||
@@ -840,10 +840,10 @@ const AutomationPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Credits and Duration - only show during/after run */}
|
||||
{result && (result.credits_used > 0 || result.time_elapsed) && (
|
||||
{/* Credits and Duration - show during/after run */}
|
||||
{result && (result.credits_used !== undefined || result.time_elapsed) && (
|
||||
<div className="flex justify-between items-center py-2 border-t border-gray-200 dark:border-gray-700 text-xs">
|
||||
{result.credits_used > 0 && (
|
||||
{result.credits_used !== undefined && (
|
||||
<span className="font-semibold text-warning-600 dark:text-warning-400">{result.credits_used} credits</span>
|
||||
)}
|
||||
{result.time_elapsed && (
|
||||
@@ -960,7 +960,7 @@ const AutomationPage: React.FC = () => {
|
||||
{pending}
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-8 w-px bg-gray-200 dark:bg-gray-700"></div>
|
||||
<div className="h-8 w-px bg-gray-200 dark:border-gray-700"></div>
|
||||
<div className="text-center">
|
||||
<div className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase mb-0.5">Processed</div>
|
||||
<div className={`text-xl font-bold ${processed > 0 ? 'text-success-600 dark:text-success-400' : 'text-gray-400 dark:text-gray-500'}`}>
|
||||
@@ -969,10 +969,10 @@ const AutomationPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Credits and Duration - only show during/after run */}
|
||||
{result && (result.credits_used > 0 || result.time_elapsed) && (
|
||||
{/* Credits and Duration - show during/after run */}
|
||||
{result && (result.credits_used !== undefined || result.time_elapsed) && (
|
||||
<div className="flex justify-between items-center py-2 border-t border-gray-200 dark:border-gray-700 text-xs">
|
||||
{result.credits_used > 0 && (
|
||||
{result.credits_used !== undefined && (
|
||||
<span className="font-semibold text-warning-600 dark:text-warning-400">{result.credits_used} credits</span>
|
||||
)}
|
||||
{result.time_elapsed && (
|
||||
|
||||
@@ -104,31 +104,38 @@ export default function ContentCalendar() {
|
||||
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
||||
const thirtyDaysFromNow = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);
|
||||
|
||||
// Published in last 30 days (items with external_id)
|
||||
// Published in last 30 days - check EITHER external_id OR site_status='published'
|
||||
const publishedLast30Days = allContent.filter((c: Content) => {
|
||||
if (!c.external_id || c.external_id === '') return false;
|
||||
const isPublished = (c.external_id && c.external_id !== '') || c.site_status === 'published';
|
||||
if (!isPublished) return false;
|
||||
// Use updated_at as publish date since site_status_updated_at may not be set
|
||||
const publishDate = c.updated_at ? new Date(c.updated_at) : null;
|
||||
return publishDate && publishDate >= thirtyDaysAgo;
|
||||
}).length;
|
||||
|
||||
// Scheduled in next 30 days (exclude already published items with external_id)
|
||||
// Scheduled in next 30 days (exclude already published items)
|
||||
const scheduledNext30Days = allContent.filter((c: Content) => {
|
||||
if (c.site_status !== 'scheduled') return false;
|
||||
if (c.external_id && c.external_id !== '') return false; // Exclude already published
|
||||
// Exclude already published (either has external_id OR site_status='published')
|
||||
if ((c.external_id && c.external_id !== '') || c.site_status === 'published') return false;
|
||||
const schedDate = c.scheduled_publish_at ? new Date(c.scheduled_publish_at) : null;
|
||||
return schedDate && schedDate >= now && schedDate <= thirtyDaysFromNow;
|
||||
}).length;
|
||||
|
||||
return {
|
||||
// Scheduled count excludes items that are already published (have external_id)
|
||||
// Scheduled count excludes items that are already published
|
||||
scheduled: allContent.filter((c: Content) =>
|
||||
c.site_status === 'scheduled' && (!c.external_id || c.external_id === '')
|
||||
c.site_status === 'scheduled' && (!c.external_id || c.external_id === '') && c.site_status !== 'published'
|
||||
).length,
|
||||
publishing: allContent.filter((c: Content) => c.site_status === 'publishing').length,
|
||||
published: allContent.filter((c: Content) => c.external_id && c.external_id !== '').length,
|
||||
// Published: check EITHER external_id OR site_status='published'
|
||||
published: allContent.filter((c: Content) =>
|
||||
(c.external_id && c.external_id !== '') || c.site_status === 'published'
|
||||
).length,
|
||||
review: allContent.filter((c: Content) => c.status === 'review').length,
|
||||
approved: allContent.filter((c: Content) => c.status === 'approved' && (!c.external_id || c.external_id === '')).length,
|
||||
approved: allContent.filter((c: Content) =>
|
||||
c.status === 'approved' && (!c.external_id || c.external_id === '') && c.site_status !== 'published'
|
||||
).length,
|
||||
publishedLast30Days,
|
||||
scheduledNext30Days,
|
||||
};
|
||||
|
||||
@@ -1268,6 +1268,19 @@ export default function SiteSettings() {
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Save Button */}
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
variant="primary"
|
||||
tone="brand"
|
||||
onClick={() => savePublishingSettings(publishingSettings)}
|
||||
isLoading={publishingSettingsSaving}
|
||||
disabled={publishingSettingsSaving}
|
||||
>
|
||||
Save Publishing Settings
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<Card>
|
||||
|
||||
Reference in New Issue
Block a user