Просмотр файла MyTaskium-main/index.php

Размер файла: 18.58Kb
<?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>