Blockchain-based To-Do List: Store tasks on-chain with immutable completion timestamps

Introduction

Project Name: ChainTasks: Immutable Task Management System

Concept: Traditional to-do lists and task management systems store data in mutable databases where completion timestamps and task history can be easily altered or deleted. This project creates a blockchain-based task management system where every task creation, update, and completion is permanently recorded on an immutable chain, providing verifiable proof of when tasks were actually completed.

The Blockchain Solution: Instead of simply storing tasks in a database table where records can be modified, we implement a blockchain structure specifically for tasks. Each task becomes a block in the chain, containing the task description, status, timestamps, and a cryptographic link to the previous task. This creates an immutable audit trail of all task activities that cannot be altered retroactively.

Features

  • ⛓️ Immutable Task History: Every task action is permanently recorded
  • 🕒 Verifiable Timestamps: Proof of when tasks were created and completed
  • 📝 Task Chain: All tasks are cryptographically linked
  • ✅ Completion Proof: Cannot fake completion dates
  • 📊 Task Analytics: Track productivity with verifiable data
  • 🔍 Audit Trail: Complete history of all task modifications
  • 🏷️ Categories & Priorities: Organize tasks with metadata
  • 📈 Productivity Reports: Generate verifiable productivity metrics

Project File Structure

blockchain-todo-list/
│
├── index.php                 # Main application UI
├── style.css                 # Custom styling
├── script.js                 # Frontend JavaScript
├── api/
│   ├── add_task.php         # Create new task
│   ├── update_task.php      # Update task (complete, edit)
│   ├── get_tasks.php        # Retrieve all tasks
│   ├── get_task_history.php # Get task history
│   ├── verify_chain.php     # Verify blockchain integrity
│   └── get_stats.php        # Get productivity statistics
├── includes/
│   ├── config.php           # Database configuration
│   └── TaskChain.php        # Core blockchain logic for tasks
├── database/
│   └── schema.sql           # Database schema
└── README.md                # Project documentation

Database Setup (MySQL)

1. Create a database named taskchain_db.

2. Run the database/schema.sql script:

CREATE DATABASE IF NOT EXISTS taskchain_db;
USE taskchain_db;
-- Main task blockchain table
CREATE TABLE task_chain (
id INT AUTO_INCREMENT PRIMARY KEY,
block_index INT NOT NULL UNIQUE,
task_id VARCHAR(50) NOT NULL UNIQUE,
title VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(50) DEFAULT 'General',
priority ENUM('Low', 'Medium', 'High', 'Critical') DEFAULT 'Medium',
status ENUM('Pending', 'In Progress', 'Completed', 'Archived') DEFAULT 'Pending',
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL,
completed_at BIGINT NULL,
previous_hash VARCHAR(64) NOT NULL,
block_hash VARCHAR(64) NOT NULL UNIQUE,
nonce INT NOT NULL,
created_by VARCHAR(100) DEFAULT 'User',
metadata JSON,
INDEX idx_status (status),
INDEX idx_category (category),
INDEX idx_priority (priority),
INDEX idx_created (created_at)
);
-- Task history/audit trail
CREATE TABLE task_history (
id INT AUTO_INCREMENT PRIMARY KEY,
task_id VARCHAR(50) NOT NULL,
action ENUM('CREATE', 'UPDATE', 'COMPLETE', 'REOPEN', 'DELETE') NOT NULL,
old_status VARCHAR(20),
new_status VARCHAR(20),
changes TEXT,
timestamp BIGINT NOT NULL,
block_hash VARCHAR(64),
FOREIGN KEY (task_id) REFERENCES task_chain(task_id),
INDEX idx_task (task_id),
INDEX idx_timestamp (timestamp)
);
-- User productivity stats
CREATE TABLE productivity_stats (
id INT AUTO_INCREMENT PRIMARY KEY,
date DATE NOT NULL,
tasks_created INT DEFAULT 0,
tasks_completed INT DEFAULT 0,
completion_rate DECIMAL(5,2) DEFAULT 0,
avg_completion_time INT DEFAULT 0, -- in minutes
UNIQUE KEY unique_date (date)
);
-- Insert genesis block
INSERT INTO task_chain (
block_index, task_id, title, description, category, priority, 
status, created_at, updated_at, completed_at, previous_hash, block_hash, nonce
) VALUES (
0, 'GENESIS_TASK', 'Task Chain Genesis Block', 
'Initial block of the task blockchain', 'System', 'Low',
'Completed', UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), UNIX_TIMESTAMP(),
'0', 
SHA2(CONCAT('0', 'GENESIS_TASK', 'Task Chain Genesis Block', UNIX_TIMESTAMP(), '0', '0'), 256),
0
);

Code Implementation

1. includes/config.php (Database Configuration)

<?php
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_NAME', 'taskchain_db');
function getDBConnection() {
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if ($conn->connect_error) {
die(json_encode(['success' => false, 'message' => 'Database connection failed: ' . $conn->connect_error]));
}
return $conn;
}
// Task categories
$categories = ['Work', 'Personal', 'Study', 'Health', 'Finance', 'Shopping', 'General'];
// Priority levels
$priorities = ['Low', 'Medium', 'High', 'Critical'];
date_default_timezone_set('UTC');
?>

2. includes/TaskChain.php (Core Blockchain Logic)

<?php
require_once 'config.php';
class TaskChain {
private $conn;
private $difficulty = 2; // Proof of work difficulty
public function __construct() {
$this->conn = getDBConnection();
}
/**
* Calculate hash for a task block
*/
public static function calculateHash($index, $taskId, $title, $description, $category, $priority, $status, $createdAt, $updatedAt, $completedAt, $previousHash, $nonce) {
$data = $index . $taskId . $title . $description . $category . $priority . $status . $createdAt . $updatedAt . $completedAt . $previousHash . $nonce;
return hash('sha256', $data);
}
/**
* Get the last block from the chain
*/
public function getLastBlock() {
$sql = "SELECT * FROM task_chain ORDER BY block_index DESC LIMIT 1";
$result = $this->conn->query($sql);
if ($result && $result->num_rows > 0) {
return $result->fetch_assoc();
}
return null;
}
/**
* Generate unique task ID
*/
private function generateTaskId() {
return 'TASK_' . time() . '_' . rand(1000, 9999);
}
/**
* Proof of Work
*/
public function proofOfWork($index, $taskId, $title, $description, $category, $priority, $status, $createdAt, $updatedAt, $completedAt, $previousHash) {
$nonce = 0;
$prefix = str_repeat('0', $this->difficulty);
while (true) {
$hash = $this->calculateHash($index, $taskId, $title, $description, $category, $priority, $status, $createdAt, $updatedAt, $completedAt, $previousHash, $nonce);
if (substr($hash, 0, $this->difficulty) === $prefix) {
return ['nonce' => $nonce, 'hash' => $hash];
}
$nonce++;
if ($nonce > 1000000) {
throw new Exception("Proof of work timeout");
}
}
}
/**
* Add a new task
*/
public function addTask($title, $description = '', $category = 'General', $priority = 'Medium', $createdBy = 'User') {
try {
if (empty($title)) {
return ['success' => false, 'message' => 'Task title is required'];
}
// Get last block
$lastBlock = $this->getLastBlock();
if (!$lastBlock) {
return ['success' => false, 'message' => 'Task chain corrupted'];
}
$newIndex = $lastBlock['block_index'] + 1;
$taskId = $this->generateTaskId();
$timestamp = time();
$previousHash = $lastBlock['block_hash'];
// Proof of Work
$powResult = $this->proofOfWork(
$newIndex, $taskId, $title, $description, $category, $priority, 
'Pending', $timestamp, $timestamp, null, $previousHash
);
// Start transaction
$this->conn->begin_transaction();
// Insert task
$sql = "INSERT INTO task_chain (
block_index, task_id, title, description, category, priority, 
status, created_at, updated_at, completed_at, previous_hash, block_hash, nonce, created_by
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param(
"isssssssiissis",
$newIndex, $taskId, $title, $description, $category, $priority,
$status = 'Pending', $timestamp, $timestamp, $completedAt = null,
$previousHash, $powResult['hash'], $powResult['nonce'], $createdBy
);
if (!$stmt->execute()) {
throw new Exception("Failed to insert task: " . $stmt->error);
}
// Add to history
$sql2 = "INSERT INTO task_history (task_id, action, new_status, timestamp, block_hash) 
VALUES (?, 'CREATE', ?, ?, ?)";
$stmt2 = $this->conn->prepare($sql2);
$stmt2->bind_param("ssis", $taskId, $status, $timestamp, $powResult['hash']);
if (!$stmt2->execute()) {
throw new Exception("Failed to record history");
}
// Update productivity stats
$this->updateProductivityStats($timestamp, 'created');
$this->conn->commit();
return [
'success' => true,
'message' => 'Task added successfully',
'task' => [
'task_id' => $taskId,
'title' => $title,
'category' => $category,
'priority' => $priority,
'block_index' => $newIndex,
'block_hash' => $powResult['hash'],
'created_at' => date('Y-m-d H:i:s', $timestamp)
]
];
} catch (Exception $e) {
$this->conn->rollback();
return ['success' => false, 'message' => 'Error adding task: ' . $e->getMessage()];
}
}
/**
* Update task status (complete, reopen, etc.)
*/
public function updateTaskStatus($taskId, $newStatus) {
try {
// Get current task
$sql = "SELECT * FROM task_chain WHERE task_id = ? ORDER BY block_index DESC LIMIT 1";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("s", $taskId);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
return ['success' => false, 'message' => 'Task not found'];
}
$task = $result->fetch_assoc();
$oldStatus = $task['status'];
// Validate status transition
if (!$this->isValidTransition($oldStatus, $newStatus)) {
return ['success' => false, 'message' => "Invalid status transition from $oldStatus to $newStatus"];
}
// Get last block
$lastBlock = $this->getLastBlock();
$newIndex = $lastBlock['block_index'] + 1;
$timestamp = time();
$previousHash = $lastBlock['block_hash'];
// Set completed_at if marking as complete
$completedAt = ($newStatus === 'Completed') ? $timestamp : $task['completed_at'];
// Proof of Work for update
$powResult = $this->proofOfWork(
$newIndex, $taskId, $task['title'], $task['description'], 
$task['category'], $task['priority'], $newStatus,
$task['created_at'], $timestamp, $completedAt, $previousHash
);
$this->conn->begin_transaction();
// Insert updated task as new block
$sql = "INSERT INTO task_chain (
block_index, task_id, title, description, category, priority, 
status, created_at, updated_at, completed_at, previous_hash, block_hash, nonce, created_by
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param(
"isssssssiissis",
$newIndex, $taskId, $task['title'], $task['description'], 
$task['category'], $task['priority'], $newStatus,
$task['created_at'], $timestamp, $completedAt,
$previousHash, $powResult['hash'], $powResult['nonce'], $task['created_by']
);
if (!$stmt->execute()) {
throw new Exception("Failed to update task");
}
// Add to history
$action = ($newStatus === 'Completed') ? 'COMPLETE' : 
(($oldStatus === 'Completed' && $newStatus !== 'Completed') ? 'REOPEN' : 'UPDATE');
$changes = json_encode(['old_status' => $oldStatus, 'new_status' => $newStatus]);
$sql2 = "INSERT INTO task_history (task_id, action, old_status, new_status, changes, timestamp, block_hash) 
VALUES (?, ?, ?, ?, ?, ?, ?)";
$stmt2 = $this->conn->prepare($sql2);
$stmt2->bind_param("sssssis", $taskId, $action, $oldStatus, $newStatus, $changes, $timestamp, $powResult['hash']);
if (!$stmt2->execute()) {
throw new Exception("Failed to record history");
}
// Update productivity stats for completions
if ($newStatus === 'Completed') {
$this->updateProductivityStats($timestamp, 'completed');
// Calculate completion time
$completionTime = $timestamp - $task['created_at'];
$this->updateAvgCompletionTime($completionTime);
}
$this->conn->commit();
return [
'success' => true,
'message' => "Task $newStatus successfully",
'task' => [
'task_id' => $taskId,
'status' => $newStatus,
'updated_at' => date('Y-m-d H:i:s', $timestamp),
'block_hash' => $powResult['hash']
]
];
} catch (Exception $e) {
$this->conn->rollback();
return ['success' => false, 'message' => 'Error updating task: ' . $e->getMessage()];
}
}
/**
* Validate status transitions
*/
private function isValidTransition($old, $new) {
$validTransitions = [
'Pending' => ['In Progress', 'Completed', 'Archived'],
'In Progress' => ['Pending', 'Completed', 'Archived'],
'Completed' => ['In Progress', 'Archived'], // Reopen allowed
'Archived' => ['Pending'] // Unarchive
];
return isset($validTransitions[$old]) && in_array($new, $validTransitions[$old]);
}
/**
* Get all tasks (latest version of each)
*/
public function getAllTasks() {
// Get the latest version of each task (max block_index per task_id)
$sql = "SELECT t1.* FROM task_chain t1
INNER JOIN (
SELECT task_id, MAX(block_index) as max_block
FROM task_chain
WHERE task_id != 'GENESIS_TASK'
GROUP BY task_id
) t2 ON t1.task_id = t2.task_id AND t1.block_index = t2.max_block
ORDER BY 
CASE t1.priority 
WHEN 'Critical' THEN 1
WHEN 'High' THEN 2
WHEN 'Medium' THEN 3
WHEN 'Low' THEN 4
END,
t1.created_at DESC";
$result = $this->conn->query($sql);
$tasks = [];
while ($row = $result->fetch_assoc()) {
$row['formatted_created'] = date('Y-m-d H:i:s', $row['created_at']);
$row['formatted_updated'] = date('Y-m-d H:i:s', $row['updated_at']);
if ($row['completed_at']) {
$row['formatted_completed'] = date('Y-m-d H:i:s', $row['completed_at']);
}
$tasks[] = $row;
}
return $tasks;
}
/**
* Get task history
*/
public function getTaskHistory($taskId) {
$sql = "SELECT * FROM task_history WHERE task_id = ? ORDER BY timestamp DESC";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("s", $taskId);
$stmt->execute();
$result = $stmt->get_result();
$history = [];
while ($row = $result->fetch_assoc()) {
$row['formatted_time'] = date('Y-m-d H:i:s', $row['timestamp']);
$history[] = $row;
}
return $history;
}
/**
* Update productivity statistics
*/
private function updateProductivityStats($timestamp, $type) {
$date = date('Y-m-d', $timestamp);
// Check if stats exist for this date
$sql = "SELECT * FROM productivity_stats WHERE date = ?";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("s", $date);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows > 0) {
// Update existing
if ($type === 'created') {
$sql = "UPDATE productivity_stats SET tasks_created = tasks_created + 1 WHERE date = ?";
} else {
$sql = "UPDATE productivity_stats SET tasks_completed = tasks_completed + 1 WHERE date = ?";
}
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("s", $date);
$stmt->execute();
} else {
// Insert new
$created = ($type === 'created') ? 1 : 0;
$completed = ($type === 'completed') ? 1 : 0;
$sql = "INSERT INTO productivity_stats (date, tasks_created, tasks_completed) VALUES (?, ?, ?)";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("sii", $date, $created, $completed);
$stmt->execute();
}
// Update completion rate
$this->calculateCompletionRate($date);
}
/**
* Calculate completion rate for a date
*/
private function calculateCompletionRate($date) {
$sql = "SELECT tasks_created, tasks_completed FROM productivity_stats WHERE date = ?";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("s", $date);
$stmt->execute();
$result = $stmt->get_result();
$stats = $result->fetch_assoc();
if ($stats['tasks_created'] > 0) {
$rate = ($stats['tasks_completed'] / $stats['tasks_created']) * 100;
$sql = "UPDATE productivity_stats SET completion_rate = ? WHERE date = ?";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("ds", $rate, $date);
$stmt->execute();
}
}
/**
* Update average completion time
*/
private function updateAvgCompletionTime($completionTime) {
// Convert to minutes
$minutes = round($completionTime / 60);
$sql = "UPDATE productivity_stats 
SET avg_completion_time = (
SELECT AVG(completion_time) FROM (
SELECT (t.completed_at - t.created_at) / 60 as completion_time
FROM task_chain t
WHERE t.completed_at IS NOT NULL
ORDER BY t.completed_at DESC
LIMIT 100
) as recent
)";
$this->conn->query($sql);
}
/**
* Get productivity statistics
*/
public function getStats($days = 7) {
$stats = [
'total_tasks' => 0,
'completed_tasks' => 0,
'pending_tasks' => 0,
'in_progress_tasks' => 0,
'completion_rate' => 0,
'avg_completion_time' => 0,
'daily_stats' => []
];
// Get task counts
$sql = "SELECT status, COUNT(*) as count FROM (
SELECT t1.* FROM task_chain t1
INNER JOIN (
SELECT task_id, MAX(block_index) as max_block
FROM task_chain
WHERE task_id != 'GENESIS_TASK'
GROUP BY task_id
) t2 ON t1.task_id = t2.task_id AND t1.block_index = t2.max_block
) as latest
GROUP BY status";
$result = $this->conn->query($sql);
while ($row = $result->fetch_assoc()) {
switch ($row['status']) {
case 'Completed':
$stats['completed_tasks'] = $row['count'];
break;
case 'Pending':
$stats['pending_tasks'] = $row['count'];
break;
case 'In Progress':
$stats['in_progress_tasks'] = $row['count'];
break;
}
$stats['total_tasks'] += $row['count'];
}
if ($stats['total_tasks'] > 0) {
$stats['completion_rate'] = round(($stats['completed_tasks'] / $stats['total_tasks']) * 100, 2);
}
// Get average completion time
$sql = "SELECT AVG((completed_at - created_at) / 60) as avg_time 
FROM task_chain 
WHERE completed_at IS NOT NULL 
AND task_id != 'GENESIS_TASK'
LIMIT 100";
$result = $this->conn->query($sql);
if ($row = $result->fetch_assoc()) {
$stats['avg_completion_time'] = round($row['avg_time'] ?? 0);
}
// Get daily stats
$sql = "SELECT * FROM productivity_stats 
WHERE date >= DATE_SUB(CURDATE(), INTERVAL ? DAY)
ORDER BY date DESC";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("i", $days);
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
$stats['daily_stats'][] = $row;
}
return $stats;
}
/**
* Verify blockchain integrity
*/
public function verifyChainIntegrity() {
$sql = "SELECT * FROM task_chain ORDER BY block_index ASC";
$result = $this->conn->query($sql);
$blocks = [];
while ($row = $result->fetch_assoc()) {
$blocks[] = $row;
}
$verification = [
'valid' => true,
'total_blocks' => count($blocks),
'invalid_blocks' => [],
'message' => 'Blockchain is valid'
];
for ($i = 1; $i < count($blocks); $i++) {
$current = $blocks[$i];
$previous = $blocks[$i - 1];
// Check if previous hash matches
if ($current['previous_hash'] !== $previous['block_hash']) {
$verification['valid'] = false;
$verification['invalid_blocks'][] = $current['block_index'];
$verification['message'] = 'Chain broken at block ' . $current['block_index'];
continue;
}
// Verify current block's hash
$expectedHash = $this->calculateHash(
$current['block_index'],
$current['task_id'],
$current['title'],
$current['description'],
$current['category'],
$current['priority'],
$current['status'],
$current['created_at'],
$current['updated_at'],
$current['completed_at'],
$current['previous_hash'],
$current['nonce']
);
if ($expectedHash !== $current['block_hash']) {
$verification['valid'] = false;
$verification['invalid_blocks'][] = $current['block_index'];
$verification['message'] = 'Hash mismatch at block ' . $current['block_index'];
}
}
return $verification;
}
public function __destruct() {
$this->conn->close();
}
}
?>

3. api/add_task.php (Add Task Endpoint)

<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST');
header('Access-Control-Allow-Headers: Content-Type');
require_once '../includes/TaskChain.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'message' => 'Method not allowed']);
exit;
}
$input = json_decode(file_get_contents('php://input'), true);
if (!$input) {
$title = $_POST['title'] ?? '';
$description = $_POST['description'] ?? '';
$category = $_POST['category'] ?? 'General';
$priority = $_POST['priority'] ?? 'Medium';
$createdBy = $_POST['created_by'] ?? 'User';
} else {
$title = $input['title'] ?? '';
$description = $input['description'] ?? '';
$category = $input['category'] ?? 'General';
$priority = $input['priority'] ?? 'Medium';
$createdBy = $input['created_by'] ?? 'User';
}
$taskChain = new TaskChain();
$result = $taskChain->addTask($title, $description, $category, $priority, $createdBy);
echo json_encode($result);
?>

4. api/update_task.php (Update Task Status)

<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST');
header('Access-Control-Allow-Headers: Content-Type');
require_once '../includes/TaskChain.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'message' => 'Method not allowed']);
exit;
}
$input = json_decode(file_get_contents('php://input'), true);
if (!$input) {
$taskId = $_POST['task_id'] ?? '';
$status = $_POST['status'] ?? '';
} else {
$taskId = $input['task_id'] ?? '';
$status = $input['status'] ?? '';
}
if (empty($taskId) || empty($status)) {
echo json_encode(['success' => false, 'message' => 'Task ID and status required']);
exit;
}
$validStatuses = ['Pending', 'In Progress', 'Completed', 'Archived'];
if (!in_array($status, $validStatuses)) {
echo json_encode(['success' => false, 'message' => 'Invalid status']);
exit;
}
$taskChain = new TaskChain();
$result = $taskChain->updateTaskStatus($taskId, $status);
echo json_encode($result);
?>

5. api/get_tasks.php (Get All Tasks)

<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
require_once '../includes/TaskChain.php';
$taskChain = new TaskChain();
$tasks = $taskChain->getAllTasks();
echo json_encode([
'success' => true,
'total' => count($tasks),
'tasks' => $tasks
]);
?>

6. api/get_task_history.php (Get Task History)

<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
require_once '../includes/TaskChain.php';
$taskId = $_GET['task_id'] ?? '';
if (empty($taskId)) {
echo json_encode(['success' => false, 'message' => 'Task ID required']);
exit;
}
$taskChain = new TaskChain();
$history = $taskChain->getTaskHistory($taskId);
echo json_encode([
'success' => true,
'task_id' => $taskId,
'history' => $history
]);
?>

7. api/verify_chain.php (Verify Blockchain Integrity)

<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
require_once '../includes/TaskChain.php';
$taskChain = new TaskChain();
$verification = $taskChain->verifyChainIntegrity();
echo json_encode($verification);
?>

8. api/get_stats.php (Get Productivity Statistics)

<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
require_once '../includes/TaskChain.php';
$days = $_GET['days'] ?? 7;
$taskChain = new TaskChain();
$stats = $taskChain->getStats($days);
echo json_encode([
'success' => true,
'stats' => $stats
]);
?>

9. index.php (Main Application UI)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ChainTasks | Blockchain To-Do List</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Inter', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.task-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
transition: all 0.3s ease;
border-left: 4px solid transparent;
}
.task-card:hover {
transform: translateX(5px);
box-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.3);
}
.priority-Critical { border-left-color: #ef4444; }
.priority-High { border-left-color: #f97316; }
.priority-Medium { border-left-color: #eab308; }
.priority-Low { border-left-color: #22c55e; }
.status-badge {
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.blockchain-hash {
font-family: 'Monaco', 'Menlo', monospace;
font-size: 0.7rem;
color: #6b7280;
word-break: break-all;
}
.stat-card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-5px);
border-color: rgba(255, 255, 255, 0.4);
}
@keyframes blockchainPulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.blockchain-verified {
animation: blockchainPulse 2s infinite;
}
.task-enter {
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
</head>
<body class="text-gray-800">
<div class="max-w-7xl mx-auto px-4 py-8">
<!-- Header -->
<div class="text-center mb-8">
<div class="flex items-center justify-center mb-4">
<i class="fas fa-link text-5xl text-white mr-3"></i>
<h1 class="text-5xl font-bold text-white">
ChainTasks
</h1>
</div>
<p class="text-xl text-white/90 mb-2">Immutable Task Management System</p>
<p class="text-white/70 max-w-2xl mx-auto">
Every task and completion timestamp is permanently recorded on the blockchain. 
Never lose your productivity history again.
</p>
</div>
<!-- Blockchain Status Bar -->
<div class="bg-white/10 backdrop-blur-lg rounded-xl p-4 mb-8 border border-white/20">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-4">
<div class="flex items-center">
<i class="fas fa-circle text-green-400 text-xs mr-2"></i>
<span class="text-white" id="chainStatus">Chain Valid</span>
</div>
<div class="flex items-center">
<i class="fas fa-cube text-white/70 mr-2"></i>
<span class="text-white" id="blockCount">0 blocks</span>
</div>
</div>
<button onclick="verifyChain()" class="text-white/70 hover:text-white transition">
<i class="fas fa-shield-alt mr-1"></i>
Verify Chain
</button>
</div>
</div>
<!-- Stats Dashboard -->
<div class="grid grid-cols-1 md:grid-cols-5 gap-4 mb-8">
<div class="stat-card rounded-xl p-4">
<p class="text-white/70 text-sm mb-1">Total Tasks</p>
<p id="totalTasks" class="text-2xl font-bold text-white">0</p>
</div>
<div class="stat-card rounded-xl p-4">
<p class="text-white/70 text-sm mb-1">Completed</p>
<p id="completedTasks" class="text-2xl font-bold text-green-400">0</p>
</div>
<div class="stat-card rounded-xl p-4">
<p class="text-white/70 text-sm mb-1">In Progress</p>
<p id="inProgressTasks" class="text-2xl font-bold text-yellow-400">0</p>
</div>
<div class="stat-card rounded-xl p-4">
<p class="text-white/70 text-sm mb-1">Pending</p>
<p id="pendingTasks" class="text-2xl font-bold text-blue-400">0</p>
</div>
<div class="stat-card rounded-xl p-4">
<p class="text-white/70 text-sm mb-1">Completion Rate</p>
<p id="completionRate" class="text-2xl font-bold text-purple-400">0%</p>
</div>
</div>
<!-- Main Content -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Left Column: Add Task Form -->
<div class="lg:col-span-1">
<div class="bg-white rounded-xl shadow-xl p-6 sticky top-6">
<h2 class="text-xl font-bold mb-6 flex items-center">
<i class="fas fa-plus-circle text-indigo-600 mr-2"></i>
Add New Task
</h2>
<form id="taskForm" class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Task Title</label>
<input type="text" id="taskTitle" required
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500"
placeholder="Enter task title">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Description</label>
<textarea id="taskDescription" rows="3"
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500"
placeholder="Task description (optional)"></textarea>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Category</label>
<select id="taskCategory" class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500">
<option value="Work">Work</option>
<option value="Personal">Personal</option>
<option value="Study">Study</option>
<option value="Health">Health</option>
<option value="Finance">Finance</option>
<option value="Shopping">Shopping</option>
<option value="General">General</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Priority</label>
<select id="taskPriority" class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500">
<option value="Low">Low</option>
<option value="Medium" selected>Medium</option>
<option value="High">High</option>
<option value="Critical">Critical</option>
</select>
</div>
</div>
<button type="submit" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 px-4 rounded-lg transition duration-300">
<i class="fas fa-link mr-2"></i>
Add to Blockchain
</button>
</form>
<!-- Recent Activity -->
<div class="mt-6 pt-6 border-t border-gray-200">
<h3 class="text-sm font-medium text-gray-700 mb-3">Recent Activity</h3>
<div id="recentActivity" class="space-y-2 max-h-60 overflow-y-auto">
<!-- Activity will be loaded here -->
</div>
</div>
</div>
</div>
<!-- Right Column: Task List -->
<div class="lg:col-span-2">
<!-- Filter Bar -->
<div class="bg-white rounded-t-xl shadow-lg p-4 border-b border-gray-200">
<div class="flex flex-wrap gap-4">
<select id="filterStatus" onchange="filterTasks()" class="border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500">
<option value="all">All Status</option>
<option value="Pending">Pending</option>
<option value="In Progress">In Progress</option>
<option value="Completed">Completed</option>
<option value="Archived">Archived</option>
</select>
<select id="filterPriority" onchange="filterTasks()" class="border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500">
<option value="all">All Priorities</option>
<option value="Critical">Critical</option>
<option value="High">High</option>
<option value="Medium">Medium</option>
<option value="Low">Low</option>
</select>
<select id="filterCategory" onchange="filterTasks()" class="border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500">
<option value="all">All Categories</option>
<option value="Work">Work</option>
<option value="Personal">Personal</option>
<option value="Study">Study</option>
<option value="Health">Health</option>
<option value="Finance">Finance</option>
<option value="Shopping">Shopping</option>
<option value="General">General</option>
</select>
<button onclick="refreshTasks()" class="ml-auto text-indigo-600 hover:text-indigo-800">
<i class="fas fa-sync-alt mr-1"></i>
Refresh
</button>
</div>
</div>
<!-- Task List -->
<div id="taskContainer" class="bg-white rounded-b-xl shadow-xl p-4 space-y-3 max-h-[600px] overflow-y-auto">
<!-- Tasks will be loaded here -->
<div class="text-center text-gray-500 py-8">
<i class="fas fa-spinner fa-spin text-3xl mb-2"></i>
<p>Loading tasks...</p>
</div>
</div>
</div>
</div>
<!-- Daily Productivity Chart -->
<div class="mt-8 bg-white rounded-xl shadow-xl p-6">
<h2 class="text-xl font-bold mb-4 flex items-center">
<i class="fas fa-chart-line text-indigo-600 mr-2"></i>
Daily Productivity (Last 7 Days)
</h2>
<div id="productivityChart" class="grid grid-cols-7 gap-2 h-40 items-end">
<!-- Chart bars will be generated here -->
</div>
</div>
</div>
<!-- Task History Modal -->
<div id="historyModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
<div class="bg-white rounded-xl p-6 max-w-2xl w-full mx-4 max-h-[80vh] overflow-y-auto">
<h3 class="text-xl font-bold mb-4 flex items-center">
<i class="fas fa-history text-indigo-600 mr-2"></i>
Task History
</h3>
<div id="historyContent" class="space-y-3">
<!-- History will be loaded here -->
</div>
<button onclick="closeHistoryModal()" class="mt-4 w-full bg-gray-200 hover:bg-gray-300 text-gray-800 py-2 rounded-lg transition">
Close
</button>
</div>
</div>
<script src="script.js"></script>
</body>
</html>

10. script.js (Frontend JavaScript)

// script.js
let allTasks = [];
let currentFilters = {
status: 'all',
priority: 'all',
category: 'all'
};
// Load data on page load
document.addEventListener('DOMContentLoaded', function() {
loadTasks();
loadStats();
verifyChain();
// Set up form submission
document.getElementById('taskForm').addEventListener('submit', function(e) {
e.preventDefault();
addTask();
});
// Auto-refresh every 30 seconds
setInterval(refreshTasks, 30000);
});
// Add new task
async function addTask() {
const title = document.getElementById('taskTitle').value;
const description = document.getElementById('taskDescription').value;
const category = document.getElementById('taskCategory').value;
const priority = document.getElementById('taskPriority').value;
if (!title) {
showNotification('Please enter a task title', 'error');
return;
}
const formData = {
title: title,
description: description,
category: category,
priority: priority,
created_by: 'User'
};
try {
const response = await fetch('api/add_task.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
const data = await response.json();
if (data.success) {
showNotification(data.message, 'success');
document.getElementById('taskForm').reset();
loadTasks();
loadStats();
addRecentActivity('Task created: ' + title);
} else {
showNotification('Error: ' + data.message, 'error');
}
} catch (error) {
console.error('Error:', error);
showNotification('Failed to add task', 'error');
}
}
// Load all tasks
async function loadTasks() {
try {
const response = await fetch('api/get_tasks.php');
const data = await response.json();
if (data.success) {
allTasks = data.tasks;
displayTasks(allTasks);
}
} catch (error) {
console.error('Error loading tasks:', error);
}
}
// Display tasks with filters
function displayTasks(tasks) {
const container = document.getElementById('taskContainer');
if (!tasks || tasks.length === 0) {
container.innerHTML = '<div class="text-center text-gray-500 py-8">No tasks found</div>';
return;
}
// Apply filters
const filtered = tasks.filter(task => {
if (currentFilters.status !== 'all' && task.status !== currentFilters.status) return false;
if (currentFilters.priority !== 'all' && task.priority !== currentFilters.priority) return false;
if (currentFilters.category !== 'all' && task.category !== currentFilters.category) return false;
return true;
});
if (filtered.length === 0) {
container.innerHTML = '<div class="text-center text-gray-500 py-8">No tasks match filters</div>';
return;
}
let html = '';
filtered.forEach(task => {
const priorityClass = `priority-${task.priority}`;
const statusColors = {
'Pending': 'bg-yellow-100 text-yellow-800',
'In Progress': 'bg-blue-100 text-blue-800',
'Completed': 'bg-green-100 text-green-800',
'Archived': 'bg-gray-100 text-gray-800'
};
html += `
<div class="task-card rounded-lg p-4 ${priorityClass} task-enter" data-task-id="${task.task_id}">
<div class="flex items-start justify-between mb-2">
<div class="flex-1">
<div class="flex items-center space-x-2 mb-1">
<h3 class="font-semibold text-lg">${escapeHtml(task.title)}</h3>
<span class="status-badge ${statusColors[task.status]}">${task.status}</span>
</div>
${task.description ? `<p class="text-sm text-gray-600 mb-2">${escapeHtml(task.description)}</p>` : ''}
<div class="flex flex-wrap gap-2 text-xs text-gray-500 mb-2">
<span class="bg-gray-100 px-2 py-1 rounded">
<i class="fas fa-tag mr-1"></i>${task.category}
</span>
<span class="bg-gray-100 px-2 py-1 rounded">
<i class="fas fa-flag mr-1"></i>${task.priority}
</span>
<span class="bg-gray-100 px-2 py-1 rounded">
<i class="fas fa-clock mr-1"></i>${task.formatted_created}
</span>
</div>
${task.completed_at ? `
<div class="text-xs text-green-600 mb-2">
<i class="fas fa-check-circle mr-1"></i>
Completed: ${task.formatted_completed}
</div>
` : ''}
<div class="blockchain-hash bg-gray-50 p-2 rounded text-xs">
<span class="text-gray-500">Block #${task.block_index} | </span>
<span class="text-indigo-600">${task.block_hash.substring(0, 20)}...</span>
</div>
</div>
</div>
<div class="flex space-x-2 mt-3 pt-3 border-t border-gray-100">
${task.status !== 'Completed' ? `
<button onclick="updateTaskStatus('${task.task_id}', 'Completed')" 
class="flex-1 bg-green-500 hover:bg-green-600 text-white text-sm py-1 px-2 rounded transition">
<i class="fas fa-check mr-1"></i> Complete
</button>
` : ''}
${task.status === 'Pending' ? `
<button onclick="updateTaskStatus('${task.task_id}', 'In Progress')" 
class="flex-1 bg-blue-500 hover:bg-blue-600 text-white text-sm py-1 px-2 rounded transition">
<i class="fas fa-play mr-1"></i> Start
</button>
` : ''}
${task.status === 'Completed' ? `
<button onclick="updateTaskStatus('${task.task_id}', 'In Progress')" 
class="flex-1 bg-yellow-500 hover:bg-yellow-600 text-white text-sm py-1 px-2 rounded transition">
<i class="fas fa-undo mr-1"></i> Reopen
</button>
` : ''}
<button onclick="viewHistory('${task.task_id}')" 
class="bg-indigo-500 hover:bg-indigo-600 text-white text-sm py-1 px-3 rounded transition">
<i class="fas fa-history"></i>
</button>
${task.status === 'Completed' ? `
<button onclick="updateTaskStatus('${task.task_id}', 'Archived')" 
class="bg-gray-500 hover:bg-gray-600 text-white text-sm py-1 px-3 rounded transition">
<i class="fas fa-archive"></i>
</button>
` : ''}
</div>
</div>
`;
});
container.innerHTML = html;
}
// Update task status
async function updateTaskStatus(taskId, newStatus) {
if (!confirm(`Mark task as ${newStatus}?`)) return;
try {
const response = await fetch('api/update_task.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
task_id: taskId,
status: newStatus
})
});
const data = await response.json();
if (data.success) {
showNotification(data.message, 'success');
loadTasks();
loadStats();
addRecentActivity(`Task ${newStatus.toLowerCase()}: ${taskId}`);
} else {
showNotification('Error: ' + data.message, 'error');
}
} catch (error) {
console.error('Error:', error);
showNotification('Failed to update task', 'error');
}
}
// View task history
async function viewHistory(taskId) {
try {
const response = await fetch(`api/get_task_history.php?task_id=${taskId}`);
const data = await response.json();
if (data.success) {
displayHistory(data.history);
}
} catch (error) {
console.error('Error:', error);
}
}
// Display task history modal
function displayHistory(history) {
const modal = document.getElementById('historyModal');
const content = document.getElementById('historyContent');
if (history.length === 0) {
content.innerHTML = '<p class="text-gray-500 text-center py-4">No history available</p>';
} else {
let html = '';
history.forEach(entry => {
const actionColors = {
'CREATE': 'bg-green-100 text-green-800',
'UPDATE': 'bg-blue-100 text-blue-800',
'COMPLETE': 'bg-green-100 text-green-800',
'REOPEN': 'bg-yellow-100 text-yellow-800',
'DELETE': 'bg-red-100 text-red-800'
};
html += `
<div class="border-l-4 border-indigo-500 bg-gray-50 p-3 rounded">
<div class="flex justify-between items-start">
<span class="status-badge ${actionColors[entry.action] || 'bg-gray-100'}">${entry.action}</span>
<span class="text-xs text-gray-500">${entry.formatted_time}</span>
</div>
${entry.old_status && entry.new_status ? `
<p class="text-sm mt-1">
Status: <span class="text-gray-600">${entry.old_status}</span> 
<i class="fas fa-arrow-right text-xs mx-1"></i> 
<span class="text-gray-600">${entry.new_status}</span>
</p>
` : ''}
<p class="text-xs text-gray-400 mt-1 break-all">${entry.block_hash || ''}</p>
</div>
`;
});
content.innerHTML = html;
}
modal.classList.remove('hidden');
}
// Close history modal
function closeHistoryModal() {
document.getElementById('historyModal').classList.add('hidden');
}
// Filter tasks
function filterTasks() {
currentFilters = {
status: document.getElementById('filterStatus').value,
priority: document.getElementById('filterPriority').value,
category: document.getElementById('filterCategory').value
};
displayTasks(allTasks);
}
// Refresh tasks
function refreshTasks() {
loadTasks();
loadStats();
verifyChain();
}
// Load statistics
async function loadStats() {
try {
const response = await fetch('api/get_stats.php?days=7');
const data = await response.json();
if (data.success) {
const stats = data.stats;
document.getElementById('totalTasks').textContent = stats.total_tasks;
document.getElementById('completedTasks').textContent = stats.completed_tasks;
document.getElementById('inProgressTasks').textContent = stats.in_progress_tasks;
document.getElementById('pendingTasks').textContent = stats.pending_tasks;
document.getElementById('completionRate').textContent = stats.completion_rate + '%';
displayProductivityChart(stats.daily_stats);
}
} catch (error) {
console.error('Error loading stats:', error);
}
}
// Display productivity chart
function displayProductivityChart(dailyStats) {
const chart = document.getElementById('productivityChart');
if (!dailyStats || dailyStats.length === 0) {
chart.innerHTML = '<p class="text-gray-500 text-center col-span-7">No data available</p>';
return;
}
// Find max value for scaling
const maxTasks = Math.max(...dailyStats.map(d => Math.max(d.tasks_created, d.tasks_completed)), 1);
let html = '';
dailyStats.forEach(day => {
const createdHeight = (day.tasks_created / maxTasks) * 100;
const completedHeight = (day.tasks_completed / maxTasks) * 100;
const date = new Date(day.date).toLocaleDateString('en-US', { weekday: 'short' });
html += `
<div class="flex flex-col items-center">
<div class="relative w-full h-32 flex items-end justify-center space-x-1">
<div class="w-4 bg-indigo-400 rounded-t" style="height: ${createdHeight}%"></div>
<div class="w-4 bg-green-400 rounded-t" style="height: ${completedHeight}%"></div>
</div>
<div class="text-xs mt-2 text-gray-600">${date}</div>
<div class="text-xs text-gray-500">${day.tasks_created}/${day.tasks_completed}</div>
</div>
`;
});
chart.innerHTML = html;
}
// Verify blockchain integrity
async function verifyChain() {
try {
const response = await fetch('api/verify_chain.php');
const data = await response.json();
const statusEl = document.getElementById('chainStatus');
const blockCountEl = document.getElementById('blockCount');
blockCountEl.textContent = data.total_blocks + ' blocks';
if (data.valid) {
statusEl.innerHTML = '<i class="fas fa-circle text-green-400 text-xs mr-2"></i>Chain Valid';
} else {
statusEl.innerHTML = '<i class="fas fa-exclamation-triangle text-red-400 text-xs mr-2"></i>Chain Invalid';
showNotification('Blockchain integrity issue detected!', 'error');
}
} catch (error) {
console.error('Error verifying chain:', error);
}
}
// Add to recent activity
function addRecentActivity(message) {
const container = document.getElementById('recentActivity');
const timestamp = new Date().toLocaleTimeString();
const activityEl = document.createElement('div');
activityEl.className = 'text-sm text-gray-600 p-2 bg-gray-50 rounded';
activityEl.innerHTML = `
<span class="text-xs text-gray-400">${timestamp}</span>
<p class="truncate">${message}</p>
`;
container.prepend(activityEl);
// Keep only last 10 activities
while (container.children.length > 10) {
container.removeChild(container.lastChild);
}
}
// Helper: Escape HTML
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Helper: Show notification
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `fixed top-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 animate-bounce ${
type === 'success' ? 'bg-green-500' :
type === 'error' ? 'bg-red-500' :
'bg-blue-500'
} text-white`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 3000);
}

11. style.css (Additional Styles)

```css
/* style.css - Additional custom styles */

/* Custom scrollbar */

taskContainer::-webkit-scrollbar,

recentActivity::-webkit-scrollbar,

historyContent::-webkit-scrollbar {

width: 8px;

}

taskContainer::-webkit-scrollbar-track,

recentActivity::-webkit-scrollbar-track,

historyContent::-webkit-scrollbar-track {

background: #f1f1f1;
border-radius: 4px;

}

taskContainer::-webkit-scrollbar-thumb,

recentActivity::-webkit-scrollbar-thumb,

historyContent::-webkit-scrollbar-thumb {

background: #c7d2fe;
border-radius: 4px;

}

taskContainer::-webkit-scrollbar-thumb:hover,

recentActivity::-webkit-scrollbar-thumb:hover,

historyContent::-webkit-scrollbar-thumb:hover {

background: #a5b4fc;

}

/* Task card animations */
@keyframes taskComplete {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.02);
background: rgba(34, 197, 94, 0.1);
}
}

.task-complete-animation {
animation: taskComplete 0.5s ease-in-out;
}

/* Blockchain verification badge */
.verification-badge {
display: inline-flex;
align-items: center;
padding: 0.25rem 0.75rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
}

/* Priority indicators */
.priority-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
margin-right: 6px;
}

.priority-Critical .priority-indicator { background: #ef4444; }
.priority-High .priority-indicator { background: #f97316; }
priority-Medium .priority-indicator { background: #eab308; }
.priority-Low .priority-indicator { background: #22c55e; }

/* Stat card hover effect */
.stat-card {
position: relative;
overflow: hidden;
}

.stat-card::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(
45deg,
transparent,
rgba(255, 255, 255, 0.1),
transparent
);
transform: rotate(45deg);
transition: all 0.5s ease;
opacity: 0;
}

.stat-card:hover::before {
opacity: 1;
transform: rotate(45deg) translate(50%, 50%);
}

/* Loading spinner */
.task-spinner {
border: 3px solid #e5e7eb;
border-radius: 50%;
border-top: 3px solid #6366f1;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
}

@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

/* Blockchain hash tooltip */
.hash-tooltip {
position: relative;
cursor: help;
}

.hash-tooltip:hover::after {
content: attr(data-full-hash);
position: absolute;
bottom: 100%;
left: 0;
background: #1f2937;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.7rem;
white-space: nowrap;
z-index: 10;
}

/* Productivity chart bar animation */
@key

/* Productivity chart bar animation */
@keyframes barGrow {
from {
transform: scaleY(0);
opacity: 0;
}
to {
transform: scaleY(1);
opacity: 1;
}
}
#productivityChart div > div {
animation: barGrow 0.5s ease-out;
transform-origin: bottom;
}
/* Task completion celebration */
@keyframes celebrate {
0%, 100% {
box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.7);
}
50% {
box-shadow: 0 0 0 10px rgba(34, 197, 94, 0);
}
}
.task-celebrate {
animation: celebrate 1s ease-in-out;
}
/* Blockchain verification pulse */
.verification-pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
/* Modal animations */
.modal-enter {
animation: modalFadeIn 0.3s ease-out;
}
@keyframes modalFadeIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Status transition effects */
.status-transition {
transition: all 0.3s ease;
}
/* Blockchain block visualization */
.block-visualization {
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
}
.block-dot {
width: 8px;
height: 8px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 2px;
transition: all 0.3s ease;
}
.block-dot:hover {
transform: scale(1.5);
box-shadow: 0 0 10px rgba(102, 126, 234, 0.5);
}
/* Responsive design adjustments */
@media (max-width: 768px) {
.task-card .flex {
flex-direction: column;
}
.task-card .flex > button {
width: 100%;
margin-top: 0.5rem;
}
#productivityChart {
grid-template-columns: repeat(7, 1fr);
gap: 0.5rem;
}
}
/* Dark mode support (optional) */
@media (prefers-color-scheme: dark) {
.task-card {
background: rgba(31, 41, 55, 0.95);
color: #f3f4f6;
}
.task-card .text-gray-600 {
color: #9ca3af;
}
.blockchain-hash {
background: #1f2937;
color: #9ca3af;
}
}

Additional Features Implementation

1. Enhanced Task Analytics - Add to TaskChain.php

/**
* Get detailed task analytics
*/
public function getDetailedAnalytics($period = 'week') {
$analytics = [
'completion_trend' => [],
'category_breakdown' => [],
'priority_distribution' => [],
'productivity_score' => 0,
'best_performing_day' => null,
'average_tasks_per_day' => 0
];
// Determine time range
switch ($period) {
case 'week':
$interval = 7;
break;
case 'month':
$interval = 30;
break;
case 'year':
$interval = 365;
break;
default:
$interval = 7;
}
// Get completion trend
$sql = "SELECT 
DATE(FROM_UNIXTIME(completed_at)) as completion_date,
COUNT(*) as tasks_completed
FROM task_chain 
WHERE completed_at IS NOT NULL 
AND completed_at >= UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL ? DAY))
AND task_id != 'GENESIS_TASK'
GROUP BY DATE(FROM_UNIXTIME(completed_at))
ORDER BY completion_date DESC";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("i", $interval);
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
$analytics['completion_trend'][] = $row;
}
// Category breakdown
$sql = "SELECT 
category,
COUNT(*) as total_tasks,
SUM(CASE WHEN status = 'Completed' THEN 1 ELSE 0 END) as completed_tasks
FROM (
SELECT t1.* FROM task_chain t1
INNER JOIN (
SELECT task_id, MAX(block_index) as max_block
FROM task_chain
WHERE task_id != 'GENESIS_TASK'
GROUP BY task_id
) t2 ON t1.task_id = t2.task_id AND t1.block_index = t2.max_block
) as latest
GROUP BY category";
$result = $this->conn->query($sql);
while ($row = $result->fetch_assoc()) {
$analytics['category_breakdown'][] = $row;
}
// Priority distribution
$sql = "SELECT 
priority,
COUNT(*) as count
FROM (
SELECT t1.* FROM task_chain t1
INNER JOIN (
SELECT task_id, MAX(block_index) as max_block
FROM task_chain
WHERE task_id != 'GENESIS_TASK'
GROUP BY task_id
) t2 ON t1.task_id = t2.task_id AND t1.block_index = t2.max_block
) as latest
GROUP BY priority";
$result = $this->conn->query($sql);
while ($row = $result->fetch_assoc()) {
$analytics['priority_distribution'][$row['priority']] = $row['count'];
}
// Calculate productivity score (0-100)
$totalTasks = array_sum(array_column($analytics['category_breakdown'], 'total_tasks'));
$completedTasks = array_sum(array_column($analytics['category_breakdown'], 'completed_tasks'));
if ($totalTasks > 0) {
$completionRate = ($completedTasks / $totalTasks) * 100;
// Get average completion time
$sql = "SELECT AVG(completed_at - created_at) as avg_time 
FROM task_chain 
WHERE completed_at IS NOT NULL 
AND task_id != 'GENESIS_TASK'";
$result = $this->conn->query($sql);
$avgTime = $result->fetch_assoc()['avg_time'];
// Productivity score formula: 40% completion rate + 30% speed + 30% consistency
$speedScore = $avgTime ? max(0, 100 - ($avgTime / 86400)) : 50; // Lower time = higher score
$consistencyScore = count($analytics['completion_trend']) > 0 ? 70 : 0;
$analytics['productivity_score'] = round(
($completionRate * 0.4) + ($speedScore * 0.3) + ($consistencyScore * 0.3)
);
}
// Best performing day
if (!empty($analytics['completion_trend'])) {
$best = max(array_column($analytics['completion_trend'], 'tasks_completed'));
$analytics['best_performing_day'] = $best;
}
// Average tasks per day
$analytics['average_tasks_per_day'] = round($completedTasks / $interval, 1);
return $analytics;
}

2. Task Search API - Create api/search_tasks.php

<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
require_once '../includes/TaskChain.php';
$query = $_GET['q'] ?? '';
$category = $_GET['category'] ?? '';
$priority = $_GET['priority'] ?? '';
$status = $_GET['status'] ?? '';
if (empty($query) && empty($category) && empty($priority) && empty($status)) {
echo json_encode(['success' => false, 'message' => 'Search parameters required']);
exit;
}
$taskChain = new TaskChain();
$allTasks = $taskChain->getAllTasks();
// Filter tasks based on search criteria
$results = array_filter($allTasks, function($task) use ($query, $category, $priority, $status) {
$match = true;
if (!empty($query)) {
$query = strtolower($query);
$titleMatch = strpos(strtolower($task['title']), $query) !== false;
$descMatch = strpos(strtolower($task['description'] ?? ''), $query) !== false;
$match = $match && ($titleMatch || $descMatch);
}
if (!empty($category) && $category !== 'all') {
$match = $match && ($task['category'] === $category);
}
if (!empty($priority) && $priority !== 'all') {
$match = $match && ($task['priority'] === $priority);
}
if (!empty($status) && $status !== 'all') {
$match = $match && ($task['status'] === $status);
}
return $match;
});
echo json_encode([
'success' => true,
'total' => count($results),
'results' => array_values($results)
]);
?>

3. Task Export Feature - Create api/export_tasks.php

<?php
require_once '../includes/TaskChain.php';
$format = $_GET['format'] ?? 'json';
$taskChain = new TaskChain();
$tasks = $taskChain->getAllTasks();
switch ($format) {
case 'csv':
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="tasks_export_' . date('Y-m-d') . '.csv"');
$output = fopen('php://output', 'w');
fputcsv($output, ['Task ID', 'Title', 'Description', 'Category', 'Priority', 'Status', 'Created', 'Completed', 'Block Index', 'Block Hash']);
foreach ($tasks as $task) {
fputcsv($output, [
$task['task_id'],
$task['title'],
$task['description'],
$task['category'],
$task['priority'],
$task['status'],
date('Y-m-d H:i:s', $task['created_at']),
$task['completed_at'] ? date('Y-m-d H:i:s', $task['completed_at']) : '',
$task['block_index'],
$task['block_hash']
]);
}
fclose($output);
break;
case 'json':
default:
header('Content-Type: application/json');
header('Content-Disposition: attachment; filename="tasks_export_' . date('Y-m-d') . '.json"');
echo json_encode(['tasks' => $tasks, 'exported_at' => date('Y-m-d H:i:s')], JSON_PRETTY_PRINT);
break;
}
?>

4. Add Search UI to index.php (Add after filter bar)

<!-- Search Bar -->
<div class="bg-white rounded-lg shadow-lg p-4 mb-4">
<div class="flex space-x-2">
<div class="flex-1">
<input type="text" id="searchQuery" placeholder="Search tasks..." 
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
<button onclick="searchTasks()" class="bg-indigo-600 hover:bg-indigo-700 text-white px-6 py-2 rounded-lg transition">
<i class="fas fa-search mr-2"></i>
Search
</button>
<button onclick="clearSearch()" class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg transition">
<i class="fas fa-times"></i>
</button>
</div>
<!-- Search Results -->
<div id="searchResults" class="mt-4 hidden">
<h3 class="text-sm font-medium text-gray-700 mb-2">Search Results</h3>
<div id="searchResultsList" class="space-y-2 max-h-60 overflow-y-auto">
<!-- Results will be loaded here -->
</div>
</div>
</div>

5. Add Search Functions to script.js

// Search tasks
async function searchTasks() {
const query = document.getElementById('searchQuery').value;
const category = document.getElementById('filterCategory').value;
const priority = document.getElementById('filterPriority').value;
const status = document.getElementById('filterStatus').value;
if (!query && category === 'all' && priority === 'all' && status === 'all') {
showNotification('Please enter search criteria', 'warning');
return;
}
try {
const params = new URLSearchParams({
q: query,
category: category,
priority: priority,
status: status
});
const response = await fetch(`api/search_tasks.php?${params}`);
const data = await response.json();
if (data.success) {
displaySearchResults(data.results);
}
} catch (error) {
console.error('Error searching tasks:', error);
showNotification('Search failed', 'error');
}
}
// Display search results
function displaySearchResults(results) {
const container = document.getElementById('searchResults');
const list = document.getElementById('searchResultsList');
if (results.length === 0) {
list.innerHTML = '<p class="text-gray-500 text-center py-4">No tasks found</p>';
} else {
let html = '';
results.forEach(task => {
html += `
<div class="bg-gray-50 p-3 rounded-lg cursor-pointer hover:bg-gray-100 transition" 
onclick="scrollToTask('${task.task_id}')">
<div class="flex justify-between items-start">
<div>
<p class="font-medium">${escapeHtml(task.title)}</p>
<p class="text-xs text-gray-500">${task.category} • ${task.priority}</p>
</div>
<span class="text-xs status-badge ${
task.status === 'Completed' ? 'bg-green-100 text-green-800' :
task.status === 'In Progress' ? 'bg-blue-100 text-blue-800' :
'bg-yellow-100 text-yellow-800'
}">${task.status}</span>
</div>
</div>
`;
});
list.innerHTML = html;
}
container.classList.remove('hidden');
}
// Clear search
function clearSearch() {
document.getElementById('searchQuery').value = '';
document.getElementById('searchResults').classList.add('hidden');
displayTasks(allTasks);
}
// Scroll to specific task
function scrollToTask(taskId) {
const taskElement = document.querySelector(`[data-task-id="${taskId}"]`);
if (taskElement) {
taskElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
taskElement.classList.add('task-celebrate');
setTimeout(() => {
taskElement.classList.remove('task-celebrate');
}, 1000);
// Clear search
clearSearch();
}
}
// Export tasks
async function exportTasks(format = 'json') {
try {
window.location.href = `api/export_tasks.php?format=${format}`;
showNotification(`Tasks exported as ${format.toUpperCase()}`, 'success');
} catch (error) {
console.error('Error exporting tasks:', error);
showNotification('Export failed', 'error');
}
}

6. Add Export Button to UI (Add near filters)

<!-- Add to filter bar section -->
<button onclick="exportTasks('json')" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg text-sm transition">
<i class="fas fa-download mr-1"></i>
Export JSON
</button>
<button onclick="exportTasks('csv')" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm transition">
<i class="fas fa-file-csv mr-1"></i>
Export CSV
</button>

Database Indexes Optimization

Add these indexes to database/schema.sql for better performance:

-- Additional indexes for performance
CREATE INDEX idx_task_lookup ON task_chain(task_id, block_index);
CREATE INDEX idx_status_priority ON task_chain(status, priority);
CREATE INDEX idx_date_range ON task_chain(created_at, completed_at);
CREATE INDEX idx_history_lookup ON task_history(task_id, timestamp);
CREATE INDEX idx_stats_date ON productivity_stats(date);
-- Full-text search index (MySQL 5.6+)
ALTER TABLE task_chain ADD FULLTEXT INDEX ft_search (title, description);

How to Run

  1. Setup Environment:
  • Ensure XAMPP/WAMP/Laragon with PHP 7.4+ and MySQL
  • Start Apache and MySQL services
  1. Database Setup:
  • Open phpMyAdmin (http://localhost/phpmyadmin)
  • Create database taskchain_db
  • Import database/schema.sql
  1. Configure Database:
  • Update includes/config.php with your credentials
  1. Place Files:
  • Create folder blockchain-todo-list in web root
  • Copy all files maintaining structure
  1. Access Application:
  • Navigate to: http://localhost/blockchain-todo-list/

Testing the System

  1. Create Tasks:
  • Add tasks with different priorities and categories
  • Each task creates a new block in the chain
  1. Complete Tasks:
  • Mark tasks as complete
  • Notice the immutable timestamp recorded
  1. Verify Blockchain:
  • Click "Verify Chain" to check integrity
  • All blocks should be valid
  1. View History:
  • Click history icon on any task
  • See complete audit trail
  1. Search and Export:
  • Search for tasks by keywords
  • Export data in JSON or CSV format

Security Features

  • Immutable Records: Each task update creates a new block
  • Proof of Work: Prevents spam and ensures data integrity
  • Chain Verification: Automatic detection of tampering
  • Audit Trail: Complete history of all changes
  • Cryptographic Hashes: Each block is uniquely identified

Use Cases

  • Personal Productivity: Track tasks with verifiable completion times
  • Team Management: Audit task completion for remote teams
  • Compliance: Prove when tasks were completed for audits
  • Research: Track experiment steps with timestamps
  • Legal: Maintain immutable records of task completion

This project demonstrates a blockchain-based task management system where every action is permanently recorded and verifiable, providing complete transparency and trust in task completion history.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper