conten view tempalte tiem fix
This commit is contained in:
@@ -21,6 +21,7 @@ import { ArrowLeftIcon, CalendarIcon, TagIcon, FileTextIcon, CheckCircleIcon, XC
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import Button from '../components/ui/button/Button';
|
import Button from '../components/ui/button/Button';
|
||||||
import { useToast } from '../components/ui/toast/ToastContainer';
|
import { useToast } from '../components/ui/toast/ToastContainer';
|
||||||
|
import { formatDateTime, formatDateTimeLocal, getDatePartsInAccountTimezone, toUtcISOStringFromLocal } from '../utils/date';
|
||||||
|
|
||||||
interface ContentViewTemplateProps {
|
interface ContentViewTemplateProps {
|
||||||
content: Content | null;
|
content: Content | null;
|
||||||
@@ -632,16 +633,17 @@ export default function ContentViewTemplate({ content, loading, onBack }: Conten
|
|||||||
// Initialize schedule datetime when content loads
|
// Initialize schedule datetime when content loads
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (content?.scheduled_publish_at) {
|
if (content?.scheduled_publish_at) {
|
||||||
// Convert ISO string to datetime-local format (YYYY-MM-DDTHH:mm)
|
const localDateTime = formatDateTimeLocal(content.scheduled_publish_at);
|
||||||
const date = new Date(content.scheduled_publish_at);
|
|
||||||
const localDateTime = date.toISOString().slice(0, 16);
|
|
||||||
setScheduleDateTime(localDateTime);
|
setScheduleDateTime(localDateTime);
|
||||||
} else if (content?.site_status === 'failed') {
|
} else if (content?.site_status === 'failed') {
|
||||||
// Default to tomorrow at 9 AM for failed items without a schedule
|
// Default to tomorrow at 9 AM (account timezone) for failed items without a schedule
|
||||||
const tomorrow = new Date();
|
const todayParts = getDatePartsInAccountTimezone(new Date());
|
||||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
const baseDate = new Date(Date.UTC(todayParts.year, todayParts.month - 1, todayParts.day));
|
||||||
tomorrow.setHours(9, 0, 0, 0);
|
baseDate.setUTCDate(baseDate.getUTCDate() + 1);
|
||||||
setScheduleDateTime(tomorrow.toISOString().slice(0, 16));
|
const year = baseDate.getUTCFullYear();
|
||||||
|
const month = String(baseDate.getUTCMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(baseDate.getUTCDate()).padStart(2, '0');
|
||||||
|
setScheduleDateTime(`${year}-${month}-${day}T09:00`);
|
||||||
}
|
}
|
||||||
}, [content?.scheduled_publish_at, content?.site_status]);
|
}, [content?.scheduled_publish_at, content?.site_status]);
|
||||||
|
|
||||||
@@ -651,7 +653,11 @@ export default function ContentViewTemplate({ content, loading, onBack }: Conten
|
|||||||
|
|
||||||
setIsUpdatingSchedule(true);
|
setIsUpdatingSchedule(true);
|
||||||
try {
|
try {
|
||||||
const isoDateTime = new Date(scheduleDateTime).toISOString();
|
const isoDateTime = toUtcISOStringFromLocal(scheduleDateTime);
|
||||||
|
if (!isoDateTime) {
|
||||||
|
toast.error('Invalid schedule date/time.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Use reschedule endpoint for failed items, schedule endpoint for scheduled items
|
// Use reschedule endpoint for failed items, schedule endpoint for scheduled items
|
||||||
const endpoint = content.site_status === 'failed'
|
const endpoint = content.site_status === 'failed'
|
||||||
@@ -875,20 +881,6 @@ export default function ContentViewTemplate({ content, loading, onBack }: Conten
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatDate = (dateString: string) => {
|
|
||||||
try {
|
|
||||||
return new Date(dateString).toLocaleDateString('en-US', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'long',
|
|
||||||
day: 'numeric',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
return dateString;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStatusColor = (status: string) => {
|
const getStatusColor = (status: string) => {
|
||||||
const statusLower = status.toLowerCase();
|
const statusLower = status.toLowerCase();
|
||||||
if (statusLower === 'generated' || statusLower === 'published' || statusLower === 'complete') {
|
if (statusLower === 'generated' || statusLower === 'published' || statusLower === 'complete') {
|
||||||
@@ -974,7 +966,7 @@ export default function ContentViewTemplate({ content, loading, onBack }: Conten
|
|||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400">Generated</p>
|
<p className="text-xs text-gray-500 dark:text-gray-400">Generated</p>
|
||||||
<p className="text-sm font-medium text-gray-900 dark:text-white">
|
<p className="text-sm font-medium text-gray-900 dark:text-white">
|
||||||
{formatDate(content.generated_at)}
|
{formatDateTime(content.generated_at)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -984,7 +976,7 @@ export default function ContentViewTemplate({ content, loading, onBack }: Conten
|
|||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400">Last Updated</p>
|
<p className="text-xs text-gray-500 dark:text-gray-400">Last Updated</p>
|
||||||
<p className="text-sm font-medium text-gray-900 dark:text-white">
|
<p className="text-sm font-medium text-gray-900 dark:text-white">
|
||||||
{formatDate(content.updated_at)}
|
{formatDateTime(content.updated_at)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1225,14 +1217,16 @@ export default function ContentViewTemplate({ content, loading, onBack }: Conten
|
|||||||
setIsEditingSchedule(false);
|
setIsEditingSchedule(false);
|
||||||
// Reset to original value or tomorrow if failed
|
// Reset to original value or tomorrow if failed
|
||||||
if (content.scheduled_publish_at) {
|
if (content.scheduled_publish_at) {
|
||||||
const date = new Date(content.scheduled_publish_at);
|
setScheduleDateTime(formatDateTimeLocal(content.scheduled_publish_at));
|
||||||
setScheduleDateTime(date.toISOString().slice(0, 16));
|
|
||||||
} else if (content.site_status === 'failed') {
|
} else if (content.site_status === 'failed') {
|
||||||
// Default to tomorrow for failed items
|
// Default to tomorrow for failed items
|
||||||
const tomorrow = new Date();
|
const todayParts = getDatePartsInAccountTimezone(new Date());
|
||||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
const baseDate = new Date(Date.UTC(todayParts.year, todayParts.month - 1, todayParts.day));
|
||||||
tomorrow.setHours(9, 0, 0, 0);
|
baseDate.setUTCDate(baseDate.getUTCDate() + 1);
|
||||||
setScheduleDateTime(tomorrow.toISOString().slice(0, 16));
|
const year = baseDate.getUTCFullYear();
|
||||||
|
const month = String(baseDate.getUTCMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(baseDate.getUTCDate()).padStart(2, '0');
|
||||||
|
setScheduleDateTime(`${year}-${month}-${day}T09:00`);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -1243,10 +1237,10 @@ export default function ContentViewTemplate({ content, loading, onBack }: Conten
|
|||||||
<>
|
<>
|
||||||
<span className="text-sm text-gray-500 dark:text-gray-400">
|
<span className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
{content.site_status === 'failed' && (
|
{content.site_status === 'failed' && (
|
||||||
content.scheduled_publish_at ? `Was scheduled: ${formatDate(content.scheduled_publish_at)}` : 'Not scheduled'
|
content.scheduled_publish_at ? `Was scheduled: ${formatDateTime(content.scheduled_publish_at)}` : 'Not scheduled'
|
||||||
)}
|
)}
|
||||||
{content.site_status === 'scheduled' && (
|
{content.site_status === 'scheduled' && (
|
||||||
content.scheduled_publish_at ? formatDate(content.scheduled_publish_at) : ''
|
content.scheduled_publish_at ? formatDateTime(content.scheduled_publish_at) : ''
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -19,6 +19,28 @@ const getDatePartsInTimezone = (date: Date, timeZone: string) => {
|
|||||||
return { year, month, day };
|
return { year, month, day };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getDatePartsInAccountTimezone = (date: Date = new Date()) =>
|
||||||
|
getDatePartsInTimezone(date, getAccountTimezone());
|
||||||
|
|
||||||
|
const getTimeZoneOffset = (date: Date, timeZone: string): number => {
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-US', {
|
||||||
|
timeZone,
|
||||||
|
hour12: false,
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
});
|
||||||
|
|
||||||
|
const parts = formatter.formatToParts(date);
|
||||||
|
const values = Object.fromEntries(parts.map((part) => [part.type, part.value]));
|
||||||
|
const localIso = `${values.year}-${values.month}-${values.day}T${values.hour}:${values.minute}:${values.second}Z`;
|
||||||
|
const localDate = new Date(localIso);
|
||||||
|
return (localDate.getTime() - date.getTime()) / 60000;
|
||||||
|
};
|
||||||
|
|
||||||
export function formatRelativeDate(dateString: string | Date): string {
|
export function formatRelativeDate(dateString: string | Date): string {
|
||||||
if (!dateString) {
|
if (!dateString) {
|
||||||
return 'Today';
|
return 'Today';
|
||||||
@@ -162,3 +184,57 @@ export function formatTime(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format date/time to datetime-local value using account timezone
|
||||||
|
* @param dateString - ISO date string or Date object
|
||||||
|
*/
|
||||||
|
export function formatDateTimeLocal(
|
||||||
|
dateString: string | Date | null | undefined
|
||||||
|
): string {
|
||||||
|
if (!dateString) return '';
|
||||||
|
|
||||||
|
const date = typeof dateString === 'string' ? new Date(dateString) : dateString;
|
||||||
|
if (isNaN(date.getTime())) return '';
|
||||||
|
|
||||||
|
const timeZone = getAccountTimezone();
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-US', {
|
||||||
|
timeZone,
|
||||||
|
hour12: false,
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
});
|
||||||
|
|
||||||
|
const parts = formatter.formatToParts(date);
|
||||||
|
const values = Object.fromEntries(parts.map((part) => [part.type, part.value]));
|
||||||
|
return `${values.year}-${values.month}-${values.day}T${values.hour}:${values.minute}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a datetime-local value (in account timezone) to a UTC ISO string
|
||||||
|
* @param dateTimeLocal - datetime-local value (YYYY-MM-DDTHH:mm)
|
||||||
|
*/
|
||||||
|
export function toUtcISOStringFromLocal(
|
||||||
|
dateTimeLocal: string,
|
||||||
|
timeZone: string = getAccountTimezone()
|
||||||
|
): string | null {
|
||||||
|
if (!dateTimeLocal) return null;
|
||||||
|
|
||||||
|
const [datePart, timePart] = dateTimeLocal.split('T');
|
||||||
|
if (!datePart || !timePart) return null;
|
||||||
|
|
||||||
|
const [year, month, day] = datePart.split('-').map((value) => parseInt(value, 10));
|
||||||
|
const [hour, minute] = timePart.split(':').map((value) => parseInt(value, 10));
|
||||||
|
|
||||||
|
if ([year, month, day, hour, minute].some((value) => Number.isNaN(value))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const utcDate = new Date(Date.UTC(year, month - 1, day, hour, minute, 0));
|
||||||
|
const offsetMinutes = getTimeZoneOffset(utcDate, timeZone);
|
||||||
|
const adjustedDate = new Date(utcDate.getTime() - offsetMinutes * 60000);
|
||||||
|
return adjustedDate.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user