Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions Frontend/src/components/analytics/PostingVolumeByHour.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react';

export interface HourlyVolume {
hour: number;
volume: number;
}

export interface PostingVolumeByHourProps {
hourlyData?: HourlyVolume[];
}

export const PostingVolumeByHour: React.FC<PostingVolumeByHourProps> = ({
hourlyData = [],
}) => {
const maxVolume = Math.max(...hourlyData.map(d => d.volume), 1);

return (
<div className="p-4 bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 shadow-sm">
<h3 className="text-lg font-semibold mb-2 text-gray-900 dark:text-gray-100">
Posting Volume by Hour of Day
</h3>
<div className="flex items-end h-40 gap-1 pt-4">
{Array.from({ length: 24 }).map((_, hour) => {
const item = hourlyData.find(d => d.hour === hour);
const vol = item ? item.volume : 0;
const heightPercent = (vol / maxVolume) * 100;
return (
<div key={hour} className="flex-1 flex flex-col items-center group">
<div className="w-full bg-gray-100 dark:bg-gray-800 h-full flex items-end rounded-t overflow-hidden">
<div
className="w-full bg-indigo-500 group-hover:bg-indigo-400 transition-all rounded-t"
style={{ height: `${heightPercent}%` }}
/>
</div>
<span className="text-[10px] text-gray-400 mt-1 font-mono">{hour}h</span>
</div>
);
})}
</div>
</div>
);
};

export default PostingVolumeByHour;
66 changes: 66 additions & 0 deletions Frontend/src/components/analytics/WeekdayHourHeatmap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react';

export interface WeekdayHourData {
day: string; // Mon, Tue, etc.
hour: number; // 0-23
count: number;
}

export interface WeekdayHourHeatmapProps {
data?: WeekdayHourData[];
onCellClick?: (day: string, hour: number) => void;
}

export const WeekdayHourHeatmap: React.FC<WeekdayHourHeatmapProps> = ({
data = [],
onCellClick,
}) => {
const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
const hours = Array.from({ length: 24 }, (_, i) => i);

const getIntensity = (day: string, hour: number) => {
const match = data.find(d => d.day === day && d.hour === hour);
return match ? match.count : 0;
};

return (
<div className="p-4 bg-white dark:bg-gray-900 rounded-lg shadow-sm border border-gray-200 dark:border-gray-800">
<h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-gray-100">
Weekday by Hour Posting Heatmap
</h3>
<div className="overflow-x-auto">
<div className="inline-block min-w-full">
<div className="grid grid-cols-25 gap-1 text-xs">
<div className="font-medium text-gray-500">Day</div>
{hours.map(h => (
<div key={h} className="text-center font-mono text-gray-400">
{h}
</div>
))}
{days.map(day => (
<React.Fragment key={day}>
<div className="font-medium text-gray-600 dark:text-gray-300 py-1">{day}</div>
{hours.map(hour => {
const val = getIntensity(day, hour);
return (
<button
key={`${day}-${hour}`}
onClick={() => onCellClick?.(day, hour)}
className="w-full h-6 rounded transition-colors hover:ring-2 ring-indigo-500"
style={{
backgroundColor: val > 0 ? `rgba(99, 102, 241, ${Math.min(1, val / 50)})` : '#f3f4f6',
}}
title={`${day} ${hour}:00 - ${val} posts`}
/>
);
})}
</React.Fragment>
))}
</div>
</div>
</div>
</div>
);
};

export default WeekdayHourHeatmap;
Loading