<?php
### MyTaskium версия 1.0 public
### По всем вопросам пишите на почту kontrkonst@gmail.com
### Претензии по коду исправляйте сами)))
### by Konstantin Larin
session_start();
ini_set('display_errors', '0');
ini_set('log_errors', '1');
$logFile = __DIR__ . '/data/error.log';
ini_set('error_log', $logFile);
error_reporting(E_ALL);
const PASSWORD = 'pass123'; # Пароль для входа
$DATA_DIR = __DIR__ . '/data';
$TASKS_FILE = $DATA_DIR . '/tasks.json';
$ARCHIVE_FILE = $DATA_DIR . '/archive.json';
if (!is_dir($DATA_DIR)) {
mkdir($DATA_DIR, 0755, true);
}
if (!file_exists($TASKS_FILE)) file_put_contents($TASKS_FILE, "[]");
if (!file_exists($ARCHIVE_FILE)) file_put_contents($ARCHIVE_FILE, "[]");
function safeLoad(string $file): array {
$txt = @file_get_contents($file);
$arr = json_decode($txt, true);
return is_array($arr) ? $arr : [];
}
function safeSave(string $file, array $arr): void {
@file_put_contents($file, json_encode(array_values($arr), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
function normalize(array $t): array {
$title = $t['title'] ?? $t['text'] ?? '';
$deadline = $t['deadline'] ?? $t['due'] ?? '';
$status = $t['status'] ?? 'Надо сделать';
$executor = $t['executor'] ?? $t['user'] ?? 'Я';
$created = $t['created'] ?? date('Y-m-d');
$closed = isset($t['closed']) && $t['closed'] !== '' ? (string)$t['closed'] : null;
$id = isset($t['id']) ? (string)$t['id'] : uniqid('', true);
return [
'id' => $id,
'title' => (string)$title,
'deadline' => (string)$deadline,
'status' => (string)$status,
'executor' => (string)$executor,
'created' => (string)$created,
'closed' => $closed
];
}
if (!isset($_SESSION['token'])) {
$_SESSION['token'] = bin2hex(random_bytes(16));
}
$TOKEN = $_SESSION['token'];
$tasksRaw = safeLoad($TASKS_FILE);
$archiveRaw = safeLoad($ARCHIVE_FILE);
$tasks = array_map('normalize', $tasksRaw);
$archive = array_map('normalize', $archiveRaw);
if (isset($_POST['logout'])) {
session_unset();
session_destroy();
header('Location: index.php');
exit;
}
if (!isset($_SESSION['logged_in'])) {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['password'])) {
if (hash_equals((string)PASSWORD, (string)$_POST['password'])) {
$_SESSION['logged_in'] = true;
$_SESSION['token'] = bin2hex(random_bytes(16));
header('Location: index.php');
exit;
} else {
$loginError = 'Неверный пароль';
}
}
?>
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Вход — Органайзер</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light d-flex align-items-center justify-content-center vh-100">
<div class="card shadow p-4" style="max-width:420px;width:100%">
<h4 class="mb-3 text-center">Вход в органайзер</h4>
<?php if (!empty($loginError)): ?>
<div class="alert alert-danger"><?= htmlspecialchars($loginError) ?></div>
<?php endif; ?>
<form method="post" class="mb-2">
<div class="mb-3"><input name="password" type="password" class="form-control" placeholder="Пароль" required autofocus></div>
<button class="btn btn-primary w-100">Войти</button>
</form>
<div class="text-muted small">Для доступа обратитесь к админу</div>
</div>
</body>
</html>
<?php
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
$postToken = $_POST['token'] ?? '';
if (!hash_equals((string)$TOKEN, (string)$postToken)) {
http_response_code(400);
echo 'Invalid token';
exit;
}
$action = $_POST['action'];
if ($action === 'add') {
$title = trim((string)($_POST['title'] ?? ''));
$deadline = trim((string)($_POST['deadline'] ?? ''));
$status = (string)($_POST['status'] ?? 'Надо сделать');
$executor = trim((string)($_POST['executor'] ?? 'Я'));
if ($deadline !== '') {
$d = DateTime::createFromFormat('Y-m-d', $deadline);
if (!$d || $d->format('Y-m-d') !== $deadline) $deadline = '';
}
$tasks[] = [
'id' => uniqid('', true),
'title' => $title,
'deadline' => $deadline,
'status' => $status,
'executor' => $executor,
'created' => date('Y-m-d'),
'closed' => null
];
safeSave($TASKS_FILE, $tasks);
header('Location: index.php'); exit;
}
if ($action === 'edit') {
$id = (string)($_POST['id'] ?? '');
foreach ($tasks as &$t) {
if ($t['id'] === $id) {
$t['title'] = trim((string)($_POST['title'] ?? $t['title']));
$deadline = trim((string)($_POST['deadline'] ?? $t['deadline']));
if ($deadline !== '') {
$d = DateTime::createFromFormat('Y-m-d', $deadline);
$t['deadline'] = ($d && $d->format('Y-m-d') === $deadline) ? $deadline : '';
} else {
$t['deadline'] = '';
}
$t['status'] = (string)($_POST['status'] ?? $t['status']);
$t['executor'] = trim((string)($_POST['executor'] ?? $t['executor']));
break;
}
}
unset($t);
safeSave($TASKS_FILE, $tasks);
header('Location: index.php'); exit;
}
if ($action === 'close') {
$id = (string)($_POST['id'] ?? '');
foreach ($tasks as $k => $t) {
if ($t['id'] === $id) {
$t['status'] = 'Закрыто';
$t['closed'] = date('Y-m-d');
$archive[] = $t;
unset($tasks[$k]);
break;
}
}
$tasks = array_values($tasks);
safeSave($TASKS_FILE, $tasks);
safeSave($ARCHIVE_FILE, $archive);
header('Location: index.php'); exit;
}
if ($action === 'delete') {
$id = (string)($_POST['id'] ?? '');
$tasks = array_values(array_filter($tasks, fn($x) => $x['id'] !== $id));
safeSave($TASKS_FILE, $tasks);
header('Location: index.php'); exit;
}
if ($action === 'extend') {
$id = (string)($_POST['id'] ?? '');
$days = (int)($_POST['days'] ?? 0);
if ($days >= 1 && $days <= 365) {
foreach ($tasks as &$t) {
if ($t['id'] === $id) {
if (!empty($t['deadline'])) {
$dt = DateTime::createFromFormat('Y-m-d', $t['deadline']);
if ($dt) {
$dt->modify("+{$days} days");
$t['deadline'] = $dt->format('Y-m-d');
}
} else {
$dt = new DateTime();
$dt->modify("+{$days} days");
$t['deadline'] = $dt->format('Y-m-d');
}
break;
}
}
unset($t);
safeSave($TASKS_FILE, $tasks);
}
header('Location: index.php'); exit;
}
}
$tasks_json_for_js = json_encode($tasks, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
?>
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Органайзер</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.text-due-soon { color: #d4aa00 !important; font-weight: 700; } /* <=3 дней */
.text-due-tomorrow { color: #ff8800 !important; font-weight: 700; } /* 1 день */
.text-due-today, .text-overdue { color: #c0392b !important; font-weight: 700; } /* сегодня/просрочено */
.text-done, .text-archived { color: #198754 !important; font-weight: 700; } /* закрыто */
</style>
</head>
<body class="bg-light">
<div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2>📋 Органайзер задач MyTaskium</h2>
<form method="post" style="margin:0">
<input type="hidden" name="logout" value="1">
<button type="submit" class="btn btn-outline-secondary">Выйти</button>
</form>
</div>
<div class="card mb-4 shadow-sm">
<div class="card-body">
<form method="post" class="row g-3">
<input type="hidden" name="action" value="add">
<input type="hidden" name="token" value="<?= htmlspecialchars($TOKEN) ?>">
<div class="col-12">
<textarea name="title" class="form-control" rows="3" required placeholder="Что нужно сделать..."></textarea>
</div>
<div class="col-md-3">
<label class="form-label">Срок</label>
<input name="deadline" type="date" class="form-control" />
</div>
<div class="col-md-3">
<label class="form-label">Статус</label>
<select name="status" class="form-select">
<option>Надо сделать</option>
<option>В работе</option>
<option>Важно</option>
<option>Срочно</option>
<option>Закрыто</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Исполнитель</label>
<input name="executor" class="form-control" value="Я" />
</div>
<div class="col-md-3 d-flex align-items-end">
<button class="btn btn-success w-100">Добавить</button>
</div>
</form>
</div>
</div>
<div class="card mb-4 shadow-sm">
<div class="card-body">
<h5 class="text-center">Текущие задачи</h5>
<table class="table table-hover table-bordered">
<thead class="table-light">
<tr class="text-center table-primary">
<th>Задача</th>
<th>Срок</th>
<th>Статус</th>
<th>Исполнитель</th>
<th>Создана</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
<?php
$today_ts = strtotime(date('Y-m-d'));
foreach ($tasks as $t):
$daysLeft = null;
if (!empty($t['deadline'])) {
$dt = DateTime::createFromFormat('Y-m-d', $t['deadline']);
if ($dt && $dt->format('Y-m-d') === $t['deadline']) {
$daysLeft = (int)floor(($dt->getTimestamp() - $today_ts)/86400);
}
}
$textClass = '';
if ($t['status'] === 'Закрыто') $textClass = 'text-done';
else if ($daysLeft !== null) {
if ($daysLeft < 0) $textClass = 'text-overdue';
elseif ($daysLeft === 0) $textClass = 'text-due-today';
elseif ($daysLeft === 1) $textClass = 'text-due-tomorrow';
elseif ($daysLeft <= 3) $textClass = 'text-due-soon';
}
$color = '';
if ($textClass === 'text-done') $color = '#198754';
elseif ($textClass === 'text-overdue' || $textClass === 'text-due-today') $color = '#c0392b';
elseif ($textClass === 'text-due-tomorrow') $color = '#ff8800';
elseif ($textClass === 'text-due-soon') $color = '#d4aa00';
?>
<tr>
<td style="<?= $color ? "color:{$color}; font-weight:700;" : '' ?>"><?= nl2br(htmlspecialchars($t['title'])) ?></td>
<td style="<?= $color ? "color:{$color}; font-weight:700;" : '' ?>">
<?= $t['deadline'] ? htmlspecialchars($t['deadline']) : '<span class="text-muted">—</span>' ?>
<?php if ($daysLeft !== null): ?>[<?= $daysLeft ?>]<?php endif; ?>
</td>
<td style="<?= $color ? "color:{$color}; font-weight:700;" : '' ?>"><?= htmlspecialchars($t['status']) ?></td>
<td style="<?= $color ? "color:{$color}; font-weight:700;" : '' ?>"><?= htmlspecialchars($t['executor']) ?></td>
<td><?= htmlspecialchars($t['created']) ?></td>
<td class="text-nowrap">
<button class="btn btn-sm btn-primary" data-action="edit" data-id="<?= htmlspecialchars($t['id']) ?>">✏</button>
<form method="post" style="display:inline">
<input type="hidden" name="action" value="close">
<input type="hidden" name="id" value="<?= htmlspecialchars($t['id']) ?>">
<input type="hidden" name="token" value="<?= htmlspecialchars($TOKEN) ?>">
<button class="btn btn-sm btn-success">✔</button>
</form>
<form method="post" style="display:inline" onsubmit="return confirm('Удалить задачу?')">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?= htmlspecialchars($t['id']) ?>">
<input type="hidden" name="token" value="<?= htmlspecialchars($TOKEN) ?>">
<button class="btn btn-sm btn-danger">🗑</button>
</form>
<form method="post" style="display:inline">
<input type="hidden" name="action" value="extend">
<input type="hidden" name="token" value="<?= htmlspecialchars($TOKEN) ?>">
<input type="hidden" name="id" value="<?= htmlspecialchars($t['id']) ?>">
<input type="hidden" name="days" value="1">
<button class="btn btn-sm btn-outline-primary" title="+1 день">+1</button>
</form>
<form method="post" style="display:inline">
<input type="hidden" name="action" value="extend">
<input type="hidden" name="token" value="<?= htmlspecialchars($TOKEN) ?>">
<input type="hidden" name="id" value="<?= htmlspecialchars($t['id']) ?>">
<input type="hidden" name="days" value="3">
<button class="btn btn-sm btn-outline-primary" title="+3 дня">+3</button>
</form>
<form method="post" style="display:inline">
<input type="hidden" name="action" value="extend">
<input type="hidden" name="token" value="<?= htmlspecialchars($TOKEN) ?>">
<input type="hidden" name="id" value="<?= htmlspecialchars($t['id']) ?>">
<input type="hidden" name="days" value="7">
<button class="btn btn-sm btn-outline-primary" title="+7 дней">+7</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<div class="card shadow-sm">
<div class="card-body">
<h5 class="text-center">Архив (закрытые задачи)</h5>
<table class="table table-sm table-bordered">
<thead class="table-light"><tr class="text-center table-primary"><th>Задача</th><th>Срок</th><th>Исполнитель</th><th>Создана</th><th>Закрыта</th></tr></thead>
<tbody>
<?php foreach ($archive as $a): ?>
<tr>
<td style="color:#198754;font-weight:700"><?= nl2br(htmlspecialchars($a['title'])) ?></td>
<td style="color:#198754;font-weight:700"><?= htmlspecialchars($a['deadline']) ?></td>
<td style="color:#198754;font-weight:700"><?= htmlspecialchars($a['executor']) ?></td>
<td><?= htmlspecialchars($a['created']) ?></td>
<td><?= htmlspecialchars($a['closed']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<div class="modal fade" id="editModal" tabindex="-1">
<div class="modal-dialog">
<form method="post" class="modal-content" id="editForm">
<input type="hidden" name="action" value="edit">
<input type="hidden" name="id" id="edit_id">
<input type="hidden" name="token" value="<?= htmlspecialchars($TOKEN) ?>">
<div class="modal-header">
<h5 class="modal-title">Редактировать задачу</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-2">
<label class="form-label">Задача</label>
<textarea name="title" id="edit_title" class="form-control" rows="3" required></textarea>
</div>
<div class="mb-2">
<label class="form-label">Срок</label>
<input name="deadline" id="edit_deadline" type="date" class="form-control" />
</div>
<div class="mb-2">
<label class="form-label">Статус</label>
<select name="status" id="edit_status" class="form-select">
<option>Надо сделать</option>
<option>В работе</option>
<option>Важно</option>
<option>Срочно</option>
<option>Закрыто</option>
</select>
</div>
<div class="mb-2">
<label class="form-label">Исполнитель</label>
<input name="executor" id="edit_executor" class="form-control" />
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary">Сохранить</button>
</div>
</form>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
const tasksData = <?= $tasks_json_for_js ?: '[]' ?>;
function findTaskById(id) {
return tasksData.find(t => t.id === id) || null;
}
document.querySelectorAll('button[data-action="edit"]').forEach(btn => {
btn.addEventListener('click', (e) => {
const id = btn.getAttribute('data-id');
const t = findTaskById(id);
if (!t) return alert('Задача не найдена (возможно, данные устарели).');
document.getElementById('edit_id').value = t.id;
document.getElementById('edit_title').value = t.title || '';
document.getElementById('edit_deadline').value = t.deadline || '';
document.getElementById('edit_status').value = t.status || 'Надо сделать';
document.getElementById('edit_executor').value = t.executor || 'Я';
const modal = new bootstrap.Modal(document.getElementById('editModal'));
modal.show();
});
});
</script>
</body>
</html>