Преглед на файлове

Работа над генерацией отчётов

Вадим Королёв преди 1 година
родител
ревизия
22328a18cc

+ 3 - 0
.gitignore

@@ -1,3 +1,6 @@
 pockit.geany
 src/vendor
 src/.env
+src/jquery
+src/jqueryui
+src/db.sqlite3

+ 11 - 1
src/composer.json

@@ -1,5 +1,15 @@
 {
     "require": {
-        "vlucas/phpdotenv": "^5.6"
+        "vlucas/phpdotenv": "^5.6",
+        "components/jquery": "^3.7",
+        "components/jqueryui": "^1.12"
+    },
+    "scripts": {
+        "post-update-cmd": [
+            "rm -rf jquery",
+            "rm -rf jqueryui",
+            "cp -R vendor/components/jquery/ jquery",
+            "cp -R vendor/components/jqueryui jqueryui"
+        ]
     }
 }

+ 139 - 1
src/composer.lock

@@ -4,8 +4,146 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "108be68e4e2b97fed51d36a10eed0849",
+    "content-hash": "0a7b23981a3bb5ff5328f97a6b072cb7",
     "packages": [
+        {
+            "name": "components/jquery",
+            "version": "v3.7.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/components/jquery.git",
+                "reference": "8edc7785239bb8c2ad2b83302b856a1d61de60e7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/components/jquery/zipball/8edc7785239bb8c2ad2b83302b856a1d61de60e7",
+                "reference": "8edc7785239bb8c2ad2b83302b856a1d61de60e7",
+                "shasum": ""
+            },
+            "type": "component",
+            "extra": {
+                "component": {
+                    "scripts": [
+                        "jquery.js"
+                    ],
+                    "files": [
+                        "jquery.min.js",
+                        "jquery.min.map",
+                        "jquery.slim.js",
+                        "jquery.slim.min.js",
+                        "jquery.slim.min.map"
+                    ]
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "JS Foundation and other contributors"
+                }
+            ],
+            "description": "jQuery JavaScript Library",
+            "homepage": "http://jquery.com",
+            "support": {
+                "forum": "http://forum.jquery.com",
+                "irc": "irc://irc.freenode.org/jquery",
+                "issues": "https://github.com/jquery/jquery/issues",
+                "source": "https://github.com/jquery/jquery",
+                "wiki": "http://docs.jquery.com/"
+            },
+            "time": "2023-09-22T01:43:46+00:00"
+        },
+        {
+            "name": "components/jqueryui",
+            "version": "1.12.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/components/jqueryui.git",
+                "reference": "44ecf3794cc56b65954cc19737234a3119d036cc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/components/jqueryui/zipball/44ecf3794cc56b65954cc19737234a3119d036cc",
+                "reference": "44ecf3794cc56b65954cc19737234a3119d036cc",
+                "shasum": ""
+            },
+            "require": {
+                "components/jquery": ">=1.6"
+            },
+            "type": "component",
+            "extra": {
+                "component": {
+                    "name": "jquery-ui",
+                    "scripts": [
+                        "jquery-ui.js"
+                    ],
+                    "files": [
+                        "ui/**",
+                        "themes/**",
+                        "jquery-ui.min.js"
+                    ],
+                    "shim": {
+                        "deps": [
+                            "jquery"
+                        ],
+                        "exports": "jQuery"
+                    }
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "jQuery UI Team",
+                    "homepage": "http://jqueryui.com/about"
+                },
+                {
+                    "name": "Joern Zaefferer",
+                    "email": "joern.zaefferer@gmail.com",
+                    "homepage": "http://bassistance.de"
+                },
+                {
+                    "name": "Scott Gonzalez",
+                    "email": "scott.gonzalez@gmail.com",
+                    "homepage": "http://scottgonzalez.com"
+                },
+                {
+                    "name": "Kris Borchers",
+                    "email": "kris.borchers@gmail.com",
+                    "homepage": "http://krisborchers.com"
+                },
+                {
+                    "name": "Mike Sherov",
+                    "email": "mike.sherov@gmail.com",
+                    "homepage": "http://mike.sherov.com"
+                },
+                {
+                    "name": "TJ VanToll",
+                    "email": "tj.vantoll@gmail.com",
+                    "homepage": "http://tjvantoll.com"
+                },
+                {
+                    "name": "Corey Frang",
+                    "email": "gnarf37@gmail.com",
+                    "homepage": "http://gnarf.net"
+                },
+                {
+                    "name": "Felix Nagel",
+                    "email": "info@felixnagel.com",
+                    "homepage": "http://www.felixnagel.com"
+                }
+            ],
+            "description": "jQuery UI is a curated set of user interface interactions, effects, widgets, and themes built on top of the jQuery JavaScript Library. Whether you're building highly interactive web applications or you just need to add a date picker to a form control, jQuery UI is the perfect choice.",
+            "support": {
+                "issues": "https://github.com/components/jqueryui/issues",
+                "source": "https://github.com/components/jqueryui/tree/master"
+            },
+            "time": "2016-09-16T05:47:55+00:00"
+        },
         {
             "name": "graham-campbell/result-type",
             "version": "v1.1.2",

+ 7 - 1
src/config.php

@@ -15,4 +15,10 @@ if (strtoupper(substr(php_uname("s"), 0, 3)) === 'WIN') {
 }
 
 $dotenv = Dotenv\Dotenv::createImmutable(rootdir);
-$dotenv->load();
+$dotenv->load();
+
+// regen
+define("regen_surname", "Королёв");
+define("regen_full", "Королёв В.С.");
+define("regen_group", "3ИС");
+define("regen_code", ".012.09.02.07.000");

+ 1 - 1
src/controllers/GradesController.php

@@ -7,7 +7,7 @@ class GradesController extends Controller {
 	public function index() {
 		$view = new GradesView([
 			"page_title"=>"Оценки",
-			"crumbs" => ["Главная" => "/"]
+			"crumbs" => ["Главная" => "/", "Оценки" => "/grades"]
 		]);
 		$view->view();
 	}

+ 1 - 3
src/controllers/HomeController.php

@@ -18,11 +18,9 @@ class HomeController extends Controller {
 		}
 		$welcome_text .= ", ".$_ENV["user_name"];
 
-		//~ echo $welcome_text;
-
 		$view = new HomeView([
 			"welcome_text" => $welcome_text,
-			"page_title" => "ДОМ"
+			"page_title" => "Pockit"
 		]);
 		$view->view();
 	}

+ 250 - 0
src/controllers/RegenController.php

@@ -0,0 +1,250 @@
+<?php
+// Контроллер Regen
+
+class RegenController extends Controller {
+
+	// Редактирование отчёта
+	public function edit() {
+		$parts = explode("/", $this->request_uri);
+		$report_id = end($parts);
+		$report = ReportModel::getById($report_id);
+
+		$view = new RegenEditView([
+			"page_title" => "Regen: редактирование отчёта",
+			"crumbs" => ["Главная"=>"/", "Regen" => "/"],
+			"markup" => $report['markup']
+		]);
+		$view->view();
+	}
+
+	// Валидация создания отчёта
+	private function validateCreation() {
+		if (empty($_POST["number"])) {
+			// Запрос на создание
+			return 'Не указан номер работы';
+		}
+		return true;
+	}
+
+	// Создание отчёта
+	public function newReport() {
+		$subjects = SubjectModel::all();
+		$worktypes = WorkTypeModel::all();
+
+		if (!empty($_POST)) {
+			$response = $this->validateCreation();
+			if ($response === true) {
+				// Валидация успешна
+
+				// Создаём запись
+				$work_type = WorkTypeModel::getById($_POST['work_type']);
+				
+				$report_id = ReportModel::create(
+					$_POST["subject_id"],
+					$_POST['work_type'],
+					$_POST['number'],
+					$_POST['notice'],
+					"!-\n!\n#{$work_type['name_nom']} №{$_POST['number']}\n"
+				);
+
+				// Перенаправляем на предпросмотр этого отчёта
+				header("Location: /regen/edit/".$report_id);
+				return;
+			} else {
+				// Валидация провалена
+				$error_text = $response;
+			}
+		} else {
+			$error_text = null;
+		}
+		
+		$view = new RegenNewReportView([
+			"page_title" => "Regen: создание отчёта",
+			'subjects' => $subjects,
+			'worktypes' => $worktypes,
+			'error_text' => $error_text,
+			"crumbs" => ["Главная"=>"/", "Regen" => "/"]
+		]);
+		$view->view();
+	}
+
+	// Получение HTML
+	public function getHtml() {
+		$report = ReportModel::getById($_POST['report_id']);
+		$subject = SubjectModel::getById($report["subject_id"]);
+		$work_type = WorkTypeModel::getById($report["work_type"]);
+		$teacher = TeacherModel::getById($subject["teacher_id"]);
+		
+		$markup = $_POST['markup'];
+		$lines = explode("\n", $markup);
+
+		// Определение количества страниц
+		$pages_count = 0;
+		foreach ($lines as $l) {
+			if (self::isPageMarker($l)) {
+				$has_pages = true;
+				if (self::isCountablePage($l)) {
+					$pages_count++;
+				}
+			}
+		}
+
+		// Основные переменные
+		$current_line_index = -1;
+		$end_line_index = count($lines);
+		$output = "";
+
+		// Нумераторы
+		$current_page_number = 1;			// Страницы
+		$current_image_number = 1;			// Изображения
+		$current_table_number = 1;			// Таблицы
+		$current_application_number = 1;	// Приложения
+		$current_table_row = 0;				// Строка текущей таблицы
+
+		// Флаги
+		$is_generating_table = false; // Генерируем ли сейчас таблицу
+
+		// Генерируем (X)HTML!
+		while ($current_line_index < $end_line_index - 1) {
+			// Ищем маркер страницы. Пропускам всё, что не маркер
+			$current_line_index++;
+			if (!self::isPageMarker($lines[$current_line_index])) {
+				continue;
+			}
+			$current_page_marker = $lines[$current_line_index];
+			$current_line_index++;
+
+			// Генерация контента страницы
+			$page_content = "";
+			if ($is_generating_table == true) {
+				// Если мы генерируем таблицу, а сейчас начинается новая страница, то на предыдущей странице мы уже начинали
+				// генерировать и произошёл разрыв таблицы.
+				$page_content .= "<p class='tt'>Продолжение таблицы {$current_table_number}</p>
+				<table class='t{$current_table_number}'>";
+			}
+			while ($current_line_index < $end_line_index) {
+
+				if (self::isPageMarker($lines[$current_line_index])) {
+					// Эта строка - маркер страницы!
+					// Заканчиваем генерировать эту страницу и переходим к другой
+					$current_line_index--;
+					break;
+				}
+
+				$str = $lines[$current_line_index];
+
+				// Интерпретация строки
+				if ($str == "") {
+					// Пустая строка
+					$line_content = "";
+
+				} else if ($str[0] == "#") {
+					// Выровененный по центру текст
+					$line_content = "<p class='title'>".trim(substr($str, 1))."</p>";
+
+				} else if ($str[0] == "?") {
+					// Изображение
+					list($image_path, $image_title) = explode(":", substr($str, 1));
+					$picture_title = "Рисунок {$current_image_number} - {$image_title}";
+					$line_content = "<img src='/img/regen/$image_path' title='$picture_title'><p class='title'>$picture_title</p>";
+
+					$current_image_number++;
+
+				} else if (substr($str, 0, 6) === "@table") {
+					// Начало таблицы
+
+					$table_class = "t" . strval($current_table_number);
+					$parts = explode(":", substr($str, 1));
+					$style = "<style>";
+					for ($i = 1; $i < count($parts); $i++) {
+						list($width, $align) = explode("_", $parts[$i]);
+						$style .= ".{$table_class} td:nth-child({$i}),.{$table_class} th:nth-child({$i}){width:{$width}px;text-align:{$align}}";
+					}
+					$line_content .= "</style><p class='tt'>Таблица $current_table_number - {$parts[0]}</p><table class='$table_class'>";
+
+					$is_generating_table = true;
+
+				} else if ($str[0] === "|") {
+					// Продолжение таблицы
+					// Если это начало таблицы, то используем тэг th, иначе td
+					if ($current_table_row == 0) {
+						$tag_name = "th";
+					} else {
+						$tag_name = "td";
+					}
+
+					$line_content = "<tr>";
+					$parts = explode("|", $str);
+					for ($i = 1; $i < count($parts) - 1; $i++) {
+						$part = trim($parts[$i]);
+						$line_content .= "<{$tag_name}>$part</{$tag_name}>";
+					}
+					$line_content .= "</tr>";
+
+					$current_table_row += 1;
+
+					// Смотрим в будущее и ищем разрыв таблицы или конец всего отчёта
+					if ($current_line_index < $end_line_index - 1) {
+						if (self::isPageMarker($lines[$current_line_index + 1])) {
+							// Таблица разделена на 2 страницы
+							$line_content .= "</table>";
+						}
+					} else {
+						// Это конец отчёта! Нарушение разметки, но так уж и быть, поправим по-тихому
+						$line_content .= "</table>";
+					}
+
+				} else if ($str === "@endtable") {
+					// Конец таблицы
+					$line_content = "</table><br/>";
+					$is_generating_table = false;
+					$current_table_number++;
+					$current_table_row = 0;
+
+				} else {
+					// Обычный текст
+					$line_content = "<p>".$str."</p>";
+				}
+
+				$page_content .= $line_content;
+				$current_line_index++;
+			}
+
+			// Всё то, что нагенерировали засовываем в страницу
+			$page_wrapped = new RegenPageView([
+				'work_code' => $subject['code'].regen_code,
+				'teacher_full' => $teacher["surname"]." ".mb_substr($teacher['name'],0,1).'. '.mb_substr($teacher['patronymic'],0,1).'.',
+				'author_surname' => regen_surname,
+				'author_full' => regen_full,
+				'subject' => $subject,
+				'work_type' => $work_type,
+				'pages_count' => $pages_count,
+				'author_group' => regen_group,
+				'work_number' => $report['work_number'],
+				'teacher_surname' => $teacher['surname'],
+				'current_page_number' => $current_page_number,
+				'current_page_marker' => $current_page_marker,
+				'page_content' => $page_content
+			]);
+
+			$output .= $page_wrapped->render();
+
+			if ($this->isCountablePage($current_page_marker) != "!-") {
+				$current_page_number++;
+			}
+		}
+
+		// $output возвращаем
+		echo $output;
+	}
+
+	// Возвращает true если строка - это маркер страниц
+	private static function isPageMarker($line) {
+		return preg_match("/^(?:!|!!|!0|!-|!--)$/", $line);
+	}
+
+	// Возвращает true если строка - маркер страницы, которую можно включать в объём всех страниц
+	private static function isCountablePage($line) {	
+		return preg_match("/^(?:!|!!|!0|!-)$/", $line);
+	}
+}

+ 178 - 0
src/css/regen-report.css

@@ -0,0 +1,178 @@
+#markup {
+	display: flex;
+	justify-content: center;
+}
+
+#markuparea {
+	width: 95%;
+	height: 100%;
+	resize: vertical;
+}
+
+/*https://stackoverflow.com/questions/90178/make-a-div-fill-the-height-of-the-remaining-screen-space*/
+#previewdiv {
+	font-family: timesnewroman;
+	background-color: #383838;
+	color: #aaa;
+	font-size: 12pt;
+	margin:0;
+	height: 70vh;
+	overflow: scroll;
+}
+
+.preview span {
+	position: absolute;
+	font-size: 12px;
+	user-select: none;
+}
+
+/* Контейнер страницы */
+.preview .page-container {
+	padding: 16px;
+}
+
+/* Разделитель */
+.preview hr {
+	width: 50%;
+}
+
+/* Все страницы */
+.preview .page {
+	background-position:2cm 1cm;
+	background-size: 18.5cm 28.2cm;
+	box-sizing: border-box;
+	width: 21cm;
+	height: 29.7cm;
+	padding: 2cm 1.5cm 0cm 3cm;
+	background-repeat:no-repeat;
+	position: relative;
+	margin: auto;
+	overflow: hidden;
+	color:#333333;
+	background-color:#ddd;
+}
+
+/* Страницы с большой рамкой */
+.preview .page.big {
+	background-image: url(/img/regen/bigframe.gif);
+}
+
+/* Страницы с маленькой рамкой */
+.preview .page.small {
+	background-image: url(/img/regen/smallframe.gif);
+}
+
+/* Страницы без рамки */
+#previewdiv .page.tpage,
+#previewdiv .page.apage {
+	padding: 2cm
+}
+
+/* Заголовок (по центру) */
+.preview .page .title {
+	text-align:center;
+	text-align-last:center;
+	text-indent:0cm;
+}
+
+/* Текст по правому краю */
+.preview .page .r {
+	text-align:right;
+	text-align-last:right;
+	text-indent:0cm;
+}
+
+/* Обычный текст */
+.preview .page p {
+	line-height: 1.5;
+	padding: 0;
+	margin: 0;
+	text-align:justify;
+	text-align-last:left;
+	text-indent:1.25cm;
+}
+
+/* Заголовок таблицы */
+.preview .page .tt {
+	text-indent:0;
+}
+
+/* Изображение */
+.preview img {
+	max-width: 12cm;
+	margin:0.5cm auto 0.25cm auto;
+	display:block;
+	border:1px solid #000;
+}
+
+/* Таблицы */
+table, tr, td, th {border: 1px solid; border-collapse: collapse;}
+table {width:100%;}
+td {padding: 0 0.3cm;vertical-align:top;}
+th {text-align:center !important;font-weight:normal}
+
+/* Текст в рамках */
+.preview .iz::after {content: "Изм.";}
+.preview .ls::after,
+.preview .pl::after {content: "Лист";}
+.preview .nd::after {content: "№ докум.";}
+.preview .pd::after {content: "Подпись";}
+.preview .dt::after {content: "Дата";}
+.preview .rz::after {content: "Разраб.";}
+.preview .lt::after {content: "Лит.";}
+.preview .al::after {content: "Листов";}
+.preview .pr::after {content: "Провер.";}
+.preview .nc::after {content: "Н. Контр.";}
+.preview .ut::after {content: "Утверд.";}
+
+.preview .page.big .co {left:9.2cm; bottom: 3.5cm; right:0.6cm;font-size:22px;text-align:center;}
+.preview .page.big .iz {left:2.1cm;bottom:3.1cm;}
+.preview .page.big .ls {left:3.15cm;bottom:3.1cm;}
+.preview .page.big .nd {left:4.7cm;bottom:3.1cm;}
+.preview .page.big .pd {left:6.6cm;bottom:3.1cm;}
+.preview .page.big .dt {left:8.24cm;bottom:3.1cm;}
+.preview .page.big .rz {left:2.15cm;bottom:2.6cm;}
+.preview .page.big .sr {left:4.13cm; bottom:2.6cm;}
+.preview .page.big .lt {left:15.6cm;bottom:2.6cm;}
+.preview .page.big .pl {left:17.1cm;bottom:2.6cm;}
+.preview .page.big .al {left:18.8cm;bottom:2.6cm;}
+.preview .page.big .pr {left:2.15cm;bottom:2.1cm;}
+.preview .page.big .st {left:4.13cm; bottom:2.1cm;}
+.preview .page.big .cp {left:16.71cm;right:2.9cm;bottom:2.1cm;text-align:center;}
+.preview .page.big .pc {left:18.1cm;right:0.6cm;bottom:2.1cm;text-align:center;}
+.preview .page.big .nm {left:9.1cm;bottom:1.6cm;right:5.85cm;font-size:16px;text-align:center;}
+.preview .page.big .gr {left:15.25cm;right:0.6cm;bottom:1cm;text-align:center;font-size:20px;}
+.preview .page.big .nc {left:2.15cm;bottom:1.1cm;}
+.preview .page.big .ut {left:2.15cm;bottom:0.6cm;}
+
+.preview .page.small .iz {left:2.15cm;bottom:0.6cm;}
+.preview .page.small .ls {left:3.15cm;bottom:0.6cm;}
+.preview .page.small .nd {left:4.61cm;bottom:0.6cm;}
+.preview .page.small .pd {left:6.6cm;bottom:0.6cm;}
+.preview .page.small .dt {left:8.25cm;bottom:0.6cm;}
+.preview .page.small .co {left:9.1cm;bottom:1.0cm;right:1.5cm;font-size:22px;text-align:center;}
+.preview .page.small .pl {left:19.55cm;bottom:1.55cm;}
+.preview .page.small .cp {left:19.5cm;bottom:0.90cm;right:0.5cm;text-align:center;}
+
+.opt {display:block;text-align:center !important;font-size:18px;color:white;font-family:sans;margin: 8px 0px;user-select:none;}
+.opt:hover {color:#005BFF;}
+
+@media print {
+
+	#tabs,
+	#printReportButton,
+	nav,
+	#printpreview hr {
+		display: none;
+	}
+
+	#printpreview .page-container {
+		padding: 0;
+	}
+
+	#printpreview .page {
+		background-color:#fff;
+		color:#000
+	}
+
+}

BIN
src/img/regen/bigframe.gif


BIN
src/img/regen/smallframe.gif


+ 5 - 0
src/index.php

@@ -12,6 +12,11 @@ $router = new Router();
 $router->register('', ['HomeController', 'index']);
 $router->register('/grades', ['GradesController', 'index']);
 $router->register('/grades/get', ['GradesController', 'collect']);
+
+$router->register('/regen/new', ['RegenController', 'newReport']);
+$router->register('/regen/edit/\d+', ['RegenController', 'edit']);
+$router->register("/regen/gethtml", ['RegenController', 'getHtml']);
+
 $router->register404(['NotFoundController', 'index']);
 
 return $router->handle($_SERVER['REQUEST_URI']);

+ 46 - 0
src/js/regenpreview.js

@@ -0,0 +1,46 @@
+$(document).ready(function() {
+
+	$("#tabs").tabs({
+		beforeActivate: function(e, ui) {
+			if (ui.newPanel.attr('id') == 'preview') {
+				// Открывается вкладка просмотра превью
+				// Отправляем запрос на сервер для генерации HTML
+				writePreview("#previewdiv");
+			}
+		}
+	});
+
+	const elem = document.getElementById("markuparea");
+	textAreaAdjust(elem);
+	
+});
+
+// При печати текста заставляет textbox расширяться
+function textAreaAdjust(element) {
+  element.style.height = "1px";
+  element.style.height = (25+element.scrollHeight)+"px";
+}
+
+function writePreview(selector, then_print=false) {
+	const str = document.location.toString();
+	const slash_idx = str.lastIndexOf('/');
+	const report_id = str.substring(slash_idx + 1);
+	$.post(
+		"/regen/gethtml",
+		{
+			markup: $("#markuparea").val(),
+			report_id: report_id
+		},
+		function (data, textStatus, xhr) {
+			$(selector).html(data);
+			if (then_print) {
+				window.print();
+				$("#printpreview").html("");
+			}
+		}
+	);
+}
+
+function printReport() {
+	writePreview("#printpreview", true);
+}

+ 39 - 0
src/models/Model.php

@@ -0,0 +1,39 @@
+<?php
+class Model {
+	protected static $table_name;
+
+	// Возвращает одну запись, найденную по id
+	public static function getById($id) {
+		$db = Database::getConnection();
+		$stm = $db->prepare("SELECT * FROM ".static::$table_name." WHERE id=:id");
+		$stm->bindValue(":id", $id, SQLITE3_INTEGER);
+		return $stm->execute()->fetchArray();
+	}
+
+	// Собирает все записи из таблицы
+	public static function all() {
+		$db = Database::getConnection();
+		return $db->query("SELECT * FROM ".static::$table_name);
+	}
+
+	// Собирает записи по условию
+	public static function where($field, $value) {
+		$db = Database::getConnection();
+		$stm = $db->prepare("SELECT * FROM ".static::$table_name." WHERE $field=:$field");
+		if (!$stm) {
+			// Скорее всего такого поля нет
+			return false;
+		}
+		$stm->bindValue(":$field", $value);
+		return $stm->execute();
+	}
+
+	// Удаляет запись по ID
+	public static function delete($id) {
+		$db = Database::getConnection();
+		$stm = $db->prepare("DELETE FROM ".static::$table_name." WHERE id=:id");
+		$stm->bindValue(":id", $id);
+		echo $stm->getSQL(true);
+		$stm->execute();
+	}
+}

+ 17 - 0
src/models/ReportModel.php

@@ -0,0 +1,17 @@
+<?php
+class ReportModel extends Model {
+	protected static $table_name = "regen_reports";
+	
+	// Создаёт запись в таблице
+	public static function create($subject_id, $work_type, $number, $notice, $start_markup) {
+		$db = Database::getConnection();
+		$stm = $db->prepare("INSERT INTO ".static::$table_name." (subject_id, work_type, work_number, notice, date_create, markup) VALUES (:subject_id, :work_type, :work_number, :notice, datetime('now', 'localtime'), :markup)");
+		$stm->bindValue(":subject_id", $subject_id);
+		$stm->bindValue(":work_type", $work_type);
+		$stm->bindValue(":work_number", $number);
+		$stm->bindValue(":notice", $notice);
+		$stm->bindValue(":markup", $start_markup);
+		$stm->execute();
+		return $db->lastInsertRowID();
+	}
+}

+ 4 - 0
src/models/SubjectModel.php

@@ -0,0 +1,4 @@
+<?php
+class SubjectModel extends Model {
+	protected static $table_name = "regen_subjects";
+}

+ 4 - 0
src/models/TeacherModel.php

@@ -0,0 +1,4 @@
+<?php
+class TeacherModel extends Model {
+	protected static $table_name = "regen_teachers";
+}

+ 4 - 0
src/models/WorkTypeModel.php

@@ -0,0 +1,4 @@
+<?php
+class WorkTypeModel extends Model {
+	protected static $table_name = "regen_worktypes";
+}

+ 23 - 1
src/pockit.php

@@ -29,7 +29,7 @@ class Router {
 
 	// Выполняет маршрутизацию
 	public function handle(string $request_uri) {
-		if (preg_match('/^\/(?:css|fonts|img|doc|js)\//', $request_uri)) {
+		if (preg_match('/^\/(?:css|fonts|img|jquery|jqueryui|js)\//', $request_uri)) {
 			// Подача как есть
 			return false;
 		}
@@ -43,6 +43,7 @@ class Router {
 		}
 
 		// Маршрут не найден, вызываем 404!
+		header("HTTP/1.1 404 Not Found");
 		$this->invoke($this->not_found_handler, $request_uri);
 	}
 
@@ -53,4 +54,25 @@ class Router {
 		$h->$handle_method();
 		exit();
 	}
+}
+
+// Класс работы с БД
+class Database {
+    private static $db;
+    private $connection;
+    
+    private function __construct() {
+        $this->connection = new SQLite3(rootdir.'/db.sqlite3');
+    }
+
+    function __destruct() {
+        $this->connection->close();
+    }
+
+    public static function getConnection() {
+        if (self::$db == null) {
+            self::$db = new Database();
+        }
+        return self::$db->connection;
+    }
 }

+ 3 - 0
src/tools/database_up.php

@@ -0,0 +1,3 @@
+<?php
+
+$db = new SQLite3(__DIR__."/../database.sqlite3");

+ 1 - 0
src/views/GradesView.php

@@ -2,6 +2,7 @@
 // Оценки
 
 class GradesView extends LayoutView {
+
 	public function content():void { ?>
 
 <h1>Оценки</h1>

+ 25 - 7
src/views/LayoutView.php

@@ -3,12 +3,24 @@
 
 class LayoutView extends View {
 	protected $page_title;
-	protected $crumbs;
+	protected $crumbs = array();
 
-	protected function breadcrumbs() {
-		foreach ($this->crumbs as $crumb => $url) { ?>
-			<a href="<?= $url ?>"><?= $crumb ?></a>
-		<?php }
+	protected function breadcrumbs() { ?>
+<nav aria-label="breadcrumb">
+	<ol class="breadcrumb" style="--bs-breadcrumb-divider-color:var(--bs-light)">
+	<?php foreach ($this->crumbs as $crumb => $url) {
+		if ($crumb === array_key_last($this->crumbs)) { ?>
+			<li class="text-light breadcrumb-item"><?= $crumb ?></li>
+		<?php } else { ?>
+			<li class="breadcrumb-item active"><a href="<?= $url ?>"><?= $crumb ?></a></li>
+		<?php } ?>
+	<?php } ?>
+	</ol>
+</nav>
+<?php }
+
+	protected function customHead() {
+		
 	}
 	
 	public function view():void { ?>
@@ -19,11 +31,17 @@ class LayoutView extends View {
 		<meta charset="UTF-8">
 		<meta name="viewport" content="width=device-width, initial-scale=1.0">
 		<title><?= $this->page_title ?></title>
+		<link rel="stylesheet" href="/jqueryui/themes/base/jquery-ui.min.css">
+		<script src="/jquery/jquery.min.js"></script>
+		<script src="/jqueryui/jquery-ui.min.js"></script>
+
+		<?php $this->customHead() ?>
 	</head>
-	<body>
+	<body class="container bg-dark text-light">
 		<?php $this->breadcrumbs(); ?>
 		<?php $this->content(); ?>
 	</body>
 </html>
 
-<?php }}
+<?php }
+}

+ 34 - 0
src/views/RegenEditView.php

@@ -0,0 +1,34 @@
+<?php
+// Страница редактирования отчёта
+
+class RegenEditView extends LayoutView {
+	protected $markup;
+
+	public function customHead():void { ?>
+<link rel="stylesheet" href="/css/regen-report.css">
+<?php }
+
+	public function content():void { ?>
+
+<div id="tabs">
+	<ul>
+		<li><a href="#markup"><span>Разметка</span></a></li>
+		<li><a href="#preview"><span>Превью</span></a></li>
+	</ul>
+
+	<div id="markup">
+		<textarea onkeyup="textAreaAdjust(this)" id="markuparea" autocomplete="off"><?= $this->markup ?></textarea>
+	</div>
+
+	<div id="preview">
+		<div class='preview' id="previewdiv"></div>
+	</div>
+</div>
+
+<button id='printReportButton' onclick="printReport()">Печать</button>
+
+<div class='preview' id="printpreview"></div>
+ 
+<script src="/js/regenpreview.js"></script>
+<?php }
+}

+ 51 - 0
src/views/RegenNewReportView.php

@@ -0,0 +1,51 @@
+<?php
+// Страница создания отчёта
+
+class RegenNewReportView extends LayoutView {
+	protected $subjects;
+	protected $worktypes;
+	protected $error_text;
+
+	protected function content():void { ?>
+<h1 class='page-title'>Создание отчёта</h1>
+
+<?php if (isset($this->error_text)) {?>
+	<div class='card error'>
+		<?= $this->error_text ?>
+	</div>
+<?php } ?>
+
+<form action="/regen/new" method="POST">
+	<div class="card">
+		<div class="form-control-container">
+			<label for="sel-subject_id">Предмет</label>
+			<select class="form-control" id="sel-subject_id" name="subject_id">
+				<?php while ($row = $this->subjects->fetchArray()) { ?>
+					<option value="<?= $row['id'] ?>"><?= $row['name'] ?></option>
+				<?php } ?>
+			</select>
+		</div>
+
+		<div class="form-control-container">
+			<label for="sel-work_type">Тип работы</label>
+			<select class="form-control" id="sel-work_type" name="work_type">
+				<?php while ($row = $this->worktypes->fetchArray()) { ?>
+					<option value="<?= $row['id'] ?>"><?= $row['name_nom'] ?></option>
+				<?php } ?>
+			</select>
+		</div>
+
+		<div class="form-control-container">
+			<label for="inp-number">Номер работы</label>
+			<input class="form-control" id="inp-number" placeholder="Номер работы" type="text" name="number"/>
+		</div>
+		
+		<div class="form-control-container">
+			<label for="inp-notice">Комментарий</label>
+			<input class="form-control" id="inp-notice" placeholder="Всё что угодно" type="text" name="notice"/>
+		</div>
+
+		<input class="form-control" type="submit" value="Создать">
+	</div>
+</form>
+<?php }} ?>

Файловите разлики са ограничени, защото са твърде много
+ 90 - 0
src/views/RegenPageView.php


Някои файлове не бяха показани, защото твърде много файлове са промени