first commit

This commit is contained in:
2026-01-29 21:07:02 +01:00
commit bda2bc85d0
22 changed files with 2594 additions and 0 deletions

View File

@@ -0,0 +1,117 @@
<?php
require_once __DIR__ . '/../models/User.php';
class AuthController
{
public function loginForm(): void
{
$error = $_SESSION['error'] ?? null;
unset($_SESSION['error']);
require __DIR__ . '/../views/login.php';
}
public function login(): void
{
$login = trim($_POST['login'] ?? '');
$password = $_POST['password'] ?? '';
if (empty($login) || empty($password)) {
$_SESSION['error'] = 'Proszę podać login i hasło.';
header('Location: /logowanie');
exit;
}
$user = User::verifyPassword($login, $password);
if (!$user) {
$_SESSION['error'] = 'Nieprawidłowy login lub hasło.';
header('Location: /logowanie');
exit;
}
// Generowanie kodu weryfikacyjnego
$code = sprintf('%06d', random_int(0, 999999));
User::saveVerificationCode($user['id'], $code);
// Symulacja wysyłki maila
$_SESSION['pending_user_id'] = $user['id'];
$_SESSION['pending_user_login'] = $user['login'];
$_SESSION['simulated_code'] = $code; // W produkcji nie pokazywać!
header('Location: /weryfikacja');
exit;
}
public function verifyForm(): void
{
if (!isset($_SESSION['pending_user_id'])) {
header('Location: /logowanie');
exit;
}
$error = $_SESSION['error'] ?? null;
$simulatedCode = $_SESSION['simulated_code'] ?? null;
$userLogin = $_SESSION['pending_user_login'] ?? '';
unset($_SESSION['error']);
require __DIR__ . '/../views/verify.php';
}
public function verify(): void
{
if (!isset($_SESSION['pending_user_id'])) {
header('Location: /logowanie');
exit;
}
$code = trim($_POST['code'] ?? '');
$userId = $_SESSION['pending_user_id'];
if (empty($code)) {
$_SESSION['error'] = 'Proszę podać kod weryfikacyjny.';
header('Location: /weryfikacja');
exit;
}
if (!User::verifyCode($userId, $code)) {
$_SESSION['error'] = 'Nieprawidłowy lub wygasły kod weryfikacyjny.';
header('Location: /weryfikacja');
exit;
}
// Usunięcie kodu po użyciu
User::deleteVerificationCode($userId);
// Pełna autoryzacja
$_SESSION['user_id'] = $userId;
$_SESSION['user_login'] = $_SESSION['pending_user_login'];
$_SESSION['logged_in'] = true;
unset($_SESSION['pending_user_id']);
unset($_SESSION['pending_user_login']);
unset($_SESSION['simulated_code']);
header('Location: /panel');
exit;
}
public function dashboard(): void
{
if (!isset($_SESSION['logged_in']) || !$_SESSION['logged_in']) {
header('Location: /logowanie');
exit;
}
$userLogin = $_SESSION['user_login'] ?? 'Użytkownik';
require __DIR__ . '/../views/dashboard.php';
}
public function logout(): void
{
session_destroy();
header('Location: /logowanie');
exit;
}
}

View File

@@ -0,0 +1,179 @@
<?php
require_once __DIR__ . '/../models/Event.php';
class EventController
{
private function requireAuth(): void
{
if (!isset($_SESSION['logged_in']) || !$_SESSION['logged_in']) {
header('Location: /logowanie');
exit;
}
}
public function index(): void
{
$this->requireAuth();
$userId = $_SESSION['user_id'];
$userLogin = $_SESSION['user_login'];
// Pobierz rok i miesiąc z parametrów lub użyj bieżących
$year = isset($_GET['rok']) ? (int) $_GET['rok'] : (int) date('Y');
$month = isset($_GET['miesiac']) ? (int) $_GET['miesiac'] : (int) date('m');
// Walidacja
if ($month < 1) {
$month = 12;
$year--;
} elseif ($month > 12) {
$month = 1;
$year++;
}
$events = Event::getByMonth($userId, $year, $month);
// Grupowanie wydarzeń po dacie
$eventsByDate = [];
foreach ($events as $event) {
$eventsByDate[$event['event_date']][] = $event;
}
$success = $_SESSION['success'] ?? null;
$error = $_SESSION['error'] ?? null;
unset($_SESSION['success'], $_SESSION['error']);
require __DIR__ . '/../views/calendar/index.php';
}
public function dayEvents(string $date): void
{
$this->requireAuth();
$userId = $_SESSION['user_id'];
$userLogin = $_SESSION['user_login'];
// Walidacja daty
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
$_SESSION['error'] = 'Nieprawidłowy format daty.';
header('Location: /kalendarz');
exit;
}
$events = Event::getByDate($userId, $date);
$success = $_SESSION['success'] ?? null;
$error = $_SESSION['error'] ?? null;
unset($_SESSION['success'], $_SESSION['error']);
require __DIR__ . '/../views/calendar/day.php';
}
public function create(string $date = null): void
{
$this->requireAuth();
$userLogin = $_SESSION['user_login'];
$event = null;
$isEdit = false;
$selectedDate = $date ?? date('Y-m-d');
require __DIR__ . '/../views/calendar/form.php';
}
public function store(): void
{
$this->requireAuth();
$userId = $_SESSION['user_id'];
$title = trim($_POST['title'] ?? '');
$content = trim($_POST['content'] ?? '');
$eventDate = $_POST['event_date'] ?? '';
if (empty($title)) {
$_SESSION['error'] = 'Tytuł wydarzenia jest wymagany.';
header('Location: /kalendarz/nowe');
exit;
}
if (empty($eventDate)) {
$_SESSION['error'] = 'Data wydarzenia jest wymagana.';
header('Location: /kalendarz/nowe');
exit;
}
Event::create($userId, $title, $content, $eventDate);
$_SESSION['success'] = 'Wydarzenie zostało dodane.';
header('Location: /kalendarz/dzien/' . $eventDate);
exit;
}
public function edit(int $id): void
{
$this->requireAuth();
$userId = $_SESSION['user_id'];
$userLogin = $_SESSION['user_login'];
$event = Event::getById($id, $userId);
if (!$event) {
$_SESSION['error'] = 'Wydarzenie nie zostało znalezione.';
header('Location: /kalendarz');
exit;
}
$isEdit = true;
$selectedDate = $event['event_date'];
require __DIR__ . '/../views/calendar/form.php';
}
public function update(int $id): void
{
$this->requireAuth();
$userId = $_SESSION['user_id'];
$title = trim($_POST['title'] ?? '');
$content = trim($_POST['content'] ?? '');
$eventDate = $_POST['event_date'] ?? '';
if (empty($title)) {
$_SESSION['error'] = 'Tytuł wydarzenia jest wymagany.';
header("Location: /kalendarz/edytuj/$id");
exit;
}
$event = Event::getById($id, $userId);
if (!$event) {
$_SESSION['error'] = 'Wydarzenie nie zostało znalezione.';
header('Location: /kalendarz');
exit;
}
Event::update($id, $userId, $title, $content, $eventDate);
$_SESSION['success'] = 'Wydarzenie zostało zaktualizowane.';
header('Location: /kalendarz/dzien/' . $eventDate);
exit;
}
public function delete(int $id): void
{
$this->requireAuth();
$userId = $_SESSION['user_id'];
$event = Event::getById($id, $userId);
if (!$event) {
$_SESSION['error'] = 'Wydarzenie nie zostało znalezione.';
header('Location: /kalendarz');
exit;
}
$eventDate = $event['event_date'];
Event::delete($id, $userId);
$_SESSION['success'] = 'Wydarzenie zostało usunięte.';
header('Location: /kalendarz/dzien/' . $eventDate);
exit;
}
}

View File

@@ -0,0 +1,126 @@
<?php
require_once __DIR__ . '/../models/Note.php';
class NoteController
{
private function requireAuth(): void
{
if (!isset($_SESSION['logged_in']) || !$_SESSION['logged_in']) {
header('Location: /logowanie');
exit;
}
}
public function index(): void
{
$this->requireAuth();
$userId = $_SESSION['user_id'];
$userLogin = $_SESSION['user_login'];
$notes = Note::getAllByUser($userId);
$success = $_SESSION['success'] ?? null;
$error = $_SESSION['error'] ?? null;
unset($_SESSION['success'], $_SESSION['error']);
require __DIR__ . '/../views/notes/index.php';
}
public function create(): void
{
$this->requireAuth();
$userLogin = $_SESSION['user_login'];
$note = null;
$isEdit = false;
require __DIR__ . '/../views/notes/form.php';
}
public function store(): void
{
$this->requireAuth();
$userId = $_SESSION['user_id'];
$title = trim($_POST['title'] ?? '');
$content = trim($_POST['content'] ?? '');
$color = $_POST['color'] ?? 'primary';
if (empty($title)) {
$_SESSION['error'] = 'Tytuł notatki jest wymagany.';
header('Location: /notatnik/nowa');
exit;
}
Note::create($userId, $title, $content, $color);
$_SESSION['success'] = 'Notatka została dodana.';
header('Location: /notatnik');
exit;
}
public function edit(int $id): void
{
$this->requireAuth();
$userId = $_SESSION['user_id'];
$userLogin = $_SESSION['user_login'];
$note = Note::getById($id, $userId);
if (!$note) {
$_SESSION['error'] = 'Notatka nie została znaleziona.';
header('Location: /notatnik');
exit;
}
$isEdit = true;
require __DIR__ . '/../views/notes/form.php';
}
public function update(int $id): void
{
$this->requireAuth();
$userId = $_SESSION['user_id'];
$title = trim($_POST['title'] ?? '');
$content = trim($_POST['content'] ?? '');
$color = $_POST['color'] ?? 'primary';
if (empty($title)) {
$_SESSION['error'] = 'Tytuł notatki jest wymagany.';
header("Location: /notatnik/edytuj/$id");
exit;
}
$note = Note::getById($id, $userId);
if (!$note) {
$_SESSION['error'] = 'Notatka nie została znaleziona.';
header('Location: /notatnik');
exit;
}
Note::update($id, $userId, $title, $content, $color);
$_SESSION['success'] = 'Notatka została zaktualizowana.';
header('Location: /notatnik');
exit;
}
public function delete(int $id): void
{
$this->requireAuth();
$userId = $_SESSION['user_id'];
$note = Note::getById($id, $userId);
if (!$note) {
$_SESSION['error'] = 'Notatka nie została znaleziona.';
header('Location: /notatnik');
exit;
}
Note::delete($id, $userId);
$_SESSION['success'] = 'Notatka została usunięta.';
header('Location: /notatnik');
exit;
}
}

22
app/models/Database.php Normal file
View File

@@ -0,0 +1,22 @@
<?php
class Database
{
private static ?PDO $instance = null;
private static string $dbPath = __DIR__ . '/../../database/app.db';
public static function getInstance(): PDO
{
if (self::$instance === null) {
self::$instance = new PDO('sqlite:' . self::$dbPath);
self::$instance->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
self::$instance->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
}
return self::$instance;
}
public static function getDbPath(): string
{
return self::$dbPath;
}
}

182
app/models/Event.php Normal file
View File

@@ -0,0 +1,182 @@
<?php
require_once __DIR__ . '/Database.php';
class Event
{
public static function ensureTable(): void
{
$db = Database::getInstance();
$db->exec('
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT,
event_date DATE NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
)
');
}
public static function getAllByUser(int $userId): array
{
self::ensureTable();
$db = Database::getInstance();
$stmt = $db->prepare('
SELECT * FROM events
WHERE user_id = :user_id
ORDER BY event_date ASC
');
$stmt->execute(['user_id' => $userId]);
return $stmt->fetchAll();
}
public static function getByMonth(int $userId, int $year, int $month): array
{
self::ensureTable();
$db = Database::getInstance();
$startDate = sprintf('%04d-%02d-01', $year, $month);
$endDate = sprintf('%04d-%02d-%02d', $year, $month, cal_days_in_month(CAL_GREGORIAN, $month, $year));
$stmt = $db->prepare('
SELECT * FROM events
WHERE user_id = :user_id
AND event_date BETWEEN :start_date AND :end_date
ORDER BY event_date ASC
');
$stmt->execute([
'user_id' => $userId,
'start_date' => $startDate,
'end_date' => $endDate
]);
return $stmt->fetchAll();
}
public static function getByDate(int $userId, string $date): array
{
self::ensureTable();
$db = Database::getInstance();
$stmt = $db->prepare('
SELECT * FROM events
WHERE user_id = :user_id AND event_date = :event_date
ORDER BY created_at ASC
');
$stmt->execute(['user_id' => $userId, 'event_date' => $date]);
return $stmt->fetchAll();
}
public static function getById(int $id, int $userId): ?array
{
self::ensureTable();
$db = Database::getInstance();
$stmt = $db->prepare('
SELECT * FROM events
WHERE id = :id AND user_id = :user_id
');
$stmt->execute(['id' => $id, 'user_id' => $userId]);
$event = $stmt->fetch();
return $event ?: null;
}
public static function create(int $userId, string $title, string $content, string $eventDate): int
{
self::ensureTable();
$db = Database::getInstance();
$stmt = $db->prepare('
INSERT INTO events (user_id, title, content, event_date, created_at, updated_at)
VALUES (:user_id, :title, :content, :event_date, datetime("now"), datetime("now"))
');
$stmt->execute([
'user_id' => $userId,
'title' => $title,
'content' => $content,
'event_date' => $eventDate
]);
return (int) $db->lastInsertId();
}
public static function update(int $id, int $userId, string $title, string $content, string $eventDate): bool
{
self::ensureTable();
$db = Database::getInstance();
$stmt = $db->prepare('
UPDATE events
SET title = :title, content = :content, event_date = :event_date, updated_at = datetime("now")
WHERE id = :id AND user_id = :user_id
');
return $stmt->execute([
'id' => $id,
'user_id' => $userId,
'title' => $title,
'content' => $content,
'event_date' => $eventDate
]);
}
public static function delete(int $id, int $userId): bool
{
$db = Database::getInstance();
$stmt = $db->prepare('
DELETE FROM events
WHERE id = :id AND user_id = :user_id
');
return $stmt->execute(['id' => $id, 'user_id' => $userId]);
}
public static function countByUser(int $userId): int
{
self::ensureTable();
$db = Database::getInstance();
$stmt = $db->prepare('SELECT COUNT(*) as count FROM events WHERE user_id = :user_id');
$stmt->execute(['user_id' => $userId]);
return (int) $stmt->fetch()['count'];
}
public static function countThisWeek(int $userId): int
{
self::ensureTable();
$db = Database::getInstance();
// Poniedziałek tego tygodnia
$monday = date('Y-m-d', strtotime('monday this week'));
// Niedziela tego tygodnia
$sunday = date('Y-m-d', strtotime('sunday this week'));
$stmt = $db->prepare('
SELECT COUNT(*) as count FROM events
WHERE user_id = :user_id
AND event_date BETWEEN :monday AND :sunday
');
$stmt->execute([
'user_id' => $userId,
'monday' => $monday,
'sunday' => $sunday
]);
return (int) $stmt->fetch()['count'];
}
public static function getThisWeek(int $userId): array
{
self::ensureTable();
$db = Database::getInstance();
$monday = date('Y-m-d', strtotime('monday this week'));
$sunday = date('Y-m-d', strtotime('sunday this week'));
$stmt = $db->prepare('
SELECT * FROM events
WHERE user_id = :user_id
AND event_date BETWEEN :monday AND :sunday
ORDER BY event_date ASC
');
$stmt->execute([
'user_id' => $userId,
'monday' => $monday,
'sunday' => $sunday
]);
return $stmt->fetchAll();
}
}

103
app/models/Note.php Normal file
View File

@@ -0,0 +1,103 @@
<?php
require_once __DIR__ . '/Database.php';
class Note
{
public static function ensureTable(): void
{
$db = Database::getInstance();
$db->exec('
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT,
color VARCHAR(20) DEFAULT "primary",
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
)
');
}
public static function getAllByUser(int $userId): array
{
self::ensureTable();
$db = Database::getInstance();
$stmt = $db->prepare('
SELECT * FROM notes
WHERE user_id = :user_id
ORDER BY updated_at DESC
');
$stmt->execute(['user_id' => $userId]);
return $stmt->fetchAll();
}
public static function getById(int $id, int $userId): ?array
{
self::ensureTable();
$db = Database::getInstance();
$stmt = $db->prepare('
SELECT * FROM notes
WHERE id = :id AND user_id = :user_id
');
$stmt->execute(['id' => $id, 'user_id' => $userId]);
$note = $stmt->fetch();
return $note ?: null;
}
public static function create(int $userId, string $title, string $content, string $color = 'primary'): int
{
self::ensureTable();
$db = Database::getInstance();
$stmt = $db->prepare('
INSERT INTO notes (user_id, title, content, color, created_at, updated_at)
VALUES (:user_id, :title, :content, :color, datetime("now"), datetime("now"))
');
$stmt->execute([
'user_id' => $userId,
'title' => $title,
'content' => $content,
'color' => $color
]);
return (int) $db->lastInsertId();
}
public static function update(int $id, int $userId, string $title, string $content, string $color): bool
{
self::ensureTable();
$db = Database::getInstance();
$stmt = $db->prepare('
UPDATE notes
SET title = :title, content = :content, color = :color, updated_at = datetime("now")
WHERE id = :id AND user_id = :user_id
');
return $stmt->execute([
'id' => $id,
'user_id' => $userId,
'title' => $title,
'content' => $content,
'color' => $color
]);
}
public static function delete(int $id, int $userId): bool
{
$db = Database::getInstance();
$stmt = $db->prepare('
DELETE FROM notes
WHERE id = :id AND user_id = :user_id
');
return $stmt->execute(['id' => $id, 'user_id' => $userId]);
}
public static function countByUser(int $userId): int
{
self::ensureTable();
$db = Database::getInstance();
$stmt = $db->prepare('SELECT COUNT(*) as count FROM notes WHERE user_id = :user_id');
$stmt->execute(['user_id' => $userId]);
return (int) $stmt->fetch()['count'];
}
}

61
app/models/User.php Normal file
View File

@@ -0,0 +1,61 @@
<?php
require_once __DIR__ . '/Database.php';
class User
{
public static function findByLogin(string $login): ?array
{
$db = Database::getInstance();
$stmt = $db->prepare('SELECT * FROM users WHERE login = :login');
$stmt->execute(['login' => $login]);
$user = $stmt->fetch();
return $user ?: null;
}
public static function verifyPassword(string $login, string $password): ?array
{
$user = self::findByLogin($login);
if ($user && password_verify($password, $user['password'])) {
return $user;
}
return null;
}
public static function saveVerificationCode(int $userId, string $code): bool
{
$db = Database::getInstance();
$expiresAt = date('Y-m-d H:i:s', strtotime('+10 minutes'));
$stmt = $db->prepare('
INSERT OR REPLACE INTO verification_codes (user_id, code, expires_at, created_at)
VALUES (:user_id, :code, :expires_at, datetime("now"))
');
return $stmt->execute([
'user_id' => $userId,
'code' => $code,
'expires_at' => $expiresAt
]);
}
public static function verifyCode(int $userId, string $code): bool
{
$db = Database::getInstance();
$stmt = $db->prepare('
SELECT * FROM verification_codes
WHERE user_id = :user_id
AND code = :code
AND expires_at > datetime("now")
');
$stmt->execute(['user_id' => $userId, 'code' => $code]);
return (bool) $stmt->fetch();
}
public static function deleteVerificationCode(int $userId): bool
{
$db = Database::getInstance();
$stmt = $db->prepare('DELETE FROM verification_codes WHERE user_id = :user_id');
return $stmt->execute(['user_id' => $userId]);
}
}

164
app/views/calendar/day.php Normal file
View File

@@ -0,0 +1,164 @@
<?php
require_once __DIR__ . '/../layout.php';
$dayNames = [
1 => 'Poniedziałek', 2 => 'Wtorek', 3 => 'Środa', 4 => 'Czwartek',
5 => 'Piątek', 6 => 'Sobota', 7 => 'Niedziela'
];
$monthNames = [
1 => 'stycznia', 2 => 'lutego', 3 => 'marca', 4 => 'kwietnia',
5 => 'maja', 6 => 'czerwca', 7 => 'lipca', 8 => 'sierpnia',
9 => 'września', 10 => 'października', 11 => 'listopada', 12 => 'grudnia'
];
$timestamp = strtotime($date);
$dayOfWeek = date('N', $timestamp);
$day = date('j', $timestamp);
$month = date('n', $timestamp);
$year = date('Y', $timestamp);
$formattedDate = $dayNames[$dayOfWeek] . ', ' . $day . ' ' . $monthNames[$month] . ' ' . $year;
$isToday = ($date === date('Y-m-d'));
ob_start();
?>
<?php if (isset($success)): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="bi bi-check-circle-fill me-2"></i>
<?= htmlspecialchars($success) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<?php if (isset($error)): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<?= htmlspecialchars($error) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<a href="/kalendarz?rok=<?= $year ?>&miesiac=<?= $month ?>" class="btn btn-outline-light mb-2">
<i class="bi bi-arrow-left me-1"></i>Powrót do kalendarza
</a>
<h2 class="text-white mb-0">
<?php if ($isToday): ?>
<span class="badge bg-success me-2">Dzisiaj</span>
<?php endif; ?>
<?= $formattedDate ?>
</h2>
</div>
<a href="/kalendarz/nowe/<?= $date ?>" class="btn btn-light btn-lg">
<i class="bi bi-plus-lg me-1"></i>Dodaj wydarzenie
</a>
</div>
<?php if (empty($events)): ?>
<div class="card">
<div class="card-body text-center py-5">
<i class="bi bi-calendar-x text-muted" style="font-size: 4rem;"></i>
<h4 class="mt-3 text-muted">Brak wydarzeń</h4>
<p class="text-muted mb-4">Nie masz żadnych wydarzeń zaplanowanych na ten dzień.</p>
<a href="/kalendarz/nowe/<?= $date ?>" class="btn btn-primary">
<i class="bi bi-plus-lg me-1"></i>Dodaj wydarzenie
</a>
</div>
</div>
<?php else: ?>
<div class="row g-3">
<?php foreach ($events as $event): ?>
<div class="col-12">
<div class="card event-card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<div class="flex-grow-1">
<h5 class="card-title mb-2">
<i class="bi bi-calendar-event text-primary me-2"></i>
<?= htmlspecialchars($event['title']) ?>
</h5>
<?php if (!empty($event['content'])): ?>
<p class="card-text text-muted mb-2">
<?= nl2br(htmlspecialchars($event['content'])) ?>
</p>
<?php endif; ?>
<small class="text-muted">
<i class="bi bi-clock me-1"></i>
Dodano: <?= date('d.m.Y H:i', strtotime($event['created_at'])) ?>
</small>
</div>
<div class="event-actions ms-3">
<a href="/kalendarz/edytuj/<?= $event['id'] ?>" class="btn btn-sm btn-outline-primary" title="Edytuj">
<i class="bi bi-pencil"></i>
</a>
<button type="button" class="btn btn-sm btn-outline-danger"
onclick="confirmDelete(<?= $event['id'] ?>, '<?= htmlspecialchars(addslashes($event['title'])) ?>')"
title="Usuń">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<!-- Modal potwierdzenia usunięcia -->
<div class="modal fade" id="deleteModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header border-0">
<div class="text-center w-100">
<div class="bg-danger bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 80px; height: 80px;">
<i class="bi bi-exclamation-triangle text-danger" style="font-size: 2.5rem;"></i>
</div>
<h5 class="modal-title">Potwierdzenie usunięcia</h5>
</div>
<button type="button" class="btn-close position-absolute top-0 end-0 m-3" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body text-center">
<p class="mb-1">Czy na pewno chcesz usunąć wydarzenie:</p>
<p class="fw-bold text-primary" id="deleteEventTitle"></p>
<p class="text-muted small mb-0">Ta operacja jest nieodwracalna.</p>
</div>
<div class="modal-footer justify-content-center border-0">
<button type="button" class="btn btn-secondary px-4" data-bs-dismiss="modal">
<i class="bi bi-x-lg me-1"></i>Anuluj
</button>
<form id="deleteForm" method="POST" class="d-inline">
<button type="submit" class="btn btn-danger px-4">
<i class="bi bi-trash me-1"></i>Usuń
</button>
</form>
</div>
</div>
</div>
</div>
<style>
.event-card {
border-left: 4px solid #667eea;
transition: transform 0.2s, box-shadow 0.2s;
}
.event-card:hover {
transform: translateX(5px);
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
}
</style>
<script>
function confirmDelete(id, title) {
document.getElementById('deleteEventTitle').textContent = '"' + title + '"';
document.getElementById('deleteForm').action = '/kalendarz/usun/' + id;
new bootstrap.Modal(document.getElementById('deleteModal')).show();
}
</script>
<?php
$content = ob_get_clean();
renderLayout('Wydarzenia - ' . $formattedDate, $content, true, 'kalendarz', 'col-lg-8');

View File

@@ -0,0 +1,72 @@
<?php
require_once __DIR__ . '/../layout.php';
$error = $_SESSION['error'] ?? null;
unset($_SESSION['error']);
ob_start();
?>
<div class="card">
<div class="card-header text-center text-white">
<i class="bi bi-<?= $isEdit ? 'pencil-square' : 'calendar-plus' ?> icon-large"></i>
<h4 class="mt-2 mb-0"><?= $isEdit ? 'Edytuj wydarzenie' : 'Nowe wydarzenie' ?></h4>
</div>
<div class="card-body p-4">
<?php if (isset($error)): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<?= htmlspecialchars($error) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<form method="POST" action="<?= $isEdit ? '/kalendarz/zapisz/' . $event['id'] : '/kalendarz/dodaj' ?>">
<div class="mb-3">
<label for="title" class="form-label">
<i class="bi bi-type me-1"></i>Tytuł wydarzenia
</label>
<input type="text" class="form-control" id="title" name="title"
value="<?= htmlspecialchars($event['title'] ?? '') ?>"
placeholder="Np. Spotkanie z klientem" required autofocus>
</div>
<div class="mb-3">
<label for="event_date" class="form-label">
<i class="bi bi-calendar-date me-1"></i>Data
</label>
<input type="date" class="form-control" id="event_date" name="event_date"
value="<?= htmlspecialchars($event['event_date'] ?? $selectedDate) ?>" required>
</div>
<div class="mb-4">
<label for="content" class="form-label">
<i class="bi bi-text-paragraph me-1"></i>Opis (opcjonalnie)
</label>
<textarea class="form-control" id="content" name="content" rows="4"
placeholder="Dodatkowe informacje o wydarzeniu..."><?= htmlspecialchars($event['content'] ?? '') ?></textarea>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary btn-lg">
<i class="bi bi-check-lg me-2"></i><?= $isEdit ? 'Zapisz zmiany' : 'Dodaj wydarzenie' ?>
</button>
<?php
$backUrl = '/kalendarz';
if ($isEdit && isset($event['event_date'])) {
$backUrl = '/kalendarz/dzien/' . $event['event_date'];
} elseif (isset($selectedDate)) {
$backUrl = '/kalendarz/dzien/' . $selectedDate;
}
?>
<a href="<?= $backUrl ?>" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-2"></i>Anuluj
</a>
</div>
</form>
</div>
</div>
<?php
$content = ob_get_clean();
renderLayout($isEdit ? 'Edytuj wydarzenie' : 'Nowe wydarzenie', $content, true, 'kalendarz', 'col-md-6');

View File

@@ -0,0 +1,200 @@
<?php
require_once __DIR__ . '/../layout.php';
$monthNames = [
1 => 'Styczeń', 2 => 'Luty', 3 => 'Marzec', 4 => 'Kwiecień',
5 => 'Maj', 6 => 'Czerwiec', 7 => 'Lipiec', 8 => 'Sierpień',
9 => 'Wrzesień', 10 => 'Październik', 11 => 'Listopad', 12 => 'Grudzień'
];
$dayNames = ['Pon', 'Wt', 'Śr', 'Czw', 'Pt', 'Sob', 'Ndz'];
// Obliczenia kalendarza
$firstDayOfMonth = mktime(0, 0, 0, $month, 1, $year);
$daysInMonth = cal_days_in_month(CAL_GREGORIAN, $month, $year);
$dayOfWeek = date('N', $firstDayOfMonth); // 1 = poniedziałek, 7 = niedziela
$prevMonth = $month - 1;
$prevYear = $year;
if ($prevMonth < 1) {
$prevMonth = 12;
$prevYear--;
}
$nextMonth = $month + 1;
$nextYear = $year;
if ($nextMonth > 12) {
$nextMonth = 1;
$nextYear++;
}
$today = date('Y-m-d');
ob_start();
?>
<?php if (isset($success)): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="bi bi-check-circle-fill me-2"></i>
<?= htmlspecialchars($success) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<?php if (isset($error)): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<?= htmlspecialchars($error) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<div class="card">
<div class="card-header bg-transparent">
<div class="d-flex justify-content-between align-items-center">
<a href="/kalendarz?rok=<?= $prevYear ?>&miesiac=<?= $prevMonth ?>" class="btn btn-outline-primary">
<i class="bi bi-chevron-left"></i>
</a>
<h4 class="mb-0">
<i class="bi bi-calendar3 me-2"></i>
<?= $monthNames[$month] ?> <?= $year ?>
</h4>
<a href="/kalendarz?rok=<?= $nextYear ?>&miesiac=<?= $nextMonth ?>" class="btn btn-outline-primary">
<i class="bi bi-chevron-right"></i>
</a>
</div>
</div>
<div class="card-body p-0">
<table class="table table-bordered mb-0 calendar-table">
<thead>
<tr>
<?php foreach ($dayNames as $dayName): ?>
<th class="text-center py-2 bg-light"><?= $dayName ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php
$currentDay = 1;
$started = false;
for ($week = 0; $week < 6; $week++):
if ($currentDay > $daysInMonth) break;
?>
<tr>
<?php for ($dow = 1; $dow <= 7; $dow++):
$cellDate = null;
$isToday = false;
$hasEvents = false;
$dayEvents = [];
if (!$started && $dow == $dayOfWeek) {
$started = true;
}
if ($started && $currentDay <= $daysInMonth) {
$cellDate = sprintf('%04d-%02d-%02d', $year, $month, $currentDay);
$isToday = ($cellDate === $today);
$hasEvents = isset($eventsByDate[$cellDate]);
if ($hasEvents) {
$dayEvents = $eventsByDate[$cellDate];
}
$displayDay = $currentDay;
$currentDay++;
} else {
$displayDay = null;
}
?>
<td class="calendar-cell <?= $isToday ? 'today' : '' ?> <?= $hasEvents ? 'has-events' : '' ?>"
<?php if ($cellDate): ?>onclick="window.location='/kalendarz/dzien/<?= $cellDate ?>'"<?php endif; ?>>
<?php if ($displayDay): ?>
<div class="day-number <?= $isToday ? 'bg-primary text-white' : '' ?>">
<?= $displayDay ?>
</div>
<?php if ($hasEvents): ?>
<div class="day-events">
<?php foreach (array_slice($dayEvents, 0, 2) as $evt): ?>
<div class="event-dot" title="<?= htmlspecialchars($evt['title']) ?>">
<?= htmlspecialchars(mb_substr($evt['title'], 0, 15)) ?><?= mb_strlen($evt['title']) > 15 ? '...' : '' ?>
</div>
<?php endforeach; ?>
<?php if (count($dayEvents) > 2): ?>
<div class="event-more">+<?= count($dayEvents) - 2 ?> więcej</div>
<?php endif; ?>
</div>
<?php endif; ?>
<?php endif; ?>
</td>
<?php endfor; ?>
</tr>
<?php endfor; ?>
</tbody>
</table>
</div>
<div class="card-footer bg-transparent">
<div class="d-flex justify-content-between align-items-center">
<a href="/kalendarz?rok=<?= date('Y') ?>&miesiac=<?= date('m') ?>" class="btn btn-outline-secondary">
<i class="bi bi-calendar-check me-1"></i>Dzisiaj
</a>
<a href="/kalendarz/nowe" class="btn btn-primary">
<i class="bi bi-plus-lg me-1"></i>Nowe wydarzenie
</a>
</div>
</div>
</div>
<style>
.calendar-table {
table-layout: fixed;
}
.calendar-cell {
height: 100px;
vertical-align: top;
padding: 5px !important;
cursor: pointer;
transition: background-color 0.2s;
}
.calendar-cell:hover {
background-color: #f8f9fa;
}
.calendar-cell.today {
background-color: #e7f1ff;
}
.calendar-cell.has-events {
background-color: #fff3cd;
}
.calendar-cell.today.has-events {
background-color: #d1e7dd;
}
.day-number {
display: inline-block;
width: 28px;
height: 28px;
line-height: 28px;
text-align: center;
border-radius: 50%;
font-weight: 600;
margin-bottom: 3px;
}
.day-events {
font-size: 0.7rem;
}
.event-dot {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1px 5px;
border-radius: 3px;
margin-bottom: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.event-more {
color: #6c757d;
font-style: italic;
}
</style>
<?php
$content = ob_get_clean();
renderLayout('Kalendarz', $content, true, 'kalendarz', 'col-lg-10');

179
app/views/dashboard.php Normal file
View File

@@ -0,0 +1,179 @@
<?php
require_once __DIR__ . '/layout.php';
require_once __DIR__ . '/../models/Note.php';
require_once __DIR__ . '/../models/Event.php';
$userId = $_SESSION['user_id'] ?? 0;
$notesCount = Note::countByUser($userId);
$eventsThisWeek = Event::countThisWeek($userId);
$upcomingEvents = Event::getThisWeek($userId);
ob_start();
?>
<div class="row">
<div class="col-12 mb-4">
<div class="card">
<div class="card-body p-4">
<div class="d-flex align-items-center">
<div class="bg-primary bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-4"
style="width: 80px; height: 80px;">
<i class="bi bi-person-circle text-primary" style="font-size: 2.5rem;"></i>
</div>
<div>
<h3 class="mb-1">
Witaj, <span class="text-primary"><?= htmlspecialchars($userLogin) ?></span>!
</h3>
<p class="text-muted mb-0">
Pomyślnie zalogowano z uwierzytelnianiem dwuskładnikowym.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row g-4">
<div class="col-md-3">
<div class="card h-100">
<div class="card-body text-center p-4">
<div class="bg-success bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3"
style="width: 60px; height: 60px;">
<i class="bi bi-shield-fill-check text-success fs-3"></i>
</div>
<h5 class="card-title">Status 2FA</h5>
<p class="text-success mb-0 fw-bold">Aktywne</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card h-100">
<div class="card-body text-center p-4">
<div class="bg-primary bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3"
style="width: 60px; height: 60px;">
<i class="bi bi-journal-text text-primary fs-3"></i>
</div>
<h5 class="card-title">Notatki</h5>
<p class="text-primary mb-0 fw-bold fs-4"><?= $notesCount ?></p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card h-100">
<div class="card-body text-center p-4">
<div class="bg-warning bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3"
style="width: 60px; height: 60px;">
<i class="bi bi-calendar-event text-warning fs-3"></i>
</div>
<h5 class="card-title">Ten tydzień</h5>
<p class="text-warning mb-0 fw-bold fs-4"><?= $eventsThisWeek ?> <?= $eventsThisWeek == 1 ? 'wydarzenie' : ($eventsThisWeek >= 2 && $eventsThisWeek <= 4 ? 'wydarzenia' : 'wydarzeń') ?></p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card h-100">
<div class="card-body text-center p-4">
<div class="bg-info bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3"
style="width: 60px; height: 60px;">
<i class="bi bi-clock-fill text-info fs-3"></i>
</div>
<h5 class="card-title">Sesja</h5>
<p class="text-info mb-0 fw-bold">Aktywna</p>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-transparent">
<h5 class="mb-0"><i class="bi bi-lightning-fill text-warning me-2"></i>Szybkie akcje</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<a href="/notatnik/nowa" class="btn btn-outline-primary">
<i class="bi bi-plus-lg me-2"></i>Nowa notatka
</a>
<a href="/kalendarz/nowe" class="btn btn-outline-warning">
<i class="bi bi-calendar-plus me-2"></i>Nowe wydarzenie
</a>
<a href="/kalendarz" class="btn btn-outline-secondary">
<i class="bi bi-calendar3 me-2"></i>Otwórz kalendarz
</a>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-transparent d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-calendar-week text-primary me-2"></i>Wydarzenia tego tygodnia</h5>
<a href="/kalendarz" class="btn btn-sm btn-outline-primary">Zobacz wszystkie</a>
</div>
<div class="card-body">
<?php if (empty($upcomingEvents)): ?>
<p class="text-muted text-center mb-0 py-3">
<i class="bi bi-calendar-x d-block mb-2" style="font-size: 2rem;"></i>
Brak wydarzeń w tym tygodniu
</p>
<?php else: ?>
<ul class="list-unstyled mb-0">
<?php foreach (array_slice($upcomingEvents, 0, 4) as $event): ?>
<li class="d-flex align-items-center mb-2 pb-2 border-bottom">
<div class="bg-primary bg-opacity-10 rounded p-2 me-3">
<i class="bi bi-calendar-event text-primary"></i>
</div>
<div class="flex-grow-1">
<div class="fw-semibold"><?= htmlspecialchars($event['title']) ?></div>
<small class="text-muted">
<?= date('d.m (D)', strtotime($event['event_date'])) ?>
</small>
</div>
<a href="/kalendarz/dzien/<?= $event['event_date'] ?>" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-eye"></i>
</a>
</li>
<?php endforeach; ?>
<?php if (count($upcomingEvents) > 4): ?>
<li class="text-center text-muted small pt-2">
+ <?= count($upcomingEvents) - 4 ?> więcej wydarzeń
</li>
<?php endif; ?>
</ul>
<?php endif; ?>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header bg-transparent">
<h5 class="mb-0"><i class="bi bi-info-circle-fill text-info me-2"></i>Informacje o sesji</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<i class="bi bi-calendar3 text-muted me-2"></i>
<strong>Data logowania:</strong> <?= date('d.m.Y H:i') ?>
</div>
<div class="col-md-4">
<i class="bi bi-shield-check text-muted me-2"></i>
<strong>Metoda 2FA:</strong> Kod Email
</div>
<div class="col-md-4">
<i class="bi bi-lock text-muted me-2"></i>
<strong>Połączenie:</strong> Szyfrowane
</div>
</div>
</div>
</div>
</div>
</div>
<?php
$content = ob_get_clean();
renderLayout('Pulpit', $content, true, 'pulpit', 'col-lg-10');

203
app/views/layout.php Normal file
View File

@@ -0,0 +1,203 @@
<?php
function renderLayout(string $title, string $content, bool $showMenu = false, string $activeMenu = '', string $containerSize = 'col-md-5'): void
{
$userLogin = $_SESSION['user_login'] ?? '';
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= htmlspecialchars($title) ?> - System 2FA</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" rel="stylesheet">
<style>
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
body.with-menu {
display: block;
padding-top: 70px;
}
body:not(.with-menu) {
display: flex;
align-items: center;
justify-content: center;
}
.navbar-custom {
background: rgba(255,255,255,0.95) !important;
backdrop-filter: blur(10px);
box-shadow: 0 2px 20px rgba(0,0,0,0.1);
}
.navbar-brand {
font-weight: 700;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.nav-link {
color: #495057 !important;
font-weight: 500;
padding: 0.5rem 1rem !important;
border-radius: 10px;
transition: all 0.2s;
}
.nav-link:hover, .nav-link.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white !important;
}
.card {
border: none;
border-radius: 15px;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
}
.card-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 15px 15px 0 0 !important;
padding: 1.5rem;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
padding: 0.75rem 2rem;
border-radius: 25px;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
background: linear-gradient(135deg, #5a6fd6 0%, #6a4190 100%);
}
.form-control, .form-select {
border-radius: 10px;
padding: 0.75rem 1rem;
border: 2px solid #e9ecef;
}
.form-control:focus, .form-select:focus {
border-color: #667eea;
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
}
.icon-large {
font-size: 3rem;
}
.code-display {
background: #f8f9fa;
border: 2px dashed #667eea;
border-radius: 10px;
padding: 1rem;
font-family: monospace;
font-size: 1.5rem;
letter-spacing: 0.5rem;
text-align: center;
}
.note-card {
border-radius: 15px;
transition: transform 0.2s, box-shadow 0.2s;
height: 100%;
}
.note-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0,0,0,0.15);
}
.note-card .card-body {
display: flex;
flex-direction: column;
}
.note-content {
flex-grow: 1;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
}
.note-actions {
opacity: 0;
transition: opacity 0.2s;
}
.note-card:hover .note-actions {
opacity: 1;
}
.color-option {
width: 30px;
height: 30px;
border-radius: 50%;
cursor: pointer;
border: 3px solid transparent;
transition: transform 0.2s;
}
.color-option:hover {
transform: scale(1.1);
}
.color-option.selected {
border-color: #333;
}
.modal-confirm .modal-header {
border-bottom: none;
padding-bottom: 0;
}
.modal-confirm .modal-footer {
border-top: none;
padding-top: 0;
}
</style>
</head>
<body class="<?= $showMenu ? 'with-menu' : '' ?>">
<?php if ($showMenu): ?>
<nav class="navbar navbar-expand-lg navbar-custom fixed-top">
<div class="container">
<a class="navbar-brand" href="/panel">
<i class="bi bi-shield-lock-fill me-2"></i>System 2FA
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link <?= $activeMenu === 'pulpit' ? 'active' : '' ?>" href="/panel">
<i class="bi bi-house-fill me-1"></i>Pulpit
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= $activeMenu === 'notatnik' ? 'active' : '' ?>" href="/notatnik">
<i class="bi bi-journal-text me-1"></i>Notatnik
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= $activeMenu === 'kalendarz' ? 'active' : '' ?>" href="/kalendarz">
<i class="bi bi-calendar3 me-1"></i>Kalendarz
</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="bi bi-person-circle me-1"></i><?= htmlspecialchars($userLogin) ?>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item text-danger" href="/wyloguj-sie">
<i class="bi bi-box-arrow-right me-2"></i>Wyloguj się
</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<?php endif; ?>
<div class="container <?= $showMenu ? 'py-4' : '' ?>">
<div class="row justify-content-center">
<div class="<?= $containerSize ?>">
<?= $content ?>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
<?php
}

53
app/views/login.php Normal file
View File

@@ -0,0 +1,53 @@
<?php
require_once __DIR__ . '/layout.php';
ob_start();
?>
<div class="card">
<div class="card-header text-center text-white">
<i class="bi bi-shield-lock icon-large"></i>
<h4 class="mt-2 mb-0">Logowanie</h4>
</div>
<div class="card-body p-4">
<?php if (isset($error)): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<?= htmlspecialchars($error) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<form method="POST" action="/zaloguj">
<div class="mb-3">
<label for="login" class="form-label">
<i class="bi bi-person-fill me-1"></i>Login
</label>
<input type="text" class="form-control" id="login" name="login"
placeholder="Wprowadź login" required autofocus>
</div>
<div class="mb-4">
<label for="password" class="form-label">
<i class="bi bi-key-fill me-1"></i>Hasło
</label>
<input type="password" class="form-control" id="password" name="password"
placeholder="Wprowadź hasło" required>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-lg">
<i class="bi bi-box-arrow-in-right me-2"></i>Zaloguj się
</button>
</div>
</form>
</div>
<div class="card-footer text-center text-muted bg-transparent border-0 pb-4">
<small>
<i class="bi bi-info-circle me-1"></i>
System zabezpieczony uwierzytelnianiem dwuskładnikowym
</small>
</div>
</div>
<?php
$content = ob_get_clean();
renderLayout('Logowanie', $content);

91
app/views/notes/form.php Normal file
View File

@@ -0,0 +1,91 @@
<?php
require_once __DIR__ . '/../layout.php';
$error = $_SESSION['error'] ?? null;
unset($_SESSION['error']);
$colors = [
'primary' => '#667eea',
'success' => '#28a745',
'danger' => '#dc3545',
'warning' => '#ffc107',
'info' => '#17a2b8',
'secondary' => '#6c757d'
];
$currentColor = $note['color'] ?? 'primary';
ob_start();
?>
<div class="card">
<div class="card-header text-center text-white">
<i class="bi bi-<?= $isEdit ? 'pencil-square' : 'plus-circle' ?> icon-large"></i>
<h4 class="mt-2 mb-0"><?= $isEdit ? 'Edytuj notatkę' : 'Nowa notatka' ?></h4>
</div>
<div class="card-body p-4">
<?php if (isset($error)): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<?= htmlspecialchars($error) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<form method="POST" action="<?= $isEdit ? '/notatnik/zapisz/' . $note['id'] : '/notatnik/dodaj' ?>">
<div class="mb-3">
<label for="title" class="form-label">
<i class="bi bi-type me-1"></i>Tytuł
</label>
<input type="text" class="form-control" id="title" name="title"
value="<?= htmlspecialchars($note['title'] ?? '') ?>"
placeholder="Wprowadź tytuł notatki" required autofocus>
</div>
<div class="mb-3">
<label for="content" class="form-label">
<i class="bi bi-text-paragraph me-1"></i>Treść
</label>
<textarea class="form-control" id="content" name="content" rows="6"
placeholder="Wprowadź treść notatki..."><?= htmlspecialchars($note['content'] ?? '') ?></textarea>
</div>
<div class="mb-4">
<label class="form-label">
<i class="bi bi-palette me-1"></i>Kolor
</label>
<div class="d-flex gap-2 flex-wrap">
<?php foreach ($colors as $name => $hex): ?>
<div class="color-option <?= $currentColor === $name ? 'selected' : '' ?>"
style="background-color: <?= $hex ?>;"
onclick="selectColor('<?= $name ?>', this)"
title="<?= ucfirst($name) ?>">
</div>
<?php endforeach; ?>
</div>
<input type="hidden" name="color" id="colorInput" value="<?= htmlspecialchars($currentColor) ?>">
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary btn-lg">
<i class="bi bi-check-lg me-2"></i><?= $isEdit ? 'Zapisz zmiany' : 'Dodaj notatkę' ?>
</button>
<a href="/notatnik" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-2"></i>Powrót do listy
</a>
</div>
</form>
</div>
</div>
<script>
function selectColor(color, element) {
document.querySelectorAll('.color-option').forEach(el => el.classList.remove('selected'));
element.classList.add('selected');
document.getElementById('colorInput').value = color;
}
</script>
<?php
$content = ob_get_clean();
renderLayout($isEdit ? 'Edytuj notatkę' : 'Nowa notatka', $content, true, 'notatnik', 'col-md-6');

120
app/views/notes/index.php Normal file
View File

@@ -0,0 +1,120 @@
<?php
require_once __DIR__ . '/../layout.php';
ob_start();
?>
<?php if (isset($success)): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="bi bi-check-circle-fill me-2"></i>
<?= htmlspecialchars($success) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<?php if (isset($error)): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<?= htmlspecialchars($error) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="text-white mb-0">
<i class="bi bi-journal-text me-2"></i>Moje notatki
</h2>
<a href="/notatnik/nowa" class="btn btn-light btn-lg">
<i class="bi bi-plus-lg me-1"></i>Nowa notatka
</a>
</div>
<?php if (empty($notes)): ?>
<div class="card">
<div class="card-body text-center py-5">
<i class="bi bi-journal-x text-muted" style="font-size: 4rem;"></i>
<h4 class="mt-3 text-muted">Brak notatek</h4>
<p class="text-muted mb-4">Nie masz jeszcze żadnych notatek. Utwórz pierwszą!</p>
<a href="/notatnik/nowa" class="btn btn-primary">
<i class="bi bi-plus-lg me-1"></i>Utwórz notatkę
</a>
</div>
</div>
<?php else: ?>
<div class="row g-4">
<?php foreach ($notes as $note): ?>
<div class="col-md-6 col-lg-4">
<div class="card note-card border-top border-4 border-<?= htmlspecialchars($note['color']) ?>">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-2">
<h5 class="card-title mb-0"><?= htmlspecialchars($note['title']) ?></h5>
<div class="note-actions">
<a href="/notatnik/edytuj/<?= $note['id'] ?>" class="btn btn-sm btn-outline-primary" title="Edytuj">
<i class="bi bi-pencil"></i>
</a>
<button type="button" class="btn btn-sm btn-outline-danger"
onclick="confirmDelete(<?= $note['id'] ?>, '<?= htmlspecialchars(addslashes($note['title'])) ?>')"
title="Usuń">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
<p class="note-content text-muted small">
<?= nl2br(htmlspecialchars($note['content'] ?: 'Brak treści')) ?>
</p>
<div class="mt-auto pt-2 border-top">
<small class="text-muted">
<i class="bi bi-clock me-1"></i>
<?= date('d.m.Y H:i', strtotime($note['updated_at'])) ?>
</small>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<!-- Modal potwierdzenia usunięcia -->
<div class="modal fade modal-confirm" id="deleteModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<div class="text-center w-100">
<div class="bg-danger bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 80px; height: 80px;">
<i class="bi bi-exclamation-triangle text-danger" style="font-size: 2.5rem;"></i>
</div>
<h5 class="modal-title">Potwierdzenie usunięcia</h5>
</div>
<button type="button" class="btn-close position-absolute top-0 end-0 m-3" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body text-center">
<p class="mb-1">Czy na pewno chcesz usunąć notatkę:</p>
<p class="fw-bold text-primary" id="deleteNoteTitle"></p>
<p class="text-muted small mb-0">Ta operacja jest nieodwracalna.</p>
</div>
<div class="modal-footer justify-content-center">
<button type="button" class="btn btn-secondary px-4" data-bs-dismiss="modal">
<i class="bi bi-x-lg me-1"></i>Anuluj
</button>
<form id="deleteForm" method="POST" class="d-inline">
<button type="submit" class="btn btn-danger px-4">
<i class="bi bi-trash me-1"></i>Usuń
</button>
</form>
</div>
</div>
</div>
</div>
<script>
function confirmDelete(id, title) {
document.getElementById('deleteNoteTitle').textContent = '"' + title + '"';
document.getElementById('deleteForm').action = '/notatnik/usun/' + id;
new bootstrap.Modal(document.getElementById('deleteModal')).show();
}
</script>
<?php
$content = ob_get_clean();
renderLayout('Notatnik', $content, true, 'notatnik', 'col-12');

74
app/views/verify.php Normal file
View File

@@ -0,0 +1,74 @@
<?php
require_once __DIR__ . '/layout.php';
ob_start();
?>
<div class="card">
<div class="card-header text-center text-white">
<i class="bi bi-phone icon-large"></i>
<h4 class="mt-2 mb-0">Weryfikacja dwuskładnikowa</h4>
</div>
<div class="card-body p-4">
<?php if (isset($error)): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<?= htmlspecialchars($error) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<div class="alert alert-info">
<i class="bi bi-envelope-fill me-2"></i>
Kod weryfikacyjny został wysłany na adres e-mail powiązany z kontem
<strong><?= htmlspecialchars($userLogin) ?></strong>.
</div>
<?php if (isset($simulatedCode)): ?>
<div class="mb-4">
<label class="form-label text-muted">
<i class="bi bi-bug-fill me-1"></i>Kod testowy (symulacja):
</label>
<div class="code-display">
<?= htmlspecialchars($simulatedCode) ?>
</div>
</div>
<?php endif; ?>
<form method="POST" action="/zweryfikuj">
<div class="mb-4">
<label for="code" class="form-label">
<i class="bi bi-123 me-1"></i>Kod weryfikacyjny
</label>
<input type="text" class="form-control form-control-lg text-center"
id="code" name="code" maxlength="6"
placeholder="000000" required autofocus
style="letter-spacing: 0.5rem; font-size: 1.5rem;">
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary btn-lg">
<i class="bi bi-check-circle me-2"></i>Zweryfikuj
</button>
<a href="/wyloguj-sie" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-2"></i>Anuluj i wróć
</a>
</div>
</form>
</div>
<div class="card-footer text-center text-muted bg-transparent border-0 pb-4">
<small>
<i class="bi bi-clock me-1"></i>
Kod jest ważny przez 10 minut
</small>
</div>
</div>
<script>
document.getElementById('code').addEventListener('input', function(e) {
this.value = this.value.replace(/[^0-9]/g, '');
});
</script>
<?php
$content = ob_get_clean();
renderLayout('Weryfikacja', $content);