Monthly & Daily Posts Dashboard Widget Pro
//=======Monthly & Daily Posts Dashboard Widget Pro========================================
/**
* Plugin Name: Monthly & Daily Posts Dashboard Widget Pro
* Description: A modern, responsive WP dashboard widget showing annual post metrics with collapsible daily breakdowns.
* Version: 2.1.0
* Author: Your Name
* Text Domain: monthly-posts-widget
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Register Dashboard Widget
*/
add_action( 'wp_dashboard_setup', 'mpcw_register_dashboard_widget' );
function mpcw_register_dashboard_widget() {
wp_add_dashboard_widget(
'mpcw_monthly_posts_widget',
sprintf( __( 'Post Activity Overview (%d)', 'monthly-posts-widget' ), date( 'Y' ) ),
'mpcw_render_dashboard_widget'
);
}
/**
* Render Widget Content
*/
function mpcw_render_dashboard_widget() {
global $wpdb;
$current_year = (int) date( 'Y' );
$current_month = (int) date( 'n' );
// Fetch optimized month & day counts
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT MONTH(post_date) AS month_num, DAY(post_date) AS day_num, COUNT(ID) as count
FROM {$wpdb->posts}
WHERE post_type = 'post'
AND post_status = 'publish'
AND YEAR(post_date) = %d
GROUP BY MONTH(post_date), DAY(post_date)
ORDER BY month_num ASC, day_num ASC",
$current_year
)
);
// Data Structures
$monthly_counts = array_fill( 1, 12, 0 );
$daily_counts = array_fill( 1, 12, array() );
$peak_day_count = 0;
$peak_day_string = __('None', 'monthly-posts-widget');
$active_months = 0;
if ( ! empty( $results ) ) {
foreach ( $results as $row ) {
$m = (int) $row->month_num;
$d = (int) $row->day_num;
$c = (int) $row->count;
$daily_counts[ $m ][ $d ] = $c;
$monthly_counts[ $m ] += $c;
// Track the absolute peak day and its date label
if ( $c > $peak_day_count ) {
$peak_day_count = $c;
$peak_day_string = date_i18n( 'M j', mktime( 0, 0, 0, $m, $d, $current_year ) );
}
}
}
foreach ( $monthly_counts as $count ) {
if ( $count > 0 ) $active_months++;
}
$max_month_count = max( $monthly_counts ) > 0 ? max( $monthly_counts ) : 1;
$total_posts = array_sum( $monthly_counts );
?>
<style>
:root {
--mpcw-primary: var(--wp-admin-theme-color, #2271b1);
--mpcw-primary-light: rgba(34, 113, 177, 0.08);
--mpcw-border: #dcdcde;
--mpcw-bg-alt: #f6f7f7;
}
/* Responsive Summary KPI Grid */
.mpcw-summary-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 8px;
margin-bottom: 14px;
}
.mpcw-card {
background: #fff;
border: 1px solid var(--mpcw-border);
border-radius: 6px;
padding: 8px 10px;
text-align: center;
}
.mpcw-card-val {
font-size: 18px;
font-weight: 700;
line-height: 1.2;
color: #1d2327;
}
.mpcw-card-lbl {
font-size: 11px;
color: #646970;
margin-top: 2px;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.mpcw-card-sub {
font-size: 10px;
color: var(--mpcw-primary);
font-weight: 600;
margin-top: 1px;
}
/* Pro Table Styling */
.mpcw-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
border: 1px solid var(--mpcw-border);
border-radius: 6px;
overflow: hidden;
}
.mpcw-table th {
background: var(--mpcw-bg-alt);
padding: 8px 10px;
font-size: 12px;
font-weight: 600;
color: #50575e;
border-bottom: 1px solid var(--mpcw-border);
}
.mpcw-month-row {
cursor: pointer;
transition: background 0.15s ease;
}
.mpcw-month-row:hover, .mpcw-month-row:focus-within {
background-color: var(--mpcw-bg-alt);
}
.mpcw-month-row.current-month {
background-color: var(--mpcw-primary-light);
}
.mpcw-month-row td {
padding: 8px 10px;
font-size: 13px;
border-bottom: 1px solid #f0f0f1;
vertical-align: middle;
}
/* Toggle Button & Chevron */
.mpcw-month-title {
display: flex;
align-items: center;
gap: 6px;
font-weight: 500;
}
.mpcw-chevron {
width: 14px;
height: 14px;
fill: #646970;
transition: transform 0.2s ease-in-out;
flex-shrink: 0;
}
.mpcw-month-row[aria-expanded="true"] .mpcw-chevron {
transform: rotate(90deg);
fill: var(--mpcw-primary);
}
/* Progress Bar */
.mpcw-bar-track {
width: 100%;
background: #f0f0f1;
height: 6px;
border-radius: 3px;
overflow: hidden;
}
.mpcw-bar-fill {
height: 100%;
background: var(--mpcw-primary);
border-radius: 3px;
transition: width 0.4s ease;
}
/* Collapsible Day Breakdown */
.mpcw-days-row {
display: none;
}
.mpcw-days-row.is-open {
display: table-row;
}
.mpcw-days-wrap {
padding: 10px 12px;
background: #fcfcfc;
border-bottom: 1px solid var(--mpcw-border);
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.mpcw-day-pill {
display: inline-flex;
align-items: center;
gap: 4px;
background: #fff;
border: 1px solid #dcdcde;
padding: 3px 8px;
border-radius: 12px;
font-size: 11px;
color: #50575e;
box-shadow: 0 1px 2px rgba(0,0,0,0.02);
}
.mpcw-day-pill strong {
color: var(--mpcw-primary);
}
.mpcw-badge-current {
font-size: 9px;
background: var(--mpcw-primary);
color: #fff;
padding: 1px 5px;
border-radius: 3px;
text-transform: uppercase;
}
/* Mobile Adjustments */
@media (max-width: 480px) {
.mpcw-table th:nth-child(2),
.mpcw-table td:nth-child(2) {
display: none;
}
}
</style>
<!-- Top Stats Cards -->
<div class="mpcw-summary-grid">
<div class="mpcw-card">
<div class="mpcw-card-val"><?php echo esc_html( number_format_i18n( $total_posts ) ); ?></div>
<div class="mpcw-card-lbl"><?php _e( 'Total Posts', 'monthly-posts-widget' ); ?></div>
</div>
<div class="mpcw-card">
<div class="mpcw-card-val"><?php echo esc_html( $active_months ); ?></div>
<div class="mpcw-card-lbl"><?php _e( 'Active Mos', 'monthly-posts-widget' ); ?></div>
</div>
<div class="mpcw-card">
<div class="mpcw-card-val"><?php echo esc_html( $peak_day_count ); ?></div>
<div class="mpcw-card-lbl"><?php _e( 'Peak Day', 'monthly-posts-widget' ); ?></div>
<div class="mpcw-card-sub"><?php echo esc_html( $peak_day_string ); ?></div>
</div>
</div>
<!-- Data Table -->
<table class="mpcw-table">
<thead>
<tr>
<th style="width: 45%;"><?php _e( 'Month', 'monthly-posts-widget' ); ?></th>
<th style="width: 35%;"><?php _e( 'Volume', 'monthly-posts-widget' ); ?></th>
<th style="width: 20%; text-align: right;"><?php _e( 'Posts', 'monthly-posts-widget' ); ?></th>
</tr>
</thead>
<tbody>
<?php for ( $m = 1; $m <= 12; $m++ ) :
$month_name = date_i18n( 'F', mktime( 0, 0, 0, $m, 10 ) );
$count = $monthly_counts[ $m ];
$percent = round( ( $count / $max_month_count ) * 100 );
$is_current = ( $m === $current_month );
$days_data = $daily_counts[ $m ];
$has_days = ! empty( $days_data );
?>
<!-- Parent Row -->
<tr class="mpcw-month-row <?php echo $is_current ? 'current-month' : ''; ?>"
tabindex="0"
role="button"
aria-expanded="false"
aria-controls="mpcw-days-<?php echo $m; ?>"
onclick="mpcwToggleRow(this)"
onkeydown="if(event.key === 'Enter' || event.key === ' ') { event.preventDefault(); mpcwToggleRow(this); }">
<td>
<div class="mpcw-month-title">
<svg class="mpcw-chevron" viewBox="0 0 20 20">
<path d="M7 15l5-5-5-5v10z"/>
</svg>
<span><?php echo esc_html( $month_name ); ?></span>
<?php if ( $is_current ) : ?>
<span class="mpcw-badge-current"><?php _e( 'Now', 'monthly-posts-widget' ); ?></span>
<?php endif; ?>
</div>
</td>
<td>
<div class="mpcw-bar-track">
<div class="mpcw-bar-fill" style="width: <?php echo $percent; ?>%;"></div>
</div>
</td>
<td style="text-align: right; font-weight: 600;">
<?php echo esc_html( number_format_i18n( $count ) ); ?>
</td>
</tr>
<!-- Collapsible Days Row -->
<tr id="mpcw-days-<?php echo $m; ?>" class="mpcw-days-row">
<td colspan="3" style="padding: 0;">
<div class="mpcw-days-wrap">
<?php if ( $has_days ) : ?>
<?php foreach ( $days_data as $day => $day_count ) :
$day_formatted = date_i18n( 'M j', mktime( 0, 0, 0, $m, $day, $current_year ) );
?>
<span class="mpcw-day-pill">
<?php echo esc_html( $day_formatted ); ?>: <strong><?php echo esc_html( $day_count ); ?></strong>
</span>
<?php endforeach; ?>
<?php else : ?>
<span style="font-style: italic; color: #8c8f94; font-size: 11px;">
<?php _e( 'No posts published.', 'monthly-posts-widget' ); ?>
</span>
<?php endif; ?>
</div>
</td>
</tr>
<?php endfor; ?>
</tbody>
</table>
<script>
function mpcwToggleRow(row) {
var daysRow = row.nextElementSibling;
var isExpanded = row.getAttribute('aria-expanded') === 'true';
if (daysRow && daysRow.classList.contains('mpcw-days-row')) {
if (isExpanded) {
row.setAttribute('aria-expanded', 'false');
daysRow.classList.remove('is-open');
} else {
row.setAttribute('aria-expanded', 'true');
daysRow.classList.add('is-open');
}
}
}
</script>
<?php
}