diff --git a/components/Fund/lib/FundUtils.ts b/components/Fund/lib/FundUtils.ts index 978ca6c87..1bbe4f4b1 100644 --- a/components/Fund/lib/FundUtils.ts +++ b/components/Fund/lib/FundUtils.ts @@ -4,35 +4,63 @@ interface Update { content?: any; } +interface FundraisingMetadata { + startDate?: string; + endDate?: string; +} + +interface WorkData { + createdDate: string; +} + /** - * Calculate the update rate as a percentage of months with updates in the last 12 months - * Only the first update in each month counts towards the rate - * @param updates - Array of updates with createdDate - * @returns Percentage (0-100) representing how many months had updates + * Determine the start date for when updates can begin posting + * @param fundraising - Fundraising metadata object + * @param work - Work object with creation date + * @returns ISO date string for when updates can start */ -export const calculateUpdateRate = (updates: Update[]): number => { - if (updates.length === 0) { - return 0; +export const getUpdatesStartDate = ( + fundraising?: FundraisingMetadata | null, + work?: WorkData +): string => { + // Always use fundraise start date if available + if (fundraising?.startDate) { + return fundraising.startDate; + } + // Fallback to endDate minus 1 month if we have endDate + if (fundraising?.endDate) { + const endDate = new Date(fundraising.endDate); + endDate.setMonth(endDate.getMonth() - 1); + return endDate.toISOString(); } + // Final fallback to work creation date + return work?.createdDate || new Date().toISOString(); +}; +/** + * Determines the normalized start date for a timeline. + * @param startDate - Optional preferred start date string. + * @param updates - Array of updates, used as a fallback. + * @returns A normalized Date object for the start of the timeline. + */ +export const getTimelineStartDate = (startDate?: string, updates: Update[] = []): Date => { const now = new Date(); - const updatesInLast12Months = updates.filter((update) => { - const updateDate = new Date(update.createdDate); - const monthsDiff = - (now.getFullYear() - updateDate.getFullYear()) * 12 + - (now.getMonth() - updateDate.getMonth()); - return monthsDiff <= 12; - }); - // Group updates by month-year and only count unique months - const uniqueMonths = new Set(); + if (startDate) { + const startDateObj = new Date(startDate); + // Normalize to the beginning of the start month to ensure we include the full month + return new Date(startDateObj.getFullYear(), startDateObj.getMonth(), 1); + } - updatesInLast12Months.forEach((update) => { - const updateDate = new Date(update.createdDate); - const monthYear = `${updateDate.getFullYear()}-${updateDate.getMonth()}`; - uniqueMonths.add(monthYear); - }); + if (updates.length > 0) { + // Find the earliest update + const earliestUpdate = updates.reduce((earliest, update) => { + const updateDate = new Date(update.createdDate); + return updateDate < earliest ? updateDate : earliest; + }, new Date(updates[0].createdDate)); + return new Date(earliestUpdate.getFullYear(), earliestUpdate.getMonth(), 1); + } - // Calculate percentage based on unique months with updates out of 12 months - return Math.round((uniqueMonths.size / 12) * 100); + // Default to 3 months ago + return new Date(now.getFullYear(), now.getMonth() - 2, 1); }; diff --git a/components/ui/ProgressUpdates.tsx b/components/ui/ProgressUpdates.tsx index f3a5b8f7d..a737ca62d 100644 --- a/components/ui/ProgressUpdates.tsx +++ b/components/ui/ProgressUpdates.tsx @@ -1,4 +1,8 @@ +'use client'; + import React from 'react'; +import { useRouter, usePathname } from 'next/navigation'; +import { getTimelineStartDate } from '@/components/Fund/lib/FundUtils'; interface Update { id: number; @@ -8,22 +12,38 @@ interface Update { interface ProgressUpdatesProps { updates: Update[]; + startDate?: string; // When updates can start being posted (e.g., fundraise start date) className?: string; } export const ProgressUpdates: React.FC = ({ updates = [], + startDate, className = '', }) => { - // Generate exactly 12 months starting from current date + const router = useRouter(); + const pathname = usePathname(); + + const navigateToUpdatesTab = () => { + if (!pathname) return; + const basePath = pathname.replace( + /\/(updates|conversation|applications|reviews|bounties|history)$/i, + '' + ); + const target = `${basePath}/updates`; + router.push(target); + }; + // Generate timeline from startDate to current date const generateTimeline = () => { const timeline = []; const now = new Date(); - const startDate = new Date(now.getFullYear(), now.getMonth(), 1); // Start from current month - const current = new Date(startDate); - let monthCount = 0; - while (monthCount < 12) { + const start = getTimelineStartDate(startDate, updates); + const current = new Date(start); + const currentMonthStart = new Date(now.getFullYear(), now.getMonth(), 1); + + // Generate months from start to current month (inclusive) + while (current <= currentMonthStart) { const monthYear = `${current.getFullYear()}-${String(current.getMonth() + 1).padStart(2, '0')}`; const monthName = current.toLocaleDateString('en-US', { month: 'short' }); const year = current.getFullYear(); @@ -45,7 +65,6 @@ export const ProgressUpdates: React.FC = ({ }); current.setMonth(current.getMonth() + 1); - monthCount++; } return timeline; @@ -53,94 +72,45 @@ export const ProgressUpdates: React.FC = ({ const timeline = generateTimeline(); + // Don't render if timeline is empty + if (timeline.length === 0) { + return null; + } + return (
{/* Monthly Timeline */} -
+
{timeline.map((month) => { - const now = new Date(); - const monthDate = new Date(month.year, new Date(`${month.monthName} 1, 2000`).getMonth()); - const currentMonthStart = new Date(now.getFullYear(), now.getMonth(), 1); - const isInPast = monthDate < currentMonthStart; - const isPastWithoutUpdates = isInPast && !month.hasUpdate; + const monthText = `${month.monthName} ${String(month.year).slice(-2)}`; return ( -
1 ? 's' : ''}` - : `${month.monthName} ${month.year} - No updates` - } - > - {/* Diagonal lines for past months without updates */} - {isPastWithoutUpdates && ( -
+
+ {month.hasUpdate ? ( + + ) : ( + + {monthText} + )} - -
{month.monthName}
-
- {month.year.toString().slice(-2)} -
- - {/* Update Count Badge - Top Right Corner */} - {month.hasUpdate && ( -
- {month.updateCount} -
+ {month.updateCount > 1 && ( + x {month.updateCount} )}
); })}
- - {/* Compact Legend */} -
-
-
- Has updates -
-
-
- No updates -
-
); }; diff --git a/components/ui/badges/UpdateRateBadge.tsx b/components/ui/badges/UpdateRateBadge.tsx deleted file mode 100644 index 7d7a4006e..000000000 --- a/components/ui/badges/UpdateRateBadge.tsx +++ /dev/null @@ -1,30 +0,0 @@ -'use client'; - -import { Tooltip } from '@/components/ui/Tooltip'; - -interface UpdateRateBadgeProps { - updateRate: number; - className?: string; -} - -export const UpdateRateBadge = ({ updateRate, className = '' }: UpdateRateBadgeProps) => { - const tooltipContent = ( -
-

Update Rate

-

- This statistic aims to inform how communicative the authors are during the course of their - research. -

-
- ); - - return ( - - - 100% update rate - - - ); -}; diff --git a/components/work/FundDocument.tsx b/components/work/FundDocument.tsx index f55e00e11..87d9fa1de 100644 --- a/components/work/FundDocument.tsx +++ b/components/work/FundDocument.tsx @@ -14,10 +14,9 @@ import { PostBlockEditor } from './PostBlockEditor'; import { FundraiseProgress } from '@/components/Fund/FundraiseProgress'; import { ProgressUpdates } from '@/components/ui/ProgressUpdates'; import { useStorageKey } from '@/utils/storageKeys'; -import { calculateUpdateRate } from '@/components/Fund/lib/FundUtils'; +import { getUpdatesStartDate } from '@/components/Fund/lib/FundUtils'; import { FundingRightSidebar } from './FundingRightSidebar'; import { useUser } from '@/contexts/UserContext'; -import { UpdateRateBadge } from '@/components/ui/badges/UpdateRateBadge'; import { EarningOpportunityBanner } from '@/components/banners/EarningOpportunityBanner'; import { useShareModalContext } from '@/contexts/ShareContext'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -121,9 +120,6 @@ export const FundDocument = ({ Track updates made by authors about their research progress.

-
- -
({ @@ -131,6 +127,7 @@ export const FundDocument = ({ createdDate: update.createdDate, content: update.content, }))} + startDate={getUpdatesStartDate(metadata.fundraising, work)} />
diff --git a/components/work/FundingRightSidebar.tsx b/components/work/FundingRightSidebar.tsx index 61de2e995..5768bb71c 100644 --- a/components/work/FundingRightSidebar.tsx +++ b/components/work/FundingRightSidebar.tsx @@ -9,6 +9,7 @@ import { NonprofitSection } from './components/NonprofitSection'; import { FundersSection } from './components/FundersSection'; import { ApplicantsSection } from './components/ApplicantsSection'; import { UpdatesSection } from './components/UpdatesSection'; +import { getUpdatesStartDate } from '@/components/Fund/lib/FundUtils'; interface FundingRightSidebarProps { work: Work; @@ -37,7 +38,7 @@ export const FundingRightSidebar = ({ createdDate: comment.createdDate, content: comment.content, }))} - startDate={work.createdDate} + startDate={getUpdatesStartDate(metadata.fundraising, work)} className="p-0" /> {/* Applicants for the grant */} diff --git a/components/work/components/UpdatesSection.tsx b/components/work/components/UpdatesSection.tsx index 0bbd365f6..21a3ab9e9 100644 --- a/components/work/components/UpdatesSection.tsx +++ b/components/work/components/UpdatesSection.tsx @@ -2,8 +2,6 @@ import { Bell } from 'lucide-react'; import { ProgressUpdates } from '@/components/ui/ProgressUpdates'; -import { calculateUpdateRate } from '@/components/Fund/lib/FundUtils'; -import { UpdateRateBadge } from '@/components/ui/badges/UpdateRateBadge'; interface Update { id: number; @@ -18,12 +16,6 @@ interface UpdatesSectionProps { } export const UpdatesSection = ({ updates = [], startDate, className }: UpdatesSectionProps) => { - if (updates.length === 0 && !startDate) { - return null; - } - - const updateRate = calculateUpdateRate(updates); - return (
@@ -31,12 +23,9 @@ export const UpdatesSection = ({ updates = [], startDate, className }: UpdatesSe

Author Updates

-
- -
- +
); };