gitea init

This commit is contained in:
skryper
2025-10-08 21:28:30 +04:00
commit d4651a423d
2518 changed files with 522832 additions and 0 deletions
@@ -0,0 +1,26 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../../includes/init.php';
require_once __DIR__ . '/../../models/invoicesmodel.php';
require_once __DIR__ . '/../../../../../vendor/autoload.php';
InvoicesModel::setDb($pdo);
// ინვოისის ნომრის გენერაცია
$generatedInvoiceNumber = InvoicesModel::generateNextInvoiceNumber();
// კლიენტების სია dropdown-სთვის
$clients = InvoicesModel::getClientsList();
// პროდუქტების სია
$products = InvoicesModel::getProductList();
require_once __DIR__ . '/../../views/invoices/create.php';
?>
@@ -0,0 +1,29 @@
<?php
// კონტროლერი: ინვოისის წაშლა
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../../includes/init.php';
require_once __DIR__ . '/../../models/invoicesmodel.php';
require_once __DIR__ . '/../../../../../vendor/autoload.php';
use App\Config;
// ბაზასთან დაკავშირება
InvoicesModel::setDb($pdo);
// GET ID გადამოწმება
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
exit('არასწორი ID.');
}
$id = (int)$_GET['id'];
// წაშლა
InvoicesModel::deleteInvoice($id);
// გადამისამართება ინვოისების სიაზე
require_once __DIR__ . '/../../views/invoices/list.php';
@@ -0,0 +1,31 @@
<?php
// 📄 რედაქტირება
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../../includes/init.php';
require_once __DIR__ . '/../../models/invoicesmodel.php';
require_once __DIR__ . '/../../../../../vendor/autoload.php';
InvoicesModel::setDb($pdo);
// ID შემოწმება
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
exit('არასწორი ID.');
}
$id = (int)$_GET['id'];
// ინვოისის წამოღება
$invoice = InvoicesModel::getInvoiceById($id);
if (!$invoice) {
exit('ინვოისი ვერ მოიძებნა.');
}
// სელექტისთვის საჭირო მონაცემები
$clients = InvoicesModel::getClientsList();
$products = InvoicesModel::getProductList();
// ვიუ ჩატვირთვა
require_once __DIR__ . '/../../views/invoices/edit.php';
@@ -0,0 +1,54 @@
<?php
// 📄 ინვოისის PDF გენერაცია - Controller
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../../includes/init.php';
require_once __DIR__ . '/../../models/invoicesmodel.php';
require_once __DIR__ . '/../../../../vendor/autoload.php';
use Dompdf\Dompdf;
use Dompdf\Options;
use App\Config;
InvoicesModel::setDb($pdo);
// 🧾 ინვოისის ID გადმოსვლა GET-ით
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
exit('არასწორი ID');
}
$id = (int)$_GET['id'];
// 🧾 ინვოისის მონაცემების წამოღება
$invoice = InvoicesModel::getInvoiceById($id);
if (!$invoice) {
exit('ინვოისი ვერ მოიძებნა.');
}
$client_name = $invoice['first_name'] . ' ' . $invoice['last_name'];
// 📄 PDF კონფიგურაცია
$options = new Options();
$options->set('isHtml5ParserEnabled', true);
$options->set('isRemoteEnabled', true);
$dompdf = new Dompdf($options);
// 📄 შაბლონის HTML ჩატვირთვა
ob_start();
require_once realpath(Config::basePath() . '/admin/modules/billing/models/invoices/invoice_template.php');
$html = ob_get_clean();
$dompdf->loadHtml($html, 'UTF-8');
$dompdf->setPaper('A4', 'portrait');
$dompdf->render();
// 📄 PDF-ის შენახვა დროებით
$pdfOutput = $dompdf->output();
$pdfPath = '/tmp/invoice_' . $invoice['id'] . '.pdf';
file_put_contents($pdfPath, $pdfOutput);
// გადამისამართება ან ჩამოტვირთვა შეგიძლიათ დაამატოთ აქ
@@ -0,0 +1,19 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../../includes/init.php';
require_once __DIR__ . '/../../models/invoicesmodel.php';
require_once __DIR__ . '/../../../../../vendor/autoload.php';
use App\Config;
// ვუკავშირებთ ბაზას მოდელს
InvoicesModel::setDb($pdo);
// ინვოისების წამოღება
$invoices = InvoicesModel::getAllInvoices();
// ვიუს ჩატვირთვა
require_once __DIR__ . '/../../views/invoices/list.php';
@@ -0,0 +1,104 @@
<!-- გაგზავნის ლოგიკა -->
<?php
// ინვოისის გაგზავნა ელფოსტაზე PDF-ით
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../../includes/init.php';
require_once __DIR__ . '/../../models/invoicesmodel.php';
require_once __DIR__ . '/../../../../../vendor/autoload.php';
require_once __DIR__ . '/../../../../libs/phpmailer/src/PHPMailer.php';
require_once __DIR__ . '/../../../../libs/phpmailer/src/SMTP.php';
require_once __DIR__ . '/../../../../libs/phpmailer/src/Exception.php';
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
InvoicesModel::setDb($pdo);
// use Dompdf\Dompdf;
// use Dompdf\Options;
// $options = new Options();
// $options->set('isRemoteEnabled', true);
// // ️➡️ უთხარი Dompdf-ს სად არის შენი შრიფტი
// $options->setChroot(__DIR__ . '/../../'); // აქედან იმუშავებს relative path-ებით
// $options->set('defaultFont', 'bpg_glaho');
// $dompdf = new Dompdf($options);
InvoicesModel::setDb($pdo);
// 1. აიდი
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if (!$id) {
exit("არასწორი ინვოისის ID.");
}
// 2. მოიტანე ინვოისი და ნივთები
$invoice = InvoicesModel::getInvoiceWithItems($id);
if (!$invoice) {
exit("ინვოისი ვერ მოიძებნა.");
}
// 3. გენერაცია PDF ფაილის
$pdfPath = InvoicesModel::generateInvoicePDF($invoice);
// Email გაგზავნა
$mail = new PHPMailer(true);
try {
$mail->isSMTP();
$mail->Host = 'vps-7146dd3a.vps.ovh.ca'; // შეცვალე
$mail->SMTPAuth = true;
$mail->Username = 'noreply@selfhosting.ge'; // შეცვალე
$mail->Password = 'FSZtTIIIlubk'; // შეცვალე
$mail->SMTPSecure = 'ssl'; // ან ssl
$mail->Port = 465; // ან 465
$mail->setFrom('noreply@selfhosting.ge', 'ბილინგ სერვისი');
$mail->addAddress($invoice['email'], $invoice['first_name'] . ' ' . $invoice['last_name']);
$mail->CharSet = 'UTF-8'; // ✅ ეს არის მთავარი!
$mail->Encoding = 'base64'; // ხშირად დაეხმარება UTF-8-ის სწორ გადაცემას
$mail->isHTML(true);
$mail->Subject = "ინვოისი #" . $invoice['invoice_number'];
$mail->Body = "
გამარჯობა {$invoice['first_name']},<br><br>
თქვენთვის შემუშავებულია ახალი ინვოისი ჯამური თანხით <strong>{$invoice['total_amount']} ₾</strong>.<br>
გადახდის ვადა: {$invoice['due_date']}<br><br>
იხილეთ დეტალურად: ინვოისის სანახავად იხილეთ მიმაგრებული ფაილი<br><br>
მადლობა თანამშრომლობისთვის.
";
$invoice = InvoicesModel::getInvoiceWithClientById($id);
$invoice['company_name'] = $invoice['client_company_name'];
$invoice['vat_number'] = $invoice['client_vat_number'];
$invoice['address1'] = $invoice['client_address1'];
$client_name = $invoice['first_name'] . ' ' . $invoice['last_name'];
// ინვოისის HTML
$html = InvoicesModel::renderInvoiceHTML($invoice);
$mail->addAttachment($pdfPath, 'Invoice_' . $invoice['invoice_number'] . '.pdf');
$mail->send();
// ✅ წარმატებული გაგზავნის შემდეგ
header("Location: dashboard.php?module=billing&submodule=invoices&action=view&id={$invoice['id']}&sent=1");
exit;
} catch (Exception $e) {
echo "შეცდომა გაგზავნისას: {$mail->ErrorInfo}";
}
?>
@@ -0,0 +1,50 @@
<?php
// 📬 გადახდის შეხსენების გაგზავნა
ini_set('display_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../../includes/init.php';
require_once __DIR__ . '/../../models/invoicesmodel.php';
require '../../libs/phpmailer/src/PHPMailer.php';
require '../../libs/phpmailer/src/SMTP.php';
require '../../libs/phpmailer/src/Exception.php';
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
foreach ($invoices as $invoice) {
$mail = new PHPMailer(true);
try {
// SMTP პარამეტრები
$mail->isSMTP();
$mail->Host = 'vps-7146dd3a.vps.ovh.ca';
$mail->SMTPAuth = true;
$mail->Username = 'levan@arabuli.info';
$mail->Password = 'Aqsxcs@1211';
$mail->SMTPSecure = 'ssl';
$mail->Port = 465;
$clientName = $invoice['first_name'] . ' ' . $invoice['last_name'];
$mail->setFrom('levan@arabuli.info', 'Billing System');
$mail->addAddress($invoice['email'], $clientName);
$mail->CharSet = 'UTF-8';
$mail->isHTML(true);
$mail->Subject = "გადახდის შეხსენება ინვოისზე #{$invoice['invoice_number']}";
$mail->Body = "
გამარჯობა {$clientName},<br><br>
გთხოვთ გადაიხადოთ ინვოისი #{$invoice['invoice_number']} <strong>{$invoice['total_amount']} ₾</strong><br>
გადახდის ბოლო ვადაა: <strong>{$invoice['due_date']}</strong><br><br>
იხილეთ ინვოისი: <a href='https://yourdomain.com/dashboard.php?module=billing&submodule=invoices&action=view&id={$invoice['id']}'>იხილეთ ინვოისი</a>
";
$mail->send();
echo "✅ შეხსენება გაიგზავნა {$clientName} ({$invoice['email']})<br>";
} catch (Exception $e) {
echo "❌ შეცდომა: {$mail->ErrorInfo}<br>";
}
}
@@ -0,0 +1,36 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../../includes/init.php';
require_once __DIR__ . '/../../models/invoicesmodel.php';
InvoicesModel::setDb($pdo);
// POST მონაცემების დამუშავება
$data = $_POST;
$data['recurring'] = isset($data['recurring']) ? 1 : 0;
// სტატუსის ვალიდაცია
if (!InvoicesModel::isValidStatus($data['status'])) {
die('არასწორი სტატუსის მნიშვნელობა');
}
// 🔢 ინვოისის ნომრის გენერაცია
if (empty($data['invoice_number'])) {
$data['invoice_number'] = InvoicesModel::generateInvoiceNumber();
}
// დუბლიკატის შემოწმება
if (InvoicesModel::isDuplicateInvoiceNumber($data['invoice_number'])) {
die('ინვოისის ნომერი უკვე გამოიყენება!');
}
// ჩასმა მოდელის მეშვეობით
$invoice_id = InvoicesModel::createInvoiceWithItems($data);
// გადამისამართება
header("Location: dashboard.php?module=billing&submodule=invoices&action=view&id=" . $invoice_id);
exit;
@@ -0,0 +1,28 @@
<?php
// ⚙️ ინვოისის განახლება
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../../includes/init.php';
require_once __DIR__ . '/../../models/invoicesmodel.php';
require_once __DIR__ . '/../../../../../vendor/autoload.php';
InvoicesModel::setDb($pdo);
// 📨 მონაცემების მიღება
if (!isset($_POST['id']) || !is_numeric($_POST['id'])) {
exit('არასწორი ID.');
}
$id = (int)$_POST['id'];
$data = $_POST;
// ინვოისის განახლება
InvoicesModel::updateInvoice($id, $data);
// ↪️ გადამისამართება
header("Location: dashboard.php?module=billing&submodule=invoices&action=view&id=$id");
exit;
@@ -0,0 +1,37 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../../includes/init.php';
require_once __DIR__ . '/../../models/invoicesmodel.php';
require_once __DIR__ . '/../../../../../vendor/autoload.php';
use App\Config;
// ბაზასთან დაკავშირება
InvoicesModel::setDb($pdo);
// ID შემოწმება
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
echo "არასწორი ID.";
exit;
}
$id = (int) $_GET['id'];
$showAlert = isset($_GET['sent']) && $_GET['sent'] == 1;
// ინვოისის წამოღება
$invoice = InvoicesModel::getInvoiceWithClient($id);
if (!$invoice) {
echo "ინვოისი ვერ მოიძებნა.";
exit;
}
// პროდუქტის items
$productItems = InvoicesModel::getInvoiceItems($id);
// ვიუ ფაილის ჩატვირთვა
require_once __DIR__ . '/../../views/invoices/view.php';
@@ -0,0 +1,21 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../../includes/init.php';
require_once __DIR__ . '/../../models/transactionsmodel.php';
require_once __DIR__ . '/../../../../../vendor/autoload.php';
TransactionsModel::setDb($pdo);
// მომხმარებლები და ინვოისები ფორმის select-ებისთვის
$clients = TransactionsModel::getClients();
$invoices = TransactionsModel::getInvoices();
// POST დამუშავება
$errors = TransactionsModel::handleTransactionFormSubmission();
require_once __DIR__ . '/../../views/transactions/create.php';
@@ -0,0 +1,24 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../../includes/init.php';
require_once __DIR__ . '/../../models/transactionsmodel.php';
require_once __DIR__ . '/../../../../../vendor/autoload.php';
TransactionsModel::setDb($pdo);
// ID გადმოსვლის შემოწმება
$id = $_GET['id'] ?? null;
if (!$id || !is_numeric($id)) {
exit('არასწორი ID.');
}
// წაშლის მცდელობა
TransactionsModel::deleteTransaction((int)$id);
// გადამისამართება
header('Location: dashboard.php?module=billing&submodule=transactions&action=list&deleted=1');
exit;
@@ -0,0 +1,36 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../../includes/init.php';
require_once __DIR__ . '/../../models/transactionsmodel.php';
require_once __DIR__ . '/../../../../../vendor/autoload.php';
TransactionsModel::setDb($pdo);
$id = $_GET['id'] ?? null;
$transaction = TransactionsModel::getTransactionById($id);
if (!$transaction) {
echo "ტრანზაქცია ვერ მოიძებნა.";
exit;
}
// შენახვა
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = [
'status' => $_POST['status'],
'method' => $_POST['method'],
'notes' => $_POST['notes'],
];
TransactionsModel::updateTransaction($id, $data);
header("Location: dashboard.php?module=billing&submodule=transactions&action=list&updated=1");
exit;
}
require_once __DIR__ . '/../../views/transactions/edit.php';
@@ -0,0 +1,25 @@
<!-- გადახდების ისტორია -->
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../../includes/init.php';
require_once __DIR__ . '/../../models/transactionsmodel.php';
require_once __DIR__ . '/../../../../../vendor/autoload.php';
TransactionsModel::setDb($pdo);
$clientId = $_GET['client_id'] ?? null;
$client = TransactionsModel::getClientInfo($clientId);
if (!$client) {
echo "კლიენტი ვერ მოიძებნა.";
exit;
}
$transactions = TransactionsModel::getClientTransactions($clientId);
require_once __DIR__ . '/../../views/transactions/history.php';
@@ -0,0 +1,29 @@
<!-- ტრანზაქციების სია -->
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../../includes/init.php';
require_once __DIR__ . '/../../models/transactionsmodel.php';
require_once __DIR__ . '/../../../../../vendor/autoload.php';
// წაკითხვა GET-პარამეტრებიდან
$successMessage = null;
if (isset($_GET['added']) && $_GET['added'] == 1) {
$successMessage = "ტრანზაქცია წარმატებით დაემატა.";
} elseif (isset($_GET['updated']) && $_GET['updated'] == 1) {
$successMessage = "ტრანზაქცია წარმატებით განახლდა.";
} elseif (isset($_GET['deleted']) && $_GET['deleted'] == 1) {
$successMessage = "ტრანზაქცია წარმატებით წაიშალა.";
}
TransactionsModel::setDb($pdo);
$transactions = TransactionsModel::getAllTransactions();
require_once __DIR__ . '/../../views/transactions/list.php';
@@ -0,0 +1 @@
<!-- გენერირება, გამოთვლები -->
@@ -0,0 +1,2 @@
<!-- გენერირება, გამოთვლები -->
@@ -0,0 +1,323 @@
<?php
class InvoicesModel
{
protected static $db;
public static function setDb($pdo)
{
self::$db = $pdo;
}
// ყველა ინვოისის წამოღება
public static function getAllInvoicesWithClientNames()
{
$stmt = self::$db->query("
SELECT i.*, CONCAT(c.first_name, ' ', c.last_name) AS client_name
FROM invoices i
JOIN clients c ON i.client_id = c.id
ORDER BY i.id DESC
");
return $stmt->fetchAll();
}
// ყველა ინვოისის წამოღება
public static function getAllInvoices()
{
$stmt = self::$db->query("SELECT i.*, c.first_name, c.last_name FROM invoices i JOIN clients c ON i.client_id = c.id ORDER BY i.id DESC");
return $stmt->fetchAll();
}
// ერთი ინვოისის წამოღება დეტალურად
public static function getInvoiceById($id)
{
$stmt = self::$db->prepare("SELECT * FROM invoices WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch();
}
// ინვოისის დამატება
public static function createInvoice($data)
{
$stmt = self::$db->prepare("INSERT INTO invoices (invoice_number, client_id, description, payment_method, status, total_amount, is_recurring, issue_date, due_date, payment_date, recurring) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([
$data['invoice_number'],
$data['client_id'],
$data['description'],
$data['payment_method'],
$data['status'],
$data['total_amount'],
$data['is_recurring'],
$data['issue_date'],
$data['due_date'],
$data['payment_date'],
$data['recurring']
]);
return self::$db->lastInsertId();
}
// ინვოისის განახლება
public static function updateInvoice($id, $data)
{
$stmt = self::$db->prepare("UPDATE invoices SET
client_id = ?, invoice_number = ?, description = ?, status = ?,
total_amount = ?, payment_method = ?, issue_date = ?, due_date = ?, recurring = ?
WHERE id = ?");
return $stmt->execute([
$data['client_id'],
$data['invoice_number'],
$data['description'],
$data['status'],
$data['total_amount'],
$data['payment_method'],
$data['issue_date'],
$data['due_date'],
isset($data['recurring']) ? 1 : 0,
$id
]);
}
// ინვოისის წაშლა
public static function deleteInvoice($id)
{
$stmt = self::$db->prepare("DELETE FROM invoices WHERE id = ?");
return $stmt->execute([$id]);
}
// ინვოისთან დაკავშირებული ნივთების წამოღება
public static function getInvoiceItems2($invoice_id)
{
$stmt = self::$db->prepare("SELECT * FROM invoice_items WHERE invoice_id = ?");
$stmt->execute([$invoice_id]);
return $stmt->fetchAll();
}
// ინვოისის ნივთების დამატება
public static function addInvoiceItem($invoice_id, $item)
{
$stmt = self::$db->prepare("INSERT INTO invoice_items (invoice_id, product_id, description, amount) VALUES (?, ?, ?, ?)");
return $stmt->execute([
$invoice_id,
$item['product_id'],
$item['description'],
$item['amount']
]);
}
// ინვოისის ნივთების წაშლა
public static function deleteInvoiceItems($invoice_id)
{
$stmt = self::$db->prepare("DELETE FROM invoice_items WHERE invoice_id = ?");
return $stmt->execute([$invoice_id]);
}
public static function getInvoiceWithClient($id)
{
$stmt = self::$db->prepare("
SELECT i.*, c.first_name, c.last_name, c.email
FROM invoices i
JOIN clients c ON c.id = i.client_id
WHERE i.id = ?
");
$stmt->execute([$id]);
return $stmt->fetch();
}
public static function getInvoiceItems($invoiceId)
{
$stmt = self::$db->prepare("
SELECT ii.*, p.name
FROM invoice_items ii
JOIN products p ON p.id = ii.product_id
WHERE ii.invoice_id = ?
");
$stmt->execute([$invoiceId]);
return $stmt->fetchAll();
}
public static function generateNextInvoiceNumber(): string
{
$currentYear = date('Y');
$prefix = 'INV-' . $currentYear . '-';
$stmt = self::$db->prepare("SELECT invoice_number FROM invoices WHERE invoice_number LIKE ? ORDER BY invoice_number DESC LIMIT 1");
$stmt->execute([$prefix . '%']);
$lastInvoice = $stmt->fetchColumn();
if ($lastInvoice) {
$lastNumber = (int) substr($lastInvoice, strrpos($lastInvoice, '-') + 1);
$newNumber = str_pad($lastNumber + 1, 3, '0', STR_PAD_LEFT);
} else {
$newNumber = '001';
}
return $prefix . $newNumber;
}
public static function getClientsList(): array
{
$stmt = self::$db->query("SELECT id, first_name, last_name FROM clients ORDER BY first_name");
return $stmt->fetchAll();
}
public static function getProductList(): array
{
$stmt = self::$db->query("SELECT id, name, price FROM products ORDER BY name");
return $stmt->fetchAll();
}
public static function createInvoiceWithItems($data)
{
$stmt = self::$db->prepare("INSERT INTO invoices
(client_id, invoice_number, description, total_amount, payment_method, status, issue_date, due_date, recurring)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([
$data['client_id'],
$data['invoice_number'],
$data['description'],
$data['total_amount'],
$data['payment_method'],
$data['status'],
$data['issue_date'],
$data['due_date'],
$data['recurring']
]);
$invoice_id = self::$db->lastInsertId();
$stmtItem = self::$db->prepare("INSERT INTO invoice_items (invoice_id, product_id, amount, description) VALUES (?, ?, ?, ?)");
for ($i = 0; $i < count($data['products']); $i++) {
$stmtItem->execute([
$invoice_id,
$data['products'][$i],
$data['amounts'][$i],
$data['descriptions'][$i]
]);
}
return $invoice_id;
}
public static function generateInvoiceNumber(): string
{
$year = date('Y');
$prefix = 'INV-' . $year . '-';
$stmt = self::$db->prepare("SELECT invoice_number FROM invoices WHERE invoice_number LIKE ? ORDER BY invoice_number DESC LIMIT 1");
$stmt->execute([$prefix . '%']);
$last = $stmt->fetchColumn();
$lastNumber = $last ? (int) substr($last, strrpos($last, '-') + 1) : 0;
$newNumber = str_pad($lastNumber + 1, 3, '0', STR_PAD_LEFT);
return $prefix . $newNumber;
}
public static function isValidStatus(string $status): bool
{
$allowed = ['დრაფტი', 'გადაუხდელი', 'გადასახდელი', 'გადახდილი', 'გაუქმებული'];
return in_array($status, $allowed);
}
public static function isDuplicateInvoiceNumber(string $invoice_number): bool
{
$stmt = self::$db->prepare("SELECT COUNT(*) FROM invoices WHERE invoice_number = ?");
$stmt->execute([$invoice_number]);
return $stmt->fetchColumn() > 0;
}
public static function getInvoicesDueTomorrow()
{
$tomorrow = date('Y-m-d', strtotime('+1 day'));
$stmt = self::$db->prepare("
SELECT invoices.*, clients.first_name, clients.last_name, clients.email
FROM invoices
JOIN clients ON invoices.client_id = clients.id
WHERE invoices.due_date = ? AND invoices.status IN ('გადაუხდელი', 'გადასახდელი')
");
$stmt->execute([$tomorrow]);
return $stmt->fetchAll();
}
public static function getInvoiceWithItems($id)
{
$stmt = self::$db->prepare("
SELECT invoices.*, clients.email, clients.first_name, clients.last_name
FROM invoices
JOIN clients ON invoices.client_id = clients.id
WHERE invoices.id = ?
");
$stmt->execute([$id]);
$invoice = $stmt->fetch();
if (!$invoice) {
return null;
}
$stmtItems = self::$db->prepare("
SELECT products.name, ii.description, ii.amount
FROM invoice_items ii
JOIN products ON products.id = ii.product_id
WHERE ii.invoice_id = ?
");
$stmtItems->execute([$id]);
$invoice['items'] = $stmtItems->fetchAll(PDO::FETCH_ASSOC);
return $invoice;
}
public static function generateInvoicePDF($invoice)
{
$html = self::renderInvoiceHTML($invoice);
$options = new \Dompdf\Options();
$options->set('isRemoteEnabled', true);
$options->set('defaultFont', 'DejaVu Sans');
$dompdf = new \Dompdf\Dompdf($options);
$dompdf->loadHtml($html, 'UTF-8');
$dompdf->setPaper('A4', 'portrait');
$dompdf->render();
$pdfPath = '/tmp/invoice_' . $invoice['id'] . '.pdf';
file_put_contents($pdfPath, $dompdf->output());
return $pdfPath;
}
public static function renderInvoiceHTML($invoice)
{
ob_start();
include __DIR__ . '/../views/invoices/pdf_template.php'; // თუ გინდა გამოყავი ცალკე template ფაილად
return ob_get_clean();
}
// მოაქვს პდფ ფაილში კლიენტის ბაზიდან დამატებითი მონაცემები
public static function getInvoiceWithClientById($id)
{
$stmt = self::$db->prepare("
SELECT
invoices.*,
clients.company_name,
clients.vat_number,
clients.address1,
clients.first_name AS client_first_name,
clients.last_name AS client_last_name
FROM invoices
LEFT JOIN clients ON invoices.client_id = clients.id
WHERE invoices.id = ?
");
$stmt->execute([$id]);
return $stmt->fetch();
}
}
@@ -0,0 +1,130 @@
<?php
class TransactionsModel
{
private static $db;
public static function setDb($pdo)
{
self::$db = $pdo;
}
public static function getClients()
{
$stmt = self::$db->query("SELECT id, first_name, last_name FROM clients ORDER BY first_name ASC");
return $stmt->fetchAll();
}
public static function getInvoices()
{
$stmt = self::$db->query("SELECT id, invoice_number FROM invoices ORDER BY invoice_number ASC");
return $stmt->fetchAll();
}
public static function handleTransactionFormSubmission()
{
$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$invoice_id = $_POST['invoice_id'] ?? '';
$client_id = $_POST['client_id'] ?? '';
$amount = $_POST['amount'] ?? '';
$method = $_POST['method'] ?? '';
$status = $_POST['status'] ?? '';
$notes = $_POST['notes'] ?? '';
if (!$invoice_id || !$client_id || !$amount || !$method || !$status) {
$errors[] = "გთხოვ შეავსო ყველა სავალდებულო ველი.";
}
if (empty($errors)) {
$stmt = self::$db->prepare("INSERT INTO transactions (invoice_id, client_id, amount, method, status, notes) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$invoice_id, $client_id, $amount, $method, $status, $notes]);
header("Location: dashboard.php?module=billing&submodule=transactions&action=list&added=1");
exit;
}
}
return $errors;
}
public static function getTransactionById($id)
{
if (!$id || !is_numeric($id)) {
return null;
}
$stmt = self::$db->prepare("
SELECT t.*, c.first_name, c.last_name, i.invoice_number
FROM transactions t
JOIN clients c ON c.id = t.client_id
JOIN invoices i ON i.id = t.invoice_id
WHERE t.id = ?
");
$stmt->execute([$id]);
return $stmt->fetch();
}
public static function updateTransaction($id, $data)
{
$stmt = self::$db->prepare("
UPDATE transactions
SET status = ?, method = ?, notes = ?
WHERE id = ?
");
return $stmt->execute([
$data['status'],
trim($data['method']),
trim($data['notes']),
$id
]);
}
public static function getClientInfo($clientId)
{
if (!$clientId || !is_numeric($clientId)) {
return null;
}
$stmt = self::$db->prepare("SELECT first_name, last_name FROM clients WHERE id = ?");
$stmt->execute([$clientId]);
return $stmt->fetch();
}
public static function getClientTransactions($clientId)
{
if (!$clientId || !is_numeric($clientId)) {
return [];
}
$stmt = self::$db->prepare("
SELECT t.*, i.invoice_number
FROM transactions t
JOIN invoices i ON i.id = t.invoice_id
WHERE t.client_id = ?
ORDER BY t.created_at DESC
");
$stmt->execute([$clientId]);
return $stmt->fetchAll();
}
public static function getAllTransactions()
{
$stmt = self::$db->query("
SELECT t.*, c.first_name, c.last_name, i.invoice_number
FROM transactions t
JOIN clients c ON t.client_id = c.id
JOIN invoices i ON t.invoice_id = i.id
ORDER BY t.created_at DESC
");
return $stmt->fetchAll();
}
public static function deleteTransaction($id)
{
$stmt = self::$db->prepare("DELETE FROM transactions WHERE id = ?");
return $stmt->execute([$id]);
}
}
@@ -0,0 +1,143 @@
<?php
require_once __DIR__ . '/../../models/invoicesmodel.php';
use App\Config;
require_once Config::includePath('head.php');
require_once Config::includePath('navbar.php');
require_once Config::includePath('pageheader.php');
require_once Config::includePath('pagebodystart.php');
?>
<!-- CONTENT START -->
<div class="container-xl mt-4">
<h2 class="mb-4">ახალი ინვოისის დამატება</h2>
<form action="dashboard.php?module=billing&submodule=invoices&action=store" method="POST">
<div class="mb-3">
<label class="form-label">კლიენტი</label>
<select name="client_id" class="form-select" required>
<option value="">აირჩიე კლიენტი</option>
<?php foreach ($clients as $client): ?>
<option value="<?= $client['id'] ?>">
<?= htmlspecialchars($client['first_name'] . ' ' . $client['last_name']) ?>
</option>
<?php endforeach ?>
</select>
</div>
<div class="mb-4">
<label class="form-label">პროდუქტები</label>
<div id="product-container">
<div class="row product-item mb-2">
<div class="col-md-5">
<select name="products[]" class="form-select" required>
<option value="">აირჩიე პროდუქტი</option>
<?php foreach ($products as $product): ?>
<option value="<?= $product['id'] ?>">
<?= htmlspecialchars($product['name']) ?> (<?= $product['price'] ?> ₾)
</option>
<?php endforeach ?>
</select>
</div>
<div class="col-md-3">
<input type="number" name="amounts[]" step="0.01" class="form-control" placeholder="თანხა ₾" required>
</div>
<div class="col-md-3">
<input type="text" name="descriptions[]" class="form-control" placeholder="აღწერა (არასავალდებულო)">
</div>
<div class="col-md-1">
<button type="button" class="btn btn-danger btn-remove">-</button>
</div>
</div>
</div>
<button type="button" id="add-product" class="btn btn-secondary btn-sm mt-2">+ პროდუქტის დამატება</button>
</div>
<div class="mb-3">
<label class="form-label">ინვოისის ნომერი (არასავალდებულო)</label>
<input type="text" name="invoice_number" class="form-control"
placeholder="მაგ: INV-2025-001"
value="<?= htmlspecialchars($generatedInvoiceNumber) ?>" />
</div>
<div class="mb-3">
<label class="form-label">ინვოისის აღწერა</label>
<textarea name="description" class="form-control" rows="3"></textarea>
</div>
<div class="mb-3">
<label class="form-label">გადასახდელი თანხა (₾)</label>
<input type="number" step="0.01" name="total_amount" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">გადახდის მეთოდი</label>
<input type="text" name="payment_method" class="form-control" value="საბანკო გადმორიცხვა" required>
</div>
<div class="mb-3">
<label class="form-label">სტატუსი</label>
<select name="status" class="form-select" required>
<option value="დრაფტი">დრაფტი</option>
<option value="გადაუხდელი">გადაუხდელი</option>
<option value="გადასახდელი">გადასახდელი</option>
<option value="გადახდილი">გადახდილი</option>
<option value="გაუქმებული">გაუქმებული</option>
</select>
</div>
<div class="mb-3 row">
<div class="col">
<label class="form-label">ინვოისის თარიღი</label>
<input type="date" name="issue_date" class="form-control" value="<?= date('Y-m-d') ?>" required>
</div>
<div class="col">
<label class="form-label">გადახდის ბოლო ვადა</label>
<input type="date" name="due_date" class="form-control" required>
</div>
</div>
<div class="mb-3">
<label class="form-check">
<input type="checkbox" name="recurring" value="1" class="form-check-input">
<span class="form-check-label">გადახდა განმეორებით (ყოველთვიურად)</span>
</label>
</div>
<div class="mt-4">
<button type="submit" class="btn btn-primary">ინვოისის შექმნა</button>
<a href="list.php" class="btn btn-secondary">გაუქმება</a>
</div>
</form>
</div></div>
<?php require_once Config::includePath('footer.php'); ?>
<script>
document.getElementById('add-product').addEventListener('click', function () {
const container = document.getElementById('product-container');
const item = container.querySelector('.product-item');
const clone = item.cloneNode(true);
// reset values
clone.querySelectorAll('input, select').forEach(el => el.value = '');
container.appendChild(clone);
});
// remove button
document.addEventListener('click', function (e) {
if (e.target.classList.contains('btn-remove')) {
const allItems = document.querySelectorAll('.product-item');
if (allItems.length > 1) {
e.target.closest('.product-item').remove();
}
}
});
</script>
@@ -0,0 +1,90 @@
<?php
require_once __DIR__ . '/../../models/invoicesmodel.php';
use App\Config;
require_once Config::includePath('head.php');
require_once Config::includePath('navbar.php');
require_once Config::includePath('pageheader.php');
require_once Config::includePath('pagebodystart.php');
?>
<!-- CONTENT START -->
<div class="page-wrapper">
<div class="container-xl mt-4">
<h2 class="mb-4">ინვოისის რედაქტირება</h2>
<form action="dashboard.php?module=billing&submodule=invoices&action=update" method="POST">
<input type="hidden" name="id" value="<?= $invoice['id'] ?>">
<div class="mb-3">
<label class="form-label">კლიენტი</label>
<select name="client_id" class="form-select" required>
<?php foreach ($clients as $client): ?>
<option value="<?= $client['id'] ?>" <?= $invoice['client_id'] == $client['id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($client['first_name'] . ' ' . $client['last_name']) ?>
</option>
<?php endforeach ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">ინვოისის ნომერი</label>
<input type="text" name="invoice_number" class="form-control" value="<?= htmlspecialchars($invoice['invoice_number']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label">აღწერა</label>
<textarea name="description" class="form-control"><?= htmlspecialchars($invoice['description']) ?></textarea>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">გადახდის მეთოდი</label>
<input type="text" name="payment_method" class="form-control" value="<?= htmlspecialchars($invoice['payment_method']) ?>" required>
</div>
<div class="col-md-6">
<label class="form-label">სტატუსი</label>
<select name="status" class="form-select">
<?php foreach (['დრაფტი', 'გადაუხდელი', 'გადასახდელი', 'გადახდილი', 'გაუქმებული'] as $status): ?>
<option value="<?= $status ?>" <?= $invoice['status'] == $status ? 'selected' : '' ?>><?= $status ?></option>
<?php endforeach ?>
</select>
</div>
</div>
<div class="mb-3">
<label class="form-label">თანხა (₾)</label>
<input type="number" name="total_amount" class="form-control" step="0.01" value="<?= $invoice['total_amount'] ?>" required>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">ინვოისის თარიღი</label>
<input type="date" name="issue_date" class="form-control" value="<?= $invoice['issue_date'] ?>" required>
</div>
<div class="col-md-6">
<label class="form-label">გადახდის ბოლო ვადა</label>
<input type="date" name="due_date" class="form-control" value="<?= $invoice['due_date'] ?>" required>
</div>
</div>
<div class="mb-3">
<label class="form-check">
<input class="form-check-input" type="checkbox" name="recurring" value="1" <?= $invoice['recurring'] ? 'checked' : '' ?>>
<span class="form-check-label">გადახდა განმეორებით (ყოველთვიურად)</span>
</label>
</div>
<div class="mt-4">
<button type="submit" class="btn btn-primary">შენახვა</button>
<a href="dashboard.php?module=billing&submodule=invoices&action=view&id=<?= $invoice['id'] ?>" class="btn btn-secondary">უკან</a>
</div>
</form>
</div>
</div>
<?php require_once Config::includePath('footer.php'); ?>
@@ -0,0 +1,64 @@
<!-- ინვოისების სია + ფილტრები -->
<?php require_once __DIR__ . '/../../models/invoicesmodel.php'; ?>
<?php
use App\Config;
InvoicesModel::setDb($pdo);
$invoices = InvoicesModel::getAllInvoicesWithClientNames();
require_once Config::includePath('head.php');
require_once Config::includePath('navbar.php');
require_once Config::includePath('pageheader.php');
require_once Config::includePath('pagebodystart.php');
?>
<div class="container-xl mt-4">
<div class="d-flex justify-content-between mb-3">
<h2>ინვოისების სია</h2>
<a href="dashboard.php?module=billing&submodule=invoices&action=create" class="btn btn-primary">+ ახალი ინვოისი</a>
</div>
<div class="card">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>კლიენტი</th>
<th>თანხა</th>
<th>სტატუსი</th>
<th>თარიღი</th>
<th>ვადა</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($invoices as $invoice): ?>
<tr>
<td><?= $invoice['id'] ?></td>
<td><?= htmlspecialchars($invoice['client_name']) ?></td>
<td><?= number_format($invoice['total_amount'], 2) ?> ₾</td>
<td>
<span class="badge bg-light">
<?= htmlspecialchars($invoice['status']) ?>
</span>
</td>
<td><?= $invoice['issue_date'] ?></td>
<td><?= $invoice['due_date'] ?></td>
<td>
<a href="dashboard.php?module=billing&submodule=invoices&action=view&id=<?= $invoice['id'] ?>" class="btn btn-sm btn-info">ნახვა</a>
<a href="dashboard.php?module=billing&submodule=invoices&action=edit&id=<?= $invoice['id'] ?>" class="btn btn-sm btn-warning">რედაქტირება</a>
<a href="dashboard.php?module=billing&submodule=invoices&action=delete&id=<?= $invoice['id'] ?>" class="btn btn-sm btn-danger" onclick="return confirm('ნამდვილად გსურს წაშლა?')">წაშლა</a>
</td>
</tr>
<?php endforeach ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Content END -->
</div>
</div>
<?php require_once Config::includePath('footer.php'); ?>
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="ka">
<head>
<meta charset="UTF-8">
<title>ინვოისი</title>
<style>
@font-face {
font-family: DejaVu Sans;
}
body {
font-family: DejaVu Sans;
font-size: 14px;
color: #333;
}
.invoice-box {
max-width: 800px;
margin: auto;
padding: 30px;
border: 1px solid #eee;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
}
.top-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.top-header img {
max-width: 250px;
}
.bank-info {
text-align: right;
font-size: 13px;
line-height: 1.6;
}
.green-banner {
position: absolute;
top: 0;
right: 0;
background: #4CAF50;
color: white;
padding: 5px 20px;
transform: rotate(45deg);
transform-origin: top right;
font-size: 16px;
}
.section {
margin-top: 20px;
}
.gray-box {
background: #eee;
padding: 10px;
font-weight: bold;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
}
table, th, td {
border: 1px solid #ccc;
}
th, td {
padding: 10px;
text-align: left;
}
th {
background: #f9f9f9;
}
.footer {
margin-top: 30px;
font-size: 12px;
text-align: center;
color: #777;
}
</style>
</head>
<body>
<div class="invoice-box">
<!-- <div class="green-banner">გადახდილია</div> -->
<div class="top-header">
<div>
<h3>SELFHOSTING.GE</h3>
</div>
<div class="bank-info">
/ ლევან არაბული<br>
/: 01001080490<br><br>
ბანკი: თიბისი ბანკი<br>
/: GE79TB7902736010100047<br><br>
ბანკი: საქართველოს ბანკი<br>
/: GE85BG0000000534211842<br>
იდენტიფიკატორი: 01001080490
</div>
</div>
<div class="section gray-box">
ინვოისი #: <?= htmlspecialchars($invoice['invoice_number']) ?><br>
ინვოისის თარიღი: <?= htmlspecialchars($invoice['issue_date']) ?><br>
გადახდის თარიღი: <?= htmlspecialchars($invoice['due_date']) ?>
</div>
<div class="section">
<strong>მიმღები:</strong><br>
<?= htmlspecialchars($invoice['first_name'] . ' ' . $invoice['last_name']) ?><br>
<?php if (!empty($invoice['company_name'])): ?>
<?= htmlspecialchars($invoice['company_name']) ?><br>
<?php endif; ?>
<?php if (!empty($invoice['address1'])): ?>
<?= htmlspecialchars($invoice['address1']) ?><br>
<?php endif; ?>
<?php if (!empty($invoice['vat_number'])): ?>
VAT: <?= htmlspecialchars($invoice['vat_number']) ?>
<?php endif; ?>
</div>
<div class="section">
<table>
<thead>
<tr>
<th>დასახელება</th>
<th>აღწერა</th>
<th>თანხა</th>
</tr>
</thead>
<tbody>
<?php foreach ($invoice['items'] as $item): ?>
<tr>
<td><?= htmlspecialchars($item['name']) ?></td>
<td><?= htmlspecialchars($item['description']) ?></td>
<td><?= number_format($item['amount'], 2) ?> ლარი</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="section">
<h3>ტრანზაქციები</h3>
<table>
<thead>
<tr>
<th>ინვოისის ნომერი</th>
<th>გადახდის მეთოდი</th>
<th>ტრანზაქციის ID</th>
<th>თანხა</th>
</tr>
</thead>
<tbody>
<tr>
<td># <?= htmlspecialchars($invoice['invoice_number']) ?></td>
<td><?= htmlspecialchars($invoice['payment_method']) ?></td>
<td>-</td>
<td><?= number_format($invoice['total_amount'], 2) ?></td>
</tr>
<tr>
<td colspan="3">ბალანსი</td>
<td>0.00 GEL</td>
</tr>
</tbody>
</table>
</div>
<div class="footer">
Powered By Stack.ge | შექმნილია Stack.ge-ს მიერ
</div>
</div>
</body>
</html>
@@ -0,0 +1,69 @@
<!-- კონკრეტული ინვოისის ხილვა -->
<?php require_once __DIR__ . '/../../models/invoicesmodel.php'; ?>
<?php
use App\Config;
require_once Config::includePath('head.php');
require_once Config::includePath('navbar.php');
require_once Config::includePath('pageheader.php');
require_once Config::includePath('pagebodystart.php');
?>
<!-- CONTENT START -->
<?php if ($showAlert): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert" id="invoiceAlert">
✅ ინვოისი წარმატებით გაიგზავნა!
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<script>
setTimeout(() => {
const alertBox = document.getElementById('invoiceAlert');
if (alertBox) alertBox.remove();
}, 5000);
</script>
<?php endif; ?>
<div class="container-xl mt-4">
<h2>ინვოისი # <?= $invoice['invoice_number'] ? htmlspecialchars($invoice['invoice_number']) : '' ?></h2>
<p><strong>კლიენტი:</strong> <?= htmlspecialchars($invoice['first_name'] . ' ' . $invoice['last_name']) ?></p>
<p><strong>სტატუსი:</strong> <?= htmlspecialchars($invoice['status']) ?></p>
<p><strong>თარიღი:</strong> <?= $invoice['issue_date'] ?></p>
<p><strong>ბოლო ვადა:</strong> <?= $invoice['due_date'] ?></p>
<p><strong>გადასახდელი თანხა:</strong> <?= number_format($invoice['total_amount'], 2) ?> ₾</p>
<p><strong>გადახდის მეთოდი:</strong> <?= htmlspecialchars($invoice['payment_method']) ?></p>
<p><strong>აღწერა:</strong> <?= nl2br(htmlspecialchars($invoice['description'])) ?></p>
<?php if (!empty($productItems)): ?>
<h4 class="mt-4">პროდუქტები</h4>
<table class="table">
<thead>
<tr>
<th>პროდუქტი</th>
<th>თანხა</th>
<th>აღწერა</th>
</tr>
</thead>
<tbody>
<?php foreach ($productItems as $item): ?>
<tr>
<td><?= htmlspecialchars($item['name']) ?></td>
<td><?= number_format($item['amount'], 2) ?> ₾</td>
<td><?= htmlspecialchars($item['description']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<div class="mt-4">
<a href="dashboard.php?module=billing&submodule=invoices&action=send&id=<?= $invoice['id'] ?>" class="btn btn-primary">გაგზავნა</a>
<a href="dashboard.php?module=billing&submodule=invoices&action=edit&id=<?= $invoice['id'] ?>" class="btn btn-secondary">რედაქტირება</a>
<a href="dashboard.php?module=billing&submodule=invoices&action=delete&id=<?= $invoice['id'] ?>" class="btn btn-danger">წაშლა</a>
<a href="dashboard.php?module=billing&submodule=invoices&action=list" class="btn btn-light">უკან</a>
</div>
</div>
<?php require_once Config::includePath('footer.php'); ?>
@@ -0,0 +1,70 @@
<?php
require_once __DIR__ . '/../../models/transactionsmodel.php';
use App\Config;
require_once Config::includePath('head.php');
require_once Config::includePath('navbar.php');
require_once Config::includePath('pageheader.php');
require_once Config::includePath('pagebodystart.php');
?>
<div class="container-xl mt-4">
<h2>ტრანზაქციის დამატება</h2>
<?php if (!empty($errors)): ?>
<div class="alert alert-danger"><?= implode('<br>', $errors) ?></div>
<?php endif; ?>
<form method="post">
<div class="mb-3">
<label class="form-label">ინვოისი</label>
<select name="invoice_id" class="form-select" required>
<option value="">აირჩიე</option>
<?php foreach ($invoices as $inv): ?>
<option value="<?= $inv['id'] ?>"><?= htmlspecialchars($inv['invoice_number']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">კლიენტი</label>
<select name="client_id" class="form-select" required>
<option value="">აირჩიე</option>
<?php foreach ($clients as $cl): ?>
<option value="<?= $cl['id'] ?>"><?= htmlspecialchars($cl['first_name'] . ' ' . $cl['last_name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">თანხა</label>
<input type="number" name="amount" step="0.01" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">მეთოდი</label>
<input type="text" name="method" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">სტატუსი</label>
<select name="status" class="form-select" required>
<option value="success">წარმატებული</option>
<option value="failed">წარუმატებელი</option>
<option value="pending">მოლოდინში</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">შენიშვნა</label>
<textarea name="notes" class="form-control"></textarea>
</div>
<button type="submit" class="btn btn-success">შენახვა</button>
<a href="list.php" class="btn btn-secondary">გაუქმება</a>
</form>
</div>
<?php require_once Config::includePath('footer.php'); ?>
@@ -0,0 +1,50 @@
<?php
require_once __DIR__ . '/../../models/transactionsmodel.php';
use App\Config;
require_once Config::includePath('head.php');
require_once Config::includePath('navbar.php');
require_once Config::includePath('pageheader.php');
require_once Config::includePath('pagebodystart.php');
?>
<div class="container-xl mt-4">
<h2>ტრანზაქციის რედაქტირება</h2>
<form method="post">
<div class="mb-3">
<label class="form-label">ინვოისი</label>
<input type="text" class="form-control" value="#<?= $transaction['invoice_number'] ?>" disabled>
</div>
<div class="mb-3">
<label class="form-label">კლიენტი</label>
<input type="text" class="form-control" value="<?= $transaction['first_name'] . ' ' . $transaction['last_name'] ?>" disabled>
</div>
<div class="mb-3">
<label class="form-label">თანხა</label>
<input type="text" class="form-control" value="<?= number_format($transaction['amount'], 2) ?> ₾" disabled>
</div>
<div class="mb-3">
<label class="form-label">სტატუსი</label>
<select name="status" class="form-select" required>
<option value="success" <?= $transaction['status'] === 'success' ? 'selected' : '' ?>>დადასტურებული</option>
<option value="failed" <?= $transaction['status'] === 'failed' ? 'selected' : '' ?>>ჩავარდა</option>
<option value="pending" <?= $transaction['status'] === 'pending' ? 'selected' : '' ?>>მოლოდინში</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">გადახდის მეთოდი</label>
<input type="text" name="method" class="form-control" value="<?= htmlspecialchars($transaction['method']) ?>">
</div>
<div class="mb-3">
<label class="form-label">შენიშვნა</label>
<textarea name="notes" class="form-control"><?= htmlspecialchars($transaction['notes']) ?></textarea>
</div>
<button type="submit" class="btn btn-primary">შენახვა</button>
<a href="list.php" class="btn btn-secondary">უკან</a>
</form>
</div>
<?php require_once Config::includePath('footer.php'); ?>
@@ -0,0 +1,52 @@
<?php
require_once __DIR__ . '/../../models/transactionsmodel.php';
use App\Config;
require_once Config::includePath('head.php');
require_once Config::includePath('navbar.php');
require_once Config::includePath('pageheader.php');
require_once Config::includePath('pagebodystart.php');
?>
<div class="container-xl mt-4">
<h2>ტრანზაქციების ისტორია - <?= htmlspecialchars($client['first_name'] . ' ' . $client['last_name']) ?></h2>
<?php if (empty($transactions)): ?>
<div class="alert alert-info">ტრანზაქცია არ მოიძებნა.</div>
<?php else: ?>
<table class="table table-hover">
<thead>
<tr>
<th>ინვოისი</th>
<th>თანხა</th>
<th>მეთოდი</th>
<th>სტატუსი</th>
<th>შენიშვნა</th>
<th>თარიღი</th>
</tr>
</thead>
<tbody>
<?php foreach ($transactions as $t): ?>
<tr>
<td>#<?= htmlspecialchars($t['invoice_number']) ?></td>
<td><?= number_format($t['amount'], 2) ?> ₾</td>
<td><?= htmlspecialchars($t['method']) ?></td>
<td>
<span class="badge bg-<?= $t['status'] === 'success' ? 'success' : ($t['status'] === 'failed' ? 'danger' : 'warning') ?>">
<?= htmlspecialchars($t['status']) ?>
</span>
</td>
<td><?= nl2br(htmlspecialchars($t['notes'])) ?></td>
<td><?= $t['created_at'] ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<a href="/admin/clients/view.php?id=<?= $clientId ?>" class="btn btn-light">🔙 უკან კლიენტის პროფილში</a>
</div>
<?php require_once Config::includePath('footer.php'); ?>
@@ -0,0 +1,54 @@
<?php
require_once __DIR__ . '/../../models/transactionsmodel.php';
use App\Config;
require_once Config::includePath('head.php');
require_once Config::includePath('navbar.php');
require_once Config::includePath('pageheader.php');
require_once Config::includePath('pagebodystart.php');
?>
<?php if (!empty($successMessage)): ?>
<div class="alert alert-success">
<?= htmlspecialchars($successMessage) ?>
</div>
<?php endif; ?>
<div class="container-xl mt-4">
<h2>ტრანზაქციები</h2>
<a href="dashboard.php?module=billing&submodule=transactions&action=create" class="btn btn-primary mb-3"> დამატება</a>
<table class="table table-bordered">
<thead>
<tr>
<th>ინვოისი</th>
<th>კლიენტი</th>
<th>თანხა</th>
<th>მეთოდი</th>
<th>სტატუსი</th>
<th>თარიღი</th>
<th>ქმედებები</th>
</tr>
</thead>
<tbody>
<?php foreach ($transactions as $tx): ?>
<tr>
<td><?= htmlspecialchars($tx['invoice_number']) ?></td>
<td><?= htmlspecialchars($tx['first_name'] . ' ' . $tx['last_name']) ?></td>
<td><?= number_format($tx['amount'], 2) ?> ₾</td>
<td><?= htmlspecialchars($tx['method']) ?></td>
<td><?= htmlspecialchars($tx['status']) ?></td>
<td><?= $tx['created_at'] ?></td>
<td>
<a href="dashboard.php?module=billing&submodule=transactions&action=edit&id=<?= $tx['id'] ?>" class="btn btn-sm btn-warning">✏️</a>
<a href="dashboard.php?module=billing&submodule=transactions&action=delete&id=<?= $tx['id'] ?>" class="btn btn-sm btn-danger" onclick="return confirm('დარწმუნებული ხარ?')">🗑️</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php require_once Config::includePath('footer.php'); ?>
+39
View File
@@ -0,0 +1,39 @@
<?php
require_once __DIR__ . '/../../../includes/init.php'; // ბაზასთან კავშირი
require_once __DIR__ . '/../models/client.php'; // Client მოდელის ჩასმა
Client::setDb($pdo); // აქ ვუკავშირებთ PDO ობიექტს მოდელს
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// ყველა მონაცემის შეგროვება ერთი მასივში
$data = [
'first_name' => trim($_POST['first_name'] ?? ''),
'last_name' => trim($_POST['last_name'] ?? ''),
'company_name' => trim($_POST['company'] ?? null),
'vat_number' => trim($_POST['vat_number'] ?? null),
'email' => trim($_POST['email'] ?? ''),
'password' => password_hash($_POST['password'], PASSWORD_BCRYPT),
'address1' => trim($_POST['address1'] ?? null),
'address2' => trim($_POST['address2'] ?? null),
'city' => trim($_POST['city'] ?? null),
'state' => trim($_POST['state'] ?? null),
'postcode' => trim($_POST['postcode'] ?? null),
'country' => trim($_POST['country'] ?? null),
'phone' => trim($_POST['phone'] ?? null),
'payment_method' => trim($_POST['payment_method'] ?? null),
'billing_contact' => trim($_POST['billing_contact'] ?? null),
'currency' => trim($_POST['currency'] ?? 'USD'),
'language' => trim($_POST['language'] ?? 'default'),
'status' => trim($_POST['status'] ?? 'active'),
'client_group' => trim($_POST['client_group'] ?? 'none'),
'admin_notes' => trim($_POST['admin_notes'] ?? null),
];
// მონაცემის დამატება მოდელიდან
Client::create($data);
header("Location: dashboard.php?module=clients&action=list");
exit;
}
// view-ის ჩატვირთვა
require_once __DIR__ . '/../views/clients_add.php';
@@ -0,0 +1,15 @@
<?php
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../models/client.php';
Client::setDb($pdo); // ვუთითებთ PDO ობიექტს
if (isset($_GET['id']) && is_numeric($_GET['id'])) {
$id = (int)$_GET['id'];
// გადავცემთ ID-ს მოდელს წასაშლელად
Client::delete($id);
}
header("Location: dashboard.php?module=clients&action=list");
exit;
@@ -0,0 +1,45 @@
<?php
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../models/client.php';
// შემოწმება GET id-ზე
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
header("Location: dashboard.php?module=clients&action=list");
exit;
}
$id = (int)$_GET['id'];
// მომხმარებლის წამოღება
Client::setDb($pdo);
$client = Client::find($id);
if (!$client) {
echo "კლიენტი ვერ მოიძებნა.";
exit;
}
// თუ შენახვის ფორმა გამოიგზავნა
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = [
'first_name' => trim($_POST['first_name'] ?? ''),
'last_name' => trim($_POST['last_name'] ?? ''),
'company_name' => trim($_POST['company'] ?? null),
'email' => trim($_POST['email'] ?? ''),
'phone' => trim($_POST['phone'] ?? null),
'status' => trim($_POST['status'] ?? 'active'),
'client_group' => trim($_POST['client_group'] ?? 'none'),
'admin_notes' => trim($_POST['admin_notes'] ?? null),
];
Client::update($id, $data);
header("Location: dashboard.php?module=clients&action=profile&id=$id");
exit;
}
// view-ის ჩატვირთვა
require_once __DIR__ . '/../views/clients_edit.php';
@@ -0,0 +1,8 @@
<?php
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../models/client.php';
Client::setDb($pdo);
$clients = Client::all(); // მოდელიდან იღებ ყველა კლიენტს
include __DIR__ . '/../views/clients_list.php'; // აჩვენე View
@@ -0,0 +1,83 @@
<?php
require_once __DIR__ . '/../../../includes/init.php';
// ID გადმოწოდებულია URL-იდან
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
header("Location: clients_list.php");
exit;
}
$id = (int)$_GET['id'];
$stmt = $pdo->prepare("SELECT * FROM clients WHERE id = ?");
$stmt->execute([$id]);
$client = $stmt->fetch();
if (!$client) {
echo "კლიენტი ვერ მოიძებნა.";
exit;
}
// Ჩვენ ვიღებთ კლიენტის ID-ს
$client_id = $_GET['id'];
// ყველა ინვოისი ამ მომხმარებლისთვის
$stmt = $pdo->prepare("SELECT status, total_amount FROM invoices WHERE client_id = ?");
$stmt->execute([$client_id]);
$invoices = $stmt->fetchAll(PDO::FETCH_ASSOC);
// ინიციალიზაცია
$paid = $draft = $unpaid_due = $cancelled = $refunded = 0;
// გამოთვლა
foreach ($invoices as $inv) {
$amount = (float)$inv['total_amount'];
switch ($inv['status']) {
case 'გადახდილი':
$paid += $amount;
break;
case 'დრაფტი':
$draft += $amount;
break;
case 'გადაუხდელი':
case 'გადასახდელი':
$unpaid_due += $amount;
break;
case 'გაუქმებული':
$cancelled += $amount;
break;
case 'დაბრუნებული':
$refunded += $amount;
break;
}
}
$gross_revenue = $paid + $unpaid_due + $draft;
$net_income = $paid;
$credit_balance = 0.00; // შეგიძლია დაამატო ცალკე ცხრილიდან
$client_id = $_GET['id'];
// სერვისების მიღება
$stmt = $pdo->prepare("
SELECT p.name AS product_name, ii.amount, ii.description, i.issue_date
FROM invoice_items ii
JOIN invoices i ON ii.invoice_id = i.id
JOIN products p ON ii.product_id = p.id
WHERE i.client_id = ?
ORDER BY i.issue_date DESC
");
$stmt->execute([$client_id]);
$services = $stmt->fetchAll(PDO::FETCH_ASSOC);
require_once __DIR__ . '/../views/client_profile.php';
?>
+130
View File
@@ -0,0 +1,130 @@
<?php
class Client
{
// ➤ ბაზასთან კავშირის ცვლადი (გლობალური ან Dependency Injection)
protected static $db;
public static function setDb($pdo)
{
self::$db = $pdo;
}
// ➤ კლიენტის დამატება
public static function create($data)
{
$sql = "INSERT INTO clients (
first_name, last_name, company_name, vat_number, email, password,
address1, address2, city, state, postcode, country, phone,
payment_method, billing_contact, currency, language, status,
client_group, admin_notes
) VALUES (
:first_name, :last_name, :company_name, :vat_number, :email, :password,
:address1, :address2, :city, :state, :postcode, :country, :phone,
:payment_method, :billing_contact, :currency, :language, :status,
:client_group, :admin_notes
)";
$stmt = self::$db->prepare($sql);
$stmt->execute([
':first_name' => $data['first_name'],
':last_name' => $data['last_name'],
':company_name' => $data['company_name'] ?? null,
':vat_number' => $data['vat_number'] ?? null,
':email' => $data['email'],
':password' => $data['password'],
':address1' => $data['address1'] ?? null,
':address2' => $data['address2'] ?? null,
':city' => $data['city'] ?? null,
':state' => $data['state'] ?? null,
':postcode' => $data['postcode'] ?? null,
':country' => $data['country'] ?? null,
':phone' => $data['phone'] ?? null,
':payment_method' => $data['payment_method'] ?? null,
':billing_contact' => $data['billing_contact'] ?? null,
':currency' => $data['currency'] ?? 'USD',
':language' => $data['language'] ?? 'default',
':status' => $data['status'] ?? 'active',
':client_group' => $data['client_group'] ?? 'none',
':admin_notes' => $data['admin_notes'] ?? null
]);
}
// ➤ ყველა კლიენტის წამოღება
public static function all()
{
$stmt = self::$db->query("SELECT * FROM clients ORDER BY id DESC");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// ➤ ერთი კლიენტის წამოღება ID-ით
public static function find($id)
{
$stmt = self::$db->prepare("SELECT * FROM clients WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
// ➤ კლიენტის განახლება
public static function update($id, $data)
{
$sql = "UPDATE clients SET
first_name = :first_name,
last_name = :last_name,
company_name = :company_name,
vat_number = :vat_number,
email = :email,
address1 = :address1,
address2 = :address2,
city = :city,
state = :state,
postcode = :postcode,
country = :country,
phone = :phone,
payment_method = :payment_method,
billing_contact = :billing_contact,
currency = :currency,
language = :language,
status = :status,
client_group = :client_group,
admin_notes = :admin_notes
WHERE id = :id";
$stmt = self::$db->prepare($sql);
// აუცილებელია ყველა პარამეტრი იყოს განსაზღვრული
$params = [
':first_name' => $data['first_name'] ?? '',
':last_name' => $data['last_name'] ?? '',
':company_name' => $data['company_name'] ?? null,
':vat_number' => $data['vat_number'] ?? null,
':email' => $data['email'] ?? '',
':address1' => $data['address1'] ?? null,
':address2' => $data['address2'] ?? null,
':city' => $data['city'] ?? null,
':state' => $data['state'] ?? null,
':postcode' => $data['postcode'] ?? null,
':country' => $data['country'] ?? null,
':phone' => $data['phone'] ?? null,
':payment_method' => $data['payment_method'] ?? null,
':billing_contact' => $data['billing_contact'] ?? null,
':currency' => $data['currency'] ?? 'USD',
':language' => $data['language'] ?? 'default',
':status' => $data['status'] ?? 'active',
':client_group' => $data['client_group'] ?? 'none',
':admin_notes' => $data['admin_notes'] ?? null,
':id' => $id
];
$stmt->execute($params);
}
// ➤ კლიენტის წაშლა
public static function delete($id)
{
$stmt = self::$db->prepare("DELETE FROM clients WHERE id = ?");
$stmt->execute([$id]);
}
}
@@ -0,0 +1,110 @@
<?php
require_once __DIR__ . '/../../../../vendor/autoload.php';
use App\Config;
require_once Config::includePath('head.php');
require_once Config::includePath('navbar.php');
require_once Config::includePath('pageheader.php');
require_once Config::includePath('pagebodystart.php');
?>
<!-- CONTENT START -->
<div class="d-flex justify-content-between align-items-center mb-3">
<h2>კლიენტის პროფილი: <?= htmlspecialchars($client['first_name'] . ' ' . $client['last_name']) ?></h2>
<a href="dashboard.php?module=clients&action=list" class="btn btn-secondary">← უკან სიისკენ</a>
</div>
<div class="row row-cards">
<div class="col-md-6">
<div class="card">
<div class="card-header"><strong>Clients Information</strong></div>
<div class="card-body">
<p><strong>First Name:</strong> <?= htmlspecialchars($client['first_name']) ?></p>
<p><strong>Last Name:</strong> <?= htmlspecialchars($client['last_name']) ?></p>
<p><strong>Company:</strong> <?= htmlspecialchars($client['company_name']) ?></p>
<p><strong>Email:</strong> <?= htmlspecialchars($client['email']) ?></p>
<p><strong>Phone:</strong> <?= htmlspecialchars($client['phone']) ?></p>
<p><strong>Address 1:</strong> <?= htmlspecialchars($client['address1']) ?></p>
<p><strong>Address 2:</strong> <?= htmlspecialchars($client['address2']) ?></p>
<p><strong>City:</strong> <?= htmlspecialchars($client['city']) ?></p>
<p><strong>State/Region:</strong> <?= htmlspecialchars($client['state']) ?></p>
<p><strong>Postcode:</strong> <?= htmlspecialchars($client['postcode']) ?></p>
<p><strong>Country:</strong> <?= htmlspecialchars($client['country']) ?></p>
</div>
</div>
</div>
<?php
?>
<div class="col-md-6">
<div class="card">
<div class="card-header"><strong>Invoices / Billing</strong></div>
<div class="card-body">
<p><strong>Paid:</strong> ₾<?= number_format($paid, 2) ?></p>
<p><strong>Draft:</strong> ₾<?= number_format($draft, 2) ?></p>
<p><strong>Unpaid / Due:</strong> ₾<?= number_format($unpaid_due, 2) ?></p>
<p><strong>Cancelled:</strong> ₾<?= number_format($cancelled, 2) ?></p>
<p><strong>Refunded:</strong> ₾<?= number_format($refunded, 2) ?></p>
<hr>
<p><strong>Gross Revenue:</strong> ₾<?= number_format($gross_revenue, 2) ?></p>
<p><strong>Net Income:</strong> ₾<?= number_format($net_income, 2) ?></p>
<p><strong>Credit Balance:</strong> ₾<?= number_format($credit_balance, 2) ?></p>
<div class="mt-3">
<a href="#" class="btn btn-outline-primary btn-sm"> Create Invoice</a>
<a href="#" class="btn btn-outline-success btn-sm"> Add Funds Invoice</a>
<a href="#" class="btn btn-outline-info btn-sm">🔁 Generate Due Invoices</a>
<a href="#" class="btn btn-outline-warning btn-sm">💳 Manage Credits</a>
<a href="/admin/billing/transactions/history.php?client_id=<?= $client['id'] ?>" class="btn btn-outline-warning btn-sm">ტრანზაქციების ისტორია</a>
</div>
</div>
</div>
</div>
</div>
<div class="row row-cards mt-4">
<div class="col-md-6">
<div class="card">
<div class="card-header"><strong>Admin Notes</strong></div>
<div class="card-body">
<p><?= nl2br(htmlspecialchars($client['admin_notes'])) ?></p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header"><strong>Products / Services</strong></div>
<div class="card-body">
<?php if (count($services) > 0): ?>
<ul class="list-group list-group-flush">
<?php foreach ($services as $service): ?>
<li class="list-group-item">
<strong><?= htmlspecialchars($service['product_name']) ?></strong>
- <?= number_format($service['amount'], 2) ?> ₾
<br>
<small class="text-muted"> <?= htmlspecialchars($service['description']) ?> </small>
<br>
<small class="text-muted"> თარიღი: <?= htmlspecialchars($service['issue_date']) ?> </small>
</li>
<?php endforeach ?>
</ul>
<?php else: ?>
<p class="text-muted">სერვისები არ მოიძებნა.</p>
<?php endif ?>
</div>
</div>
</div>
</div>
<?php require_once Config::includePath('footer.php'); ?>
+173
View File
@@ -0,0 +1,173 @@
<?php
require_once __DIR__ . '/../../../../vendor/autoload.php';
use App\Config;
require_once Config::includePath('head.php');
require_once Config::includePath('navbar.php');
require_once Config::includePath('pageheader.php');
require_once Config::includePath('pagebodystart.php');
?>
<!-- CONTENT START -->
<h2 class="mb-4">ახალი კლიენტის დამატება</h2>
<form action="dashboard.php?module=clients&action=add" method="post">
<div class="row">
<!-- მარცხენა სვეტი -->
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">სახელი</label>
<input type="text" class="form-control" name="first_name" required>
</div>
<div class="mb-3">
<label class="form-label">გვარი</label>
<input type="text" class="form-control" name="last_name" required>
</div>
<div class="mb-3">
<label class="form-label">კომპანიის სახელი <span class="text-muted">(სურვილისამებრ)</span></label>
<input type="text" class="form-control" name="company">
</div>
<div class="mb-3">
<label class="form-label">საიდ. ნომ <span class="text-muted">(სურვილისამებრ)</span></label>
<input type="text" class="form-control" name="vat_number">
</div>
<div class="mb-3">
<label class="form-label">ელ-ფოსტა</label>
<input type="email" class="form-control" name="email" required>
</div>
<div class="mb-3">
<label class="form-label">პაროლი</label>
<div class="input-group">
<input type="text" class="form-control" id="password" name="password" required>
<button class="btn btn-outline-secondary" type="button" onclick="generatePassword()">გენერირება</button>
</div>
</div>
<div class="mb-3">
<label class="form-label">ენა</label>
<select class="form-select" name="language">
<option value="default">ნაგულისხმევი</option>
<option value="en">English</option>
<option value="ka">Georgian</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">სტატუსი</label>
<select class="form-select" name="status">
<option value="active">აქტიური</option>
<option value="inactive">გაუქმებული</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">ჯგუფი</label>
<select class="form-select" name="client_group">
<option value="none">ცარიელი</option>
<option value="vip">VIP</option>
<option value="reseller">Reseller</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Email შეტყობინებები</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="emails[]" value="general" checked>
<label class="form-check-label">General Emails</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="emails[]" value="invoice" checked>
<label class="form-check-label">Invoice Emails</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="emails[]" value="support">
<label class="form-check-label">Support Emails</label>
</div>
<!-- სხვა Checkbox-ები შეგიძლია დაამატო ასე -->
</div>
</div>
<!-- მარჯვენა სვეტი -->
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">მისამართი 1</label>
<input type="text" class="form-control" name="address1">
</div>
<div class="mb-3">
<label class="form-label">მისამართი 2</label>
<input type="text" class="form-control" name="address2">
</div>
<div class="mb-3">
<label class="form-label">ქალაქი</label>
<input type="text" class="form-control" name="city">
</div>
<div class="mb-3">
<label class="form-label">შტატი/რეგიონი</label>
<input type="text" class="form-control" name="state">
</div>
<div class="mb-3">
<label class="form-label">საფოსტო ინდექსი</label>
<input type="text" class="form-control" name="postcode">
</div>
<div class="mb-3">
<label class="form-label">ქვეყანა</label>
<select class="form-select" name="country">
<option value="US">United States</option>
<option value="GE">საქართველო</option>
<!-- სხვა ქვეყნები -->
</select>
</div>
<div class="mb-3">
<label class="form-label">ტელეფონი</label>
<input type="text" class="form-control" name="phone">
</div>
<div class="mb-3">
<label class="form-label">გადახდის მეთოდი</label>
<select class="form-select" name="payment_method">
<option value="default">Default</option>
<option value="paypal">PayPal</option>
<option value="bank">საბანკო გადმორიცხვა</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Billing Contact</label>
<select class="form-select" name="billing_contact">
<option value="default">Default</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">ვალუტა</label>
<select class="form-select" name="currency">
<option value="USD">USD</option>
<option value="GEL">GEL</option>
</select>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Admin ჩანაწერი</label>
<textarea class="form-control" name="admin_notes" rows="3"></textarea>
</div>
<div class="form-footer">
<button type="submit" class="btn btn-primary">დამატება</button>
</div>
</form>
</div>
</div>
<?php require_once Config::includePath('footer.php'); ?>
<script>
function generatePassword(length = 10) {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()";
let password = "";
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * charset.length);
password += charset[randomIndex];
}
document.getElementById('password').value = password;
}
</script>
@@ -0,0 +1,63 @@
<?php
require_once __DIR__ . '/../../../../vendor/autoload.php';
use App\Config;
require_once Config::includePath('head.php');
require_once Config::includePath('navbar.php');
require_once Config::includePath('pageheader.php');
require_once Config::includePath('pagebodystart.php');
?>
<h2 class="mb-4">კლიენტის რედაქტირება</h2>
<form action="dashboard.php?module=clients&action=edit" method="POST">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">First Name</label>
<input type="text" class="form-control" name="first_name" value="<?= htmlspecialchars($client['first_name']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label">Last Name</label>
<input type="text" class="form-control" name="last_name" value="<?= htmlspecialchars($client['last_name']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label">Company</label>
<input type="text" class="form-control" name="company" value="<?= htmlspecialchars($client['company_name']) ?>">
</div>
<div class="mb-3">
<label class="form-label">Email</label>
<input type="email" class="form-control" name="email" value="<?= htmlspecialchars($client['email']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label">Phone</label>
<input type="text" class="form-control" name="phone" value="<?= htmlspecialchars($client['phone']) ?>">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Status</label>
<select class="form-select" name="status">
<option value="active" <?= $client['status'] === 'active' ? 'selected' : '' ?>>Active</option>
<option value="inactive" <?= $client['status'] === 'inactive' ? 'selected' : '' ?>>Inactive</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Client Group</label>
<input type="text" class="form-control" name="client_group" value="<?= htmlspecialchars($client['client_group']) ?>">
</div>
<div class="mb-3">
<label class="form-label">Admin Notes</label>
<textarea class="form-control" name="admin_notes" rows="5"><?= htmlspecialchars($client['admin_notes']) ?></textarea>
</div>
</div>
</div>
<div class="form-footer">
<button type="submit" class="btn btn-primary">შენახვა</button>
<a href="dashboard.php?module=clients&action=profile&id=<?= $client['id'] ?>" class="btn btn-secondary">გაუქმება</a>
</div>
</form>
<?php require_once Config::includePath('footer.php'); ?>
@@ -0,0 +1,58 @@
<?php
require_once __DIR__ . '/../../../../vendor/autoload.php';
use App\Config;
require_once Config::includePath('head.php');
require_once Config::includePath('navbar.php');
require_once Config::includePath('pageheader.php');
require_once Config::includePath('pagebodystart.php');
?>
<!-- CONTENT START -->
<h2 class="mb-4">კლიენტების სია</h2>
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>კომპანია</th>
<th>ელ.ფოსტა</th>
<th>ტელეფონი</th>
<th>შექმნის თარიღი</th>
<th>სტატუს</th>
</tr>
</thead>
<tbody>
<?php foreach ($clients as $client): ?>
<tr>
<td><?= htmlspecialchars($client['id']) ?></td>
<td><?= htmlspecialchars($client['first_name']) ?></td>
<td><?= htmlspecialchars($client['last_name']) ?></td>
<td><?= htmlspecialchars($client['company_name']) ?></td>
<td><?= htmlspecialchars($client['email']) ?></td>
<td><?= htmlspecialchars($client['phone']) ?></td>
<td><?= date('d/m/Y', strtotime($client['created_at'])) ?></td>
<td>
<span class="badge bg-light">
<?= strtoupper($client['status']) ?>
</span>
</td>
<td>
<a href="dashboard.php?module=clients&action=profile&id=<?= $client['id'] ?>" class="btn btn-sm btn-info">პროფილი</a>
<a href="dashboard.php?module=clients&action=edit&id=<?= $client['id'] ?>" class="btn btn-sm btn-warning">რედ.</a>
<a href="dashboard.php?module=clients&action=delete&id=<?= $client['id'] ?>" class="btn btn-sm btn-danger" onclick="return confirm('ნამდვილად გსურს წაშლა?');">წაშლა</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php require_once Config::includePath('footer.php'); ?>
@@ -0,0 +1,25 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../models/marketingmodels.php';
MarketingModel::setDb($pdo);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$clientIds = $_POST['clients'] ?? [];
$subject = trim($_POST['subject'] ?? '');
$message = trim($_POST['message'] ?? '');
if (!empty($clientIds) && $subject && $message) {
$success = MarketingModel::sendBroadcast($clientIds, $subject, $message);
header("Location: dashboard.php?module=marketing&action=broadcast&sent=1");
exit;
}
}
// მხოლოდ GET მოთხოვნის დროს ჩაიტვირთოს ფორმა
require_once __DIR__ . '/../views/broadcast.php';
@@ -0,0 +1,12 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../models/marketingmodels.php';
MarketingModel::setDb($pdo);
$emailLogs = MarketingModel::getEmailLogs();
require_once __DIR__ . '/../views/email_logs.php';
@@ -0,0 +1,109 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
require_once __DIR__ . '/../../../libs/phpmailer/src/PHPMailer.php';
require_once __DIR__ . '/../../../libs/phpmailer/src/SMTP.php';
require_once __DIR__ . '/../../../libs/phpmailer/src/Exception.php';
class MarketingModel
{
protected static $db;
public static function setDb($pdo)
{
self::$db = $pdo;
}
/**
* SMTP კონფიგურაციის მიღება ბაზიდან
*/
private static function getSmtpConfig()
{
$stmt = self::$db->query("SELECT setting_key, setting_value FROM smtp_settings");
$settings = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
// Default ღირებულებები თუ ბაზაში არ არის
return array_merge([
'smtp_host' => 'localhost',
'smtp_port' => '587',
'smtp_secure' => 'tls',
'smtp_auth' => '1',
'smtp_username' => '',
'smtp_password' => '',
'smtp_from_email' => 'noreply@example.com',
'smtp_from_name' => 'Website',
'smtp_debug' => '0'
], $settings);
}
public static function sendBroadcast($clientIds, $subject, $message)
{
$stmt = self::$db->prepare(
"SELECT id, email, first_name, last_name
FROM clients
WHERE id IN (" . implode(',', array_fill(0, count($clientIds), '?')) . ")"
);
$stmt->execute($clientIds);
$clients = $stmt->fetchAll();
// SMTP კონფიგურაციის მიღება ბაზიდან
$smtpConfig = self::getSmtpConfig();
foreach ($clients as $client) {
$mail = new PHPMailer(true);
try {
$mail->isSMTP();
$mail->Host = $smtpConfig['smtp_host'];
$mail->SMTPAuth = (bool)$smtpConfig['smtp_auth'];
$mail->Username = $smtpConfig['smtp_username'];
$mail->Password = $smtpConfig['smtp_password'];
$mail->SMTPSecure = $smtpConfig['smtp_secure'];
$mail->Port = (int)$smtpConfig['smtp_port'];
$mail->SMTPDebug = (int)$smtpConfig['smtp_debug'];
$mail->Debugoutput = 'error_log';
$mail->setFrom($smtpConfig['smtp_from_email'], $smtpConfig['smtp_from_name']);
$mail->addAddress($client['email'], $client['first_name'] . ' ' . $client['last_name']);
$mail->CharSet = 'UTF-8';
$mail->isHTML(true);
$mail->Subject = $subject;
$mail->Body = nl2br(htmlspecialchars($message));
if ($mail->send()) {
$log = self::$db->prepare(
"INSERT INTO email_logs (client_id, subject, message, sent_at) VALUES (?, ?, ?, NOW())"
);
$log->execute([$client['id'], $subject, $message]);
} else {
error_log("გაგზავნის შეცდომა: " . $mail->ErrorInfo);
}
} catch (Exception $e) {
error_log("PHPMailer გამონაკლისი: " . $mail->ErrorInfo);
}
}
return true;
}
public static function getAllClients()
{
$stmt = self::$db->query("SELECT id, first_name, last_name, email FROM clients ORDER BY first_name");
return $stmt->fetchAll();
}
public static function getEmailLogs()
{
$stmt = self::$db->query("SELECT email_logs.*, clients.first_name, clients.last_name FROM email_logs
JOIN clients ON email_logs.client_id = clients.id
ORDER BY sent_at DESC");
return $stmt->fetchAll();
}
}
@@ -0,0 +1,57 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../../../../vendor/autoload.php';
use App\Config;
$success = isset($_GET['sent']) && $_GET['sent'] == 1;
// კლიენტების წამოღება
MarketingModel::setDb($pdo);
$clients = MarketingModel::getAllClients();
?>
<?php require_once Config::includePath('head.php'); ?>
<?php require_once Config::includePath('navbar.php'); ?>
<?php require_once Config::includePath('pageheader.php'); ?>
<?php require_once Config::includePath('pagebodystart.php'); ?>
<div class="container-xl mt-4">
<h2>ელ.ფოსტის გაგზავნა კლიენტებზე</h2>
<?php if ($success): ?>
<div class="alert alert-success">შეტყობინება წარმატებით გაიგზავნა!</div>
<?php endif; ?>
<form action="dashboard.php?module=marketing&action=broadcast" method="POST">
<div class="mb-3">
<label class="form-label">აირჩიე კლიენტები</label><br>
<?php foreach ($clients as $client): ?>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="clients[]" value="<?= $client['id'] ?>">
<label class="form-check-label"> <?= htmlspecialchars($client['first_name'] . ' ' . $client['last_name']) ?> </label>
</div>
<?php endforeach; ?>
</div>
<div class="mb-3">
<label class="form-label">თემა</label>
<input type="text" name="subject" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">შეტყობინება</label>
<textarea name="message" class="form-control" rows="5" required></textarea>
</div>
<button type="submit" class="btn btn-primary">გაგზავნა</button>
</form>
</div>
<?php require_once Config::includePath('footer.php'); ?>
@@ -0,0 +1,42 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../../../../vendor/autoload.php';
use App\Config;
?>
<?php require_once App\Config::includePath('head.php'); ?>
<?php require_once App\Config::includePath('navbar.php'); ?>
<?php require_once App\Config::includePath('pageheader.php'); ?>
<?php require_once App\Config::includePath('pagebodystart.php'); ?>
<div class="container-xl mt-4">
<h2>ელ.ფოსტის გაგზავნის ისტორია</h2>
<table class="table table-bordered">
<thead>
<tr>
<th>კლიენტი</th>
<th>თემა</th>
<th>შეტყობინება</th>
<th>გაგზავნის დრო</th>
</tr>
</thead>
<tbody>
<?php foreach ($emailLogs as $log): ?>
<tr>
<td><?= htmlspecialchars($log['first_name'] . ' ' . $log['last_name']) ?></td>
<td><?= htmlspecialchars($log['subject']) ?></td>
<td><?= nl2br(htmlspecialchars($log['message'])) ?></td>
<td><?= htmlspecialchars($log['sent_at']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php require_once App\Config::includePath('footer.php'); ?>
@@ -0,0 +1,16 @@
<?php
// controllers/create.php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../models/productmodels.php';
require_once __DIR__ . '/../../../../vendor/autoload.php';
// ბაზის კავშირის გადაცემა მოდელს
ProductModel::setDb($pdo);
// (დროებით არ გვჭირდება dropdown-ებისთვის ცალკე მონაცემები, მაგრამ თუ დაგჭირდება შემიძლია დავამატო)
// გადადი ფორმის ვიუ გვერდზე
require_once __DIR__ . '/../views/create.php';
@@ -0,0 +1,17 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../models/productmodels.php';
ProductModel::setDb($pdo);
if (isset($_GET['id']) && is_numeric($_GET['id'])) {
$id = (int) $_GET['id'];
ProductModel::deleteProductById($id);
}
header("Location: dashboard.php?module=product&action=list&deleted=1");
exit;
@@ -0,0 +1,27 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../models/productmodels.php';
require_once __DIR__ . '/../../../../vendor/autoload.php';
ProductModel::setDb($pdo);
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
echo "<div class='container-xl mt-4'><div class='alert alert-danger'>ID არ არის სწორი.</div></div>";
exit;
}
$id = (int) $_GET['id'];
$product = ProductModel::getProductById($id);
if (!$product) {
echo "<div class='container-xl mt-4'><div class='alert alert-danger'>პროდუქტი ვერ მოიძებნა.</div></div>";
exit;
}
$tab = $_GET['tab'] ?? 'details';
require_once __DIR__ . '/../views/edit.php';
@@ -0,0 +1,5 @@
<?php
// Product index controller - redirects to list
header("Location: dashboard.php?module=product&action=list");
exit;
?>
@@ -0,0 +1,16 @@
<?php
// controllers/list.php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../models/productmodels.php';
require_once __DIR__ . '/../../../../vendor/autoload.php';
ProductModel::setDb($pdo);
$products = ProductModel::getAllProducts();
require_once __DIR__ . '/../views/list.php';
@@ -0,0 +1,124 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../models/producttypesmodel.php';
ProductTypesModel::setDb($pdo);
$message = '';
$messageType = '';
$action = $_GET['subaction'] ?? 'list';
// POST მოთხოვნების მართვა
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$postAction = $_POST['action'] ?? '';
switch ($postAction) {
case 'add':
$data = [
'name' => trim($_POST['name'] ?? ''),
'description' => trim($_POST['description'] ?? ''),
'icon' => $_POST['icon'] ?? 'box',
'sort_order' => (int)($_POST['sort_order'] ?? 0),
'is_active' => isset($_POST['is_active']) ? 1 : 0
];
if (empty($data['name'])) {
$message = 'პროდუქტის ტიპის სახელი სავალდებულოა';
$messageType = 'danger';
} else {
try {
ProductTypesModel::addType($data);
$message = 'პროდუქტის ტიპი წარმატებით დაემატა';
$messageType = 'success';
$action = 'list';
} catch (Exception $e) {
$message = 'შეცდომა: ' . $e->getMessage();
$messageType = 'danger';
}
}
break;
case 'edit':
$id = (int)($_POST['id'] ?? 0);
$data = [
'name' => trim($_POST['name'] ?? ''),
'description' => trim($_POST['description'] ?? ''),
'icon' => $_POST['icon'] ?? 'box',
'sort_order' => (int)($_POST['sort_order'] ?? 0),
'is_active' => isset($_POST['is_active']) ? 1 : 0
];
if (empty($data['name']) || $id <= 0) {
$message = 'არასწორი მონაცემები';
$messageType = 'danger';
} else {
try {
ProductTypesModel::updateType($id, $data);
$message = 'პროდუქტის ტიპი წარმატებით განახლდა';
$messageType = 'success';
$action = 'list';
} catch (Exception $e) {
$message = 'შეცდომა: ' . $e->getMessage();
$messageType = 'danger';
}
}
break;
case 'delete':
$id = (int)($_POST['id'] ?? 0);
if ($id > 0) {
try {
ProductTypesModel::deleteType($id);
$message = 'პროდუქტის ტიპი წარმატებით წაიშალა';
$messageType = 'success';
} catch (Exception $e) {
$message = 'შეცდომა: ' . $e->getMessage();
$messageType = 'danger';
}
}
$action = 'list';
break;
case 'update_order':
$orders = $_POST['orders'] ?? [];
try {
ProductTypesModel::updateSortOrder($orders);
$message = 'რიგითობა წარმატებით განახლდა';
$messageType = 'success';
} catch (Exception $e) {
$message = 'შეცდომა: ' . $e->getMessage();
$messageType = 'danger';
}
$action = 'list';
break;
}
}
// მონაცემების მომზადება view-სთვის
switch ($action) {
case 'add':
case 'edit':
$availableIcons = ProductTypesModel::getAvailableIcons();
if ($action === 'edit') {
$editId = (int)($_GET['id'] ?? 0);
$editType = ProductTypesModel::getTypeById($editId);
if (!$editType) {
$message = 'პროდუქტის ტიპი ვერ მოიძებნა';
$messageType = 'danger';
$action = 'list';
}
}
break;
case 'list':
default:
$productTypes = ProductTypesModel::getAllTypes();
break;
}
require_once __DIR__ . '/../views/product_types.php';
?>
@@ -0,0 +1,33 @@
<?php
// controllers/save.php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../models/productmodels.php';
ProductModel::setDb($pdo);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$product = [
'name' => trim($_POST['name']),
'group' => trim($_POST['group']),
'type' => trim($_POST['type']),
'url' => trim($_POST['url']),
'module' => trim($_POST['module']),
'hidden' => isset($_POST['hidden']) ? 1 : 0
];
$success = ProductModel::createProduct($product);
if ($success) {
header("Location: dashboard.php?module=product&action=list&added=1");
} else {
header("Location: dashboard.php?module=product&action=create&error=1");
}
exit;
}
header("Location: dashboard.php?module=product&action=create");
exit;
@@ -0,0 +1,38 @@
<?php
// modules/product/controllers/update.php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../models/productmodels.php';
ProductModel::setDb($pdo);
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['id'])) {
$id = (int) $_POST['id'];
$data = [
'name' => trim($_POST['name']),
'group' => trim($_POST['group']),
'type' => trim($_POST['type']),
'url' => trim($_POST['url']),
'module' => trim($_POST['module']),
'hidden' => isset($_POST['hidden']) ? 1 : 0,
];
$success = ProductModel::updateProduct($id, $data);
if ($success) {
header("Location: dashboard.php?module=product&action=edit&id=$id&updated=1");
} else {
header("Location: dashboard.php?module=product&action=edit&id=$id&updated=0");
}
exit;
} else {
header("Location: dashboard.php?module=product&action=list");
exit;
}
@@ -0,0 +1,80 @@
<?php
class ProductModel
{
private static $db;
public static function setDb($pdo)
{
self::$db = $pdo;
}
/**
* პროდუქტის ტიპების მიღება (dynamic)
*/
public static function getProductTypes()
{
$stmt = self::$db->query("SELECT name, description, icon FROM product_types WHERE is_active = 1 ORDER BY sort_order, name");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public static function deleteProductById($id)
{
if (!$id || !is_numeric($id)) {
return false;
}
$stmt = self::$db->prepare("DELETE FROM products WHERE id = ?");
return $stmt->execute([$id]);
}
public static function getProductById($id)
{
if (!$id || !is_numeric($id)) {
return null;
}
$stmt = self::$db->prepare("SELECT * FROM products WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch();
}
public static function getAllProducts()
{
$stmt = self::$db->query("SELECT * FROM products ORDER BY id DESC");
return $stmt->fetchAll();
}
public static function createProduct($data)
{
$stmt = self::$db->prepare("INSERT INTO products (name, `group`, type, url, module, hidden) VALUES (?, ?, ?, ?, ?, ?)");
return $stmt->execute([
$data['name'],
$data['group'],
$data['type'],
$data['url'],
$data['module'],
$data['hidden']
]);
}
public static function updateProduct($id, $data)
{
$stmt = self::$db->prepare("
UPDATE products
SET name = ?, `group` = ?, type = ?, url = ?, module = ?, hidden = ?, updated_at = NOW()
WHERE id = ?
");
return $stmt->execute([
$data['name'],
$data['group'],
$data['type'],
$data['url'],
$data['module'],
$data['hidden'],
$id
]);
}
}
@@ -0,0 +1,148 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
class ProductTypesModel
{
protected static $db;
public static function setDb($pdo)
{
self::$db = $pdo;
}
/**
* ყველა აქტიური product type-ის მიღება
*/
public static function getActiveTypes()
{
$stmt = self::$db->query("SELECT * FROM product_types WHERE is_active = 1 ORDER BY sort_order, name");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* ყველა product type-ის მიღება (ადმინისთვის)
*/
public static function getAllTypes()
{
$stmt = self::$db->query("SELECT * FROM product_types ORDER BY sort_order, name");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* კონკრეტული product type-ის მიღება
*/
public static function getTypeById($id)
{
$stmt = self::$db->prepare("SELECT * FROM product_types WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
/**
* ახალი product type-ის დამატება
*/
public static function addType($data)
{
$stmt = self::$db->prepare("
INSERT INTO product_types (name, description, icon, sort_order, is_active)
VALUES (?, ?, ?, ?, ?)
");
return $stmt->execute([
$data['name'],
$data['description'] ?? '',
$data['icon'] ?? 'box',
$data['sort_order'] ?? 0,
$data['is_active'] ?? 1
]);
}
/**
* Product type-ის განახლება
*/
public static function updateType($id, $data)
{
$stmt = self::$db->prepare("
UPDATE product_types
SET name = ?, description = ?, icon = ?, sort_order = ?, is_active = ?, updated_at = NOW()
WHERE id = ?
");
return $stmt->execute([
$data['name'],
$data['description'] ?? '',
$data['icon'] ?? 'box',
$data['sort_order'] ?? 0,
$data['is_active'] ?? 1,
$id
]);
}
/**
* Product type-ის წაშლა
*/
public static function deleteType($id)
{
// ვამოწმებთ არ არის თუ არა გამოყენებული პროდუქტებში
$stmt = self::$db->prepare("SELECT COUNT(*) FROM products WHERE type = (SELECT name FROM product_types WHERE id = ?)");
$stmt->execute([$id]);
$count = $stmt->fetchColumn();
if ($count > 0) {
throw new Exception("ამ ტიპის პროდუქტები უკვე არსებობს. წაშლა შეუძლებელია.");
}
$stmt = self::$db->prepare("DELETE FROM product_types WHERE id = ?");
return $stmt->execute([$id]);
}
/**
* Sort order-ის განახლება
*/
public static function updateSortOrder($orders)
{
try {
self::$db->beginTransaction();
$stmt = self::$db->prepare("UPDATE product_types SET sort_order = ? WHERE id = ?");
foreach ($orders as $id => $order) {
$stmt->execute([$order, $id]);
}
self::$db->commit();
return true;
} catch (Exception $e) {
self::$db->rollback();
throw $e;
}
}
/**
* ხელმისაწვდომი აიკონების სია
*/
public static function getAvailableIcons()
{
return [
'server' => 'სერვერი',
'server-2' => 'VPS/Dedicated',
'users' => 'მომხმარებლები',
'world' => 'დომენი',
'shield-check' => 'უსაფრთხოება',
'mail' => 'ელ.ფოსტა',
'cloud' => 'ღრუბელი',
'database' => 'ბაზა',
'box' => 'პაკეტი',
'gift' => 'საჩუქარი',
'star' => 'პრემიუმი',
'lightning' => 'სწრაფი',
'globe' => 'გლობალური',
'lock' => 'დაცული',
'dots' => 'სხვა'
];
}
}
?>
+82
View File
@@ -0,0 +1,82 @@
<div class="container-xl mt-4">
<h2 class="mb-4">პროდუქტის რედაქტირება: <?= htmlspecialchars($product['name']) ?></h2>
<div class="tab-content pt-3">
<div class="tab-pane active" id="details">
<form method="POST" action="dashboard.php?module=product&action=update">
<input type="hidden" name="id" value="<?= $product['id'] ?>">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Product Type</label>
<input type="text" name="type" class="form-control" value="<?= htmlspecialchars($product['type']) ?>">
</div>
<div class="mb-3">
<label class="form-label">Product Group</label>
<input type="text" name="group" class="form-control" value="<?= htmlspecialchars($product['group']) ?>">
</div>
<div class="mb-3">
<label class="form-label">Product Name</label>
<input type="text" name="name" class="form-control" value="<?= htmlspecialchars($product['name']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label">Product Tagline</label>
<input type="text" name="tagline" class="form-control" value="<?= htmlspecialchars($product['tagline'] ?? '') ?>">
</div>
<div class="mb-3">
<label class="form-label">URL</label>
<input type="text" name="url" class="form-control" value="<?= htmlspecialchars($product['url']) ?>">
</div>
<div class="mb-3">
<label class="form-label">Short Description</label>
<textarea name="short_description" class="form-control"><?= htmlspecialchars($product['short_description'] ?? '') ?></textarea>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea name="description" class="form-control" rows="4"><?= htmlspecialchars($product['description'] ?? '') ?></textarea>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Product Color</label>
<input type="color" name="color" class="form-control form-control-color" value="<?= htmlspecialchars($product['color'] ?? '#ffffff') ?>">
</div>
<div class="mb-3">
<label class="form-label">Welcome Email</label>
<input type="text" name="welcome_email" class="form-control" value="<?= htmlspecialchars($product['welcome_email'] ?? '') ?>">
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" name="require_domain" value="1" id="requireDomain" <?= !empty($product['require_domain']) ? 'checked' : '' ?>>
<label class="form-check-label" for="requireDomain">Check to show domain registration options</label>
</div>
<div class="mb-3">
<label class="form-label">Stock Quantity</label>
<input type="number" name="stock_quantity" class="form-control" value="<?= htmlspecialchars($product['stock_quantity'] ?? 0) ?>">
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="apply_tax" value="1" id="applyTax" <?= !empty($product['apply_tax']) ? 'checked' : '' ?>>
<label class="form-check-label" for="applyTax">Check to charge tax for this product</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="featured" value="1" id="featured" <?= !empty($product['featured']) ? 'checked' : '' ?>>
<label class="form-check-label" for="featured">Display this product more prominently</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="hidden" value="1" id="hiddenSwitch" <?= !empty($product['hidden']) ? 'checked' : '' ?>>
<label class="form-check-label" for="hiddenSwitch">Check to hide from order form</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="retired" value="1" id="retired" <?= !empty($product['retired']) ? 'checked' : '' ?>>
<label class="form-check-label" for="retired">Check to hide from dropdown menus</label>
</div>
</div>
</div>
<div class="mt-4">
<button type="submit" class="btn btn-primary">შენახვა</button>
<a href="dashboard.php?module=product&action=list" class="btn btn-secondary">უკან</a>
</div>
</form>
</div>
+62
View File
@@ -0,0 +1,62 @@
<?php
$pageTitle = "პროდუქტის რედაქტირება";
$tab = $_GET['tab'] ?? 'details';
include '../../layout/header.php';
?>
<div class="page-body">
<div class="container-xl">
<div class="card">
<div class="card-body">
<?php if ($tab === 'details') : ?>
<?php include 'tabs/details.php'; ?>
<?php elseif ($tab === 'pricing') : ?>
<h3>Pricing</h3>
<form>
<div class="mb-3">
<label class="form-label">Payment Type</label>
<div>
<label class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="payment_type" value="free"> Free
</label>
<label class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="payment_type" value="onetime"> One Time
</label>
<label class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="payment_type" value="recurring" checked> Recurring
</label>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th>Currency</th>
<th>Setup Fee</th>
<th>Price</th>
<th>Enable</th>
</tr>
</thead>
<tbody>
<tr>
<td>GEL</td>
<td><input type="text" class="form-control" value="0.00"></td>
<td><input type="text" class="form-control" value="70.00"></td>
<td><input type="checkbox" checked></td>
</tr>
<tr>
<td>USD</td>
<td><input type="text" class="form-control" value="0.00"></td>
<td><input type="text" class="form-control" value="29.00"></td>
<td><input type="checkbox" checked></td>
</tr>
</tbody>
</table>
</div>
</form>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php include '../../layout/footer.php'; ?>
@@ -0,0 +1,45 @@
<?php
require_once __DIR__ . '/includes/init.php';
?>
<?php
require_once __DIR__ . '/../vendor/autoload.php';
use App\Config;
require_once Config::includePath('head.php');
require_once Config::includePath('navbar.php');
require_once Config::includePath('pageheader.php');
require_once Config::includePath('pagebodystart.php');
?>
<?php
require 'db.php';
$action = $_GET['action'] ?? 'list';
$id = $_GET['id'] ?? null;
switch ($action) {
case 'create':
include __DIR__ . '/product/create.php';
break;
case 'edit':
if ($id) {
include __DIR__ . '/product/edit.php';
} else {
echo "<div class='container-xl mt-4'><div class='alert alert-danger'>ID ვერ მოიძებნა.</div></div>";
}
break;
case 'list':
default:
include __DIR__ . '/product/list.php';
break;
}
?>
<?php require_once Config::includePath('footer.php'); ?>
+122
View File
@@ -0,0 +1,122 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../models/productmodels.php';
use App\Config;
// Product types-ების მიღება
ProductModel::setDb($pdo);
$productTypes = ProductModel::getProductTypes();
?>
<?php require_once Config::includePath('head.php'); ?>
<?php require_once Config::includePath('navbar.php'); ?>
<?php require_once Config::includePath('pageheader.php'); ?>
<?php require_once Config::includePath('pagebodystart.php'); ?>
<div class="container-xl mt-4">
<h2 class="mb-4">ახალი პროდუქტის დამატება</h2>
<form method="POST" action="dashboard.php?module=product&action=save">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Product Type</label>
<div class="row">
<div class="col">
<select name="type" class="form-select" id="product-type-select" required>
<option value="">აირჩიეთ ტიპი...</option>
<?php foreach ($productTypes as $type): ?>
<option value="<?= htmlspecialchars($type['name']) ?>" data-icon="<?= htmlspecialchars($type['icon']) ?>">
<?= htmlspecialchars($type['name']) ?>
<?php if ($type['description']): ?>
- <?= htmlspecialchars($type['description']) ?>
<?php endif; ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-auto">
<a href="dashboard.php?module=product&action=types" class="btn btn-outline-secondary" title="ტიპების მართვა">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<path d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 0 0-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 0 0-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 0 0-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 0 0-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 0 0 1.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0"/>
</svg>
მართვა
</a>
</div>
</div>
<div class="mt-2" id="type-preview" style="display: none;">
<div class="d-flex align-items-center text-muted">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon me-2" id="type-icon">
</svg>
<span id="type-description"></span>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Product Group</label>
<input type="text" name="group" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">დასახელება</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">URL</label>
<input type="text" name="url" class="form-control">
</div>
<div class="mb-3">
<label class="form-label">მოდული</label>
<input type="text" name="module" class="form-control">
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" name="hidden" value="1" id="hiddenSwitch">
<label class="form-check-label" for="hiddenSwitch">შექმნა დამალულად</label>
</div>
</div>
</div>
<div class="form-footer mt-3">
<button type="submit" class="btn btn-primary">შენახვა</button>
<a href="dashboard.php?module=product&action=list" class="btn btn-secondary">გაუქმება</a>
</div>
</form>
</div>
<script>
// Product type selection preview
document.addEventListener('DOMContentLoaded', function() {
const typeSelect = document.getElementById('product-type-select');
const typePreview = document.getElementById('type-preview');
const typeIcon = document.getElementById('type-icon');
const typeDescription = document.getElementById('type-description');
typeSelect.addEventListener('change', function() {
const selectedOption = this.options[this.selectedIndex];
if (this.value) {
const iconName = selectedOption.getAttribute('data-icon') || 'box';
const description = selectedOption.text.split(' - ')[1] || '';
// Update icon
typeIcon.className = `icon me-2 icon-tabler icons-tabler-outline icon-tabler-${iconName}`;
// Update description
if (description) {
typeDescription.textContent = description;
typePreview.style.display = 'block';
} else {
typePreview.style.display = 'none';
}
} else {
typePreview.style.display = 'none';
}
});
});
</script>
<?php require_once Config::includePath('footer.php'); ?>
+33
View File
@@ -0,0 +1,33 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../models/productmodels.php';
use App\Config;
?>
<?php require_once Config::includePath('head.php'); ?>
<?php require_once Config::includePath('navbar.php'); ?>
<?php require_once Config::includePath('pageheader.php'); ?>
<?php require_once Config::includePath('pagebodystart.php'); ?>
<div class="tab-content mt-4">
<?php
$allowedTabs = ['details', 'pricing'];
if (!in_array($tab, $allowedTabs)) {
$tab = 'details';
}
$tabPath = __DIR__ . '/../tabs/' . $tab . '.php';
if (file_exists($tabPath)) {
include $tabPath;
} else {
echo "<div class='alert alert-danger'>ტაბის ფაილი ვერ მოიძებნა.</div>";
}
?>
</div>
+75
View File
@@ -0,0 +1,75 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../models/productmodels.php';
use App\Config;
?>
<?php require_once Config::includePath('head.php'); ?>
<?php require_once Config::includePath('navbar.php'); ?>
<?php require_once Config::includePath('pageheader.php'); ?>
<?php require_once Config::includePath('pagebodystart.php'); ?>
<?php if (isset($_GET['added']) && $_GET['added'] == 1): ?>
<div class="alert alert-success">პროდუქტი წარმატებით დაემატა.</div>
<?php endif; ?>
<div class="container-xl mt-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2>პროდუქტები და სერვისები</h2>
<div class="btn-list">
<a href="dashboard.php?module=product&action=types" class="btn btn-outline-secondary">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<rect x="3" y="3" width="7" height="7"/>
<rect x="14" y="3" width="7" height="7"/>
<rect x="14" y="14" width="7" height="7"/>
<rect x="3" y="14" width="7" height="7"/>
</svg>
ტიპების მართვა
</a>
<a href="dashboard.php?module=product&action=create" class="btn btn-primary">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<path d="M12 5l0 14"/>
<path d="M5 12l14 0"/>
</svg>
ახალი პროდუქტი
</a>
</div>
</div>
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Product Name</th>
<th>Group</th>
<th>Type</th>
<th>Pay Type</th>
<th>Auto Setup</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($products as $product): ?>
<tr>
<td><?= htmlspecialchars($product['id'] ?? '') ?></td>
<td><?= htmlspecialchars($product['name'] ?? '') ?></td>
<td><?= htmlspecialchars($product['group'] ?? '') ?></td>
<td><?= htmlspecialchars($product['type'] ?? '') ?></td>
<td><?= htmlspecialchars($product['pay_type'] ?? '') ?></td>
<td><?= htmlspecialchars($product['auto_setup'] ?? '') ?></td>
<td>
<a href="dashboard.php?module=product&action=edit&id=<?= $product['id'] ?>" class="btn btn-sm btn-warning">რედ.</a>
<a href="dashboard.php?module=product&action=delete&id=<?= $product['id'] ?>" class="btn btn-sm btn-danger" onclick="return confirm('წაშლა?');">წაშლა</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php require_once Config::includePath('footer.php'); ?>
@@ -0,0 +1,313 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../../../../vendor/autoload.php';
use App\Config;
?>
<?php require_once Config::includePath('head.php'); ?>
<?php require_once Config::includePath('navbar.php'); ?>
<?php require_once Config::includePath('pageheader.php'); ?>
<?php require_once Config::includePath('pagebodystart.php'); ?>
<div class="container-xl mt-4">
<!-- Page header -->
<div class="page-header d-print-none">
<div class="row align-items-center">
<div class="col">
<h2 class="page-title">პროდუქტის ტიპების მართვა</h2>
<div class="text-muted mt-1">პროდუქტების კატეგორიების კონფიგურაცია</div>
</div>
<div class="col-auto">
<div class="btn-list">
<?php if ($action === 'list'): ?>
<a href="dashboard.php?module=product&action=types&subaction=add" class="btn btn-primary">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<path d="M12 5l0 14"/>
<path d="M5 12l14 0"/>
</svg>
ახალი ტიპი
</a>
<?php endif; ?>
<a href="dashboard.php?module=product&action=list" class="btn btn-outline-primary">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<path d="M5 12l14 0"/>
<path d="M5 12l6 6"/>
<path d="M5 12l6 -6"/>
</svg>
უკან დაბრუნება
</a>
</div>
</div>
</div>
</div>
<!-- Messages -->
<?php if ($message): ?>
<div class="alert alert-<?= $messageType ?> alert-dismissible">
<div class="d-flex">
<div><?= htmlspecialchars($message) ?></div>
</div>
<a class="btn-close" data-bs-dismiss="alert" aria-label="close"></a>
</div>
<?php endif; ?>
<?php if ($action === 'list'): ?>
<!-- Product Types List -->
<div class="card">
<div class="card-header">
<h3 class="card-title">პროდუქტის ტიპები</h3>
<div class="card-actions">
<small class="text-muted">გადაათრიე რიგითობის ცვლილებისთვის</small>
</div>
</div>
<div class="card-body">
<?php if (empty($productTypes)): ?>
<div class="text-center text-muted py-4">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon mb-3">
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
<polyline points="3.27,6.96 12,12.01 20.73,6.96"/>
<line x1="12" y1="22.08" x2="12" y2="12"/>
</svg>
<h3>პროდუქტის ტიპები არ არის</h3>
<p class="text-muted">დაამატეთ ახალი პროდუქტის ტიპი</p>
</div>
<?php else: ?>
<div id="sortable-types" class="list-group list-group-flush">
<?php foreach ($productTypes as $type): ?>
<div class="list-group-item" data-id="<?= $type['id'] ?>">
<div class="row align-items-center">
<div class="col-auto">
<span class="handle cursor-move">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon text-muted">
<line x1="8" y1="6" x2="21" y2="6"/>
<line x1="8" y1="12" x2="21" y2="12"/>
<line x1="8" y1="18" x2="21" y2="18"/>
<line x1="3" y1="6" x2="3.01" y2="6"/>
<line x1="3" y1="12" x2="3.01" y2="12"/>
<line x1="3" y1="18" x2="3.01" y2="18"/>
</svg>
</span>
</div>
<div class="col-auto">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-<?= htmlspecialchars($type['icon']) ?>">
<!-- Icon will be rendered by Tabler -->
</svg>
</div>
<div class="col">
<div class="d-flex align-items-center">
<strong><?= htmlspecialchars($type['name']) ?></strong>
<?php if (!$type['is_active']): ?>
<span class="badge bg-secondary ms-2">უაქტივო</span>
<?php endif; ?>
</div>
<?php if ($type['description']): ?>
<div class="text-muted small"><?= htmlspecialchars($type['description']) ?></div>
<?php endif; ?>
<small class="text-muted">რიგითობა: <?= $type['sort_order'] ?></small>
</div>
<div class="col-auto">
<div class="btn-list">
<a href="dashboard.php?module=product&action=types&subaction=edit&id=<?= $type['id'] ?>" class="btn btn-sm btn-outline-primary">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<path d="M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1"/>
<path d="M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415z"/>
<path d="M16 5l3 3"/>
</svg>
რედაქტირება
</a>
<form method="post" style="display: inline;">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?= $type['id'] ?>">
<button type="submit" class="btn btn-sm btn-outline-danger" onclick="return confirm('დარწმუნებული ხართ რომ გსურთ ამ ტიპის წაშლა?')">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<path d="M4 7l16 0"/>
<path d="M10 11l0 6"/>
<path d="M14 11l0 6"/>
<path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12"/>
<path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3"/>
</svg>
წაშლა
</button>
</form>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<form id="sort-form" method="post" style="display: none;">
<input type="hidden" name="action" value="update_order">
<div id="sort-orders"></div>
</form>
<?php endif; ?>
</div>
</div>
<?php elseif ($action === 'add' || $action === 'edit'): ?>
<!-- Add/Edit Form -->
<div class="card">
<div class="card-header">
<h3 class="card-title">
<?= $action === 'add' ? 'ახალი პროდუქტის ტიპი' : 'პროდუქტის ტიპის რედაქტირება' ?>
</h3>
</div>
<div class="card-body">
<form method="post">
<input type="hidden" name="action" value="<?= $action ?>">
<?php if ($action === 'edit'): ?>
<input type="hidden" name="id" value="<?= $editType['id'] ?>">
<?php endif; ?>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">ტიპის სახელი</label>
<input type="text" name="name" class="form-control"
value="<?= htmlspecialchars($editType['name'] ?? '') ?>"
placeholder="მაგ: Shared Hosting" required>
</div>
<div class="mb-3">
<label class="form-label">აღწერა</label>
<textarea name="description" class="form-control" rows="3"
placeholder="ტიპის დეტალური აღწერა"><?= htmlspecialchars($editType['description'] ?? '') ?></textarea>
</div>
<div class="mb-3">
<label class="form-label">რიგითობა</label>
<input type="number" name="sort_order" class="form-control"
value="<?= $editType['sort_order'] ?? '0' ?>"
placeholder="0">
<small class="form-hint">უფრო პატარა რიცხვი = უფრო ზევით</small>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" name="is_active"
<?= ($editType['is_active'] ?? 1) ? 'checked' : '' ?>>
<label class="form-check-label">აქტიური</label>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">აიკონი</label>
<select name="icon" class="form-select" id="icon-select">
<?php foreach ($availableIcons as $iconName => $iconLabel): ?>
<option value="<?= $iconName ?>"
<?= ($editType['icon'] ?? 'box') === $iconName ? 'selected' : '' ?>>
<?= $iconLabel ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">აიკონის გადახედვა</label>
<div class="icon-preview p-4 border rounded text-center">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline" id="preview-icon">
<!-- Icon preview -->
</svg>
<div class="mt-2 text-muted" id="preview-text">აიკონის გადახედვა</div>
</div>
</div>
</div>
</div>
<div class="card-actions">
<button type="submit" class="btn btn-primary">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
<polyline points="17,21 17,13 7,13 7,21"/>
<polyline points="7,3 7,8 15,8"/>
</svg>
შენახვა
</button>
<a href="dashboard.php?module=product&action=types" class="btn btn-secondary">გაუქმება</a>
</div>
</form>
</div>
</div>
<?php endif; ?>
</div>
<script src="../dist/js/tabler.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Sortable functionality
const sortableEl = document.getElementById('sortable-types');
if (sortableEl) {
const sortable = Sortable.create(sortableEl, {
handle: '.handle',
animation: 150,
onEnd: function() {
const items = sortableEl.querySelectorAll('.list-group-item');
const orders = {};
items.forEach((item, index) => {
const id = item.getAttribute('data-id');
orders[id] = index + 1;
});
// Update hidden form
const sortOrdersDiv = document.getElementById('sort-orders');
sortOrdersDiv.innerHTML = '';
Object.keys(orders).forEach(id => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = `orders[${id}]`;
input.value = orders[id];
sortOrdersDiv.appendChild(input);
});
// Submit form
document.getElementById('sort-form').submit();
}
});
}
// Icon preview
const iconSelect = document.getElementById('icon-select');
const previewIcon = document.getElementById('preview-icon');
const previewText = document.getElementById('preview-text');
if (iconSelect && previewIcon) {
function updateIconPreview() {
const selectedIcon = iconSelect.value;
const selectedText = iconSelect.options[iconSelect.selectedIndex].text;
// Update icon class
previewIcon.className = `icon icon-tabler icons-tabler-outline icon-tabler-${selectedIcon}`;
previewText.textContent = selectedText;
}
// Initialize preview
updateIconPreview();
// Update on change
iconSelect.addEventListener('change', updateIconPreview);
}
// Form validation
const form = document.querySelector('form[method="post"]');
if (form && form.querySelector('input[name="name"]')) {
form.addEventListener('submit', function(e) {
const nameInput = form.querySelector('input[name="name"]');
if (!nameInput.value.trim()) {
alert('ტიპის სახელი სავალდებულოა');
e.preventDefault();
nameInput.focus();
return false;
}
});
}
});
</script>
<?php require_once Config::includePath('footer.php'); ?>
@@ -0,0 +1,60 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../models/smtpmodel.php';
SMTPModel::setDb($pdo);
$message = '';
$messageType = '';
// SMTP settings-ების განახლება
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'update_settings') {
$settings = [
'smtp_host' => trim($_POST['smtp_host'] ?? ''),
'smtp_port' => trim($_POST['smtp_port'] ?? '587'),
'smtp_secure' => $_POST['smtp_secure'] ?? 'tls',
'smtp_auth' => isset($_POST['smtp_auth']) ? '1' : '0',
'smtp_username' => trim($_POST['smtp_username'] ?? ''),
'smtp_password' => $_POST['smtp_password'] ?? '',
'smtp_from_email' => trim($_POST['smtp_from_email'] ?? ''),
'smtp_from_name' => trim($_POST['smtp_from_name'] ?? ''),
'smtp_debug' => $_POST['smtp_debug'] ?? '0'
];
try {
SMTPModel::updateMultipleSettings($settings);
$message = 'SMTP სეტინგები წარმატებით განახლდა!';
$messageType = 'success';
} catch (Exception $e) {
$message = 'შეცდომა: ' . $e->getMessage();
$messageType = 'danger';
}
}
// SMTP კავშირის ტესტი
elseif ($action === 'test_connection') {
$testEmail = trim($_POST['test_email'] ?? '');
if (empty($testEmail)) {
$result = SMTPModel::testSmtpConnection();
} else {
$result = SMTPModel::testSmtpConnection($testEmail);
}
$message = $result['message'];
$messageType = $result['success'] ? 'success' : 'danger';
}
}
// მიმდინარე settings-ების მიღება
$settings = SMTPModel::getSmtpConfig();
require_once __DIR__ . '/../views/smtp_settings.php';
?>
+140
View File
@@ -0,0 +1,140 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
class SMTPModel
{
protected static $db;
public static function setDb($pdo)
{
self::$db = $pdo;
}
/**
* ყველა SMTP setting-ის მიღება
*/
public static function getAllSettings()
{
$stmt = self::$db->query("SELECT * FROM smtp_settings ORDER BY id");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* კონკრეტული setting-ის მიღება
*/
public static function getSetting($key)
{
$stmt = self::$db->prepare("SELECT setting_value FROM smtp_settings WHERE setting_key = ?");
$stmt->execute([$key]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result ? $result['setting_value'] : null;
}
/**
* Setting-ის განახლება
*/
public static function updateSetting($key, $value)
{
$stmt = self::$db->prepare("UPDATE smtp_settings SET setting_value = ?, updated_at = NOW() WHERE setting_key = ?");
return $stmt->execute([$value, $key]);
}
/**
* ყველა setting-ის განახლება ერთდროულად
*/
public static function updateMultipleSettings($settings)
{
try {
self::$db->beginTransaction();
$stmt = self::$db->prepare("UPDATE smtp_settings SET setting_value = ?, updated_at = NOW() WHERE setting_key = ?");
foreach ($settings as $key => $value) {
$stmt->execute([$value, $key]);
}
self::$db->commit();
return true;
} catch (Exception $e) {
self::$db->rollback();
throw $e;
}
}
/**
* SMTP კონფიგურაციის ასოციაციური მასივის მიღება
*/
public static function getSmtpConfig()
{
$settings = self::getAllSettings();
$config = [];
foreach ($settings as $setting) {
$config[$setting['setting_key']] = $setting['setting_value'];
}
return $config;
}
/**
* SMTP კავშირის ტესტი
*/
public static function testSmtpConnection($testEmail = null)
{
require_once __DIR__ . '/../../../libs/phpmailer/src/PHPMailer.php';
require_once __DIR__ . '/../../../libs/phpmailer/src/SMTP.php';
require_once __DIR__ . '/../../../libs/phpmailer/src/Exception.php';
$config = self::getSmtpConfig();
$mail = new \PHPMailer\PHPMailer\PHPMailer(true);
try {
$mail->isSMTP();
$mail->Host = $config['smtp_host'] ?? 'localhost';
$mail->SMTPAuth = (bool)($config['smtp_auth'] ?? false);
$mail->Username = $config['smtp_username'] ?? '';
$mail->Password = $config['smtp_password'] ?? '';
$mail->SMTPSecure = $config['smtp_secure'] ?? 'tls';
$mail->Port = (int)($config['smtp_port'] ?? 587);
$mail->SMTPDebug = 0; // No debug output for test
$mail->setFrom(
$config['smtp_from_email'] ?? 'test@example.com',
$config['smtp_from_name'] ?? 'Test'
);
if ($testEmail) {
$mail->addAddress($testEmail);
$mail->CharSet = 'UTF-8';
$mail->isHTML(true);
$mail->Subject = 'SMTP კონფიგურაციის ტესტი';
$mail->Body = 'ეს არის SMTP კონფიგურაციის ტესტური შეტყობინება. თუ ეს წერილი მიიღეთ, SMTP სეტინგები სწორია.';
$result = $mail->send();
return [
'success' => true,
'message' => 'ტესტური ელ.წერილი წარმატებით გაიგზავნა ' . $testEmail . ' მისამართზე'
];
} else {
// მხოლოდ კავშირის ტესტი ელ.წერილის გაგზავნის გარეშე
$mail->smtpConnect();
$mail->smtpClose();
return [
'success' => true,
'message' => 'SMTP კავშირი წარმატებით დამყარდა'
];
}
} catch (\PHPMailer\PHPMailer\Exception $e) {
return [
'success' => false,
'message' => 'SMTP შეცდომა: ' . $e->getMessage()
];
}
}
}
?>
@@ -0,0 +1,295 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../../../../vendor/autoload.php';
use App\Config;
?>
<?php require_once Config::includePath('head.php'); ?>
<?php require_once Config::includePath('navbar.php'); ?>
<?php require_once Config::includePath('pageheader.php'); ?>
<?php require_once Config::includePath('pagebodystart.php'); ?>
<div class="container-xl mt-4">
<!-- Page header -->
<div class="page-header d-print-none">
<div class="row align-items-center">
<div class="col">
<h2 class="page-title">SMTP სეტინგები</h2>
<div class="text-muted mt-1">ელ.ფოსტის სერვერის კონფიგურაცია</div>
</div>
<div class="col-auto">
<a href="../dashboard.php" class="btn btn-outline-primary">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-left">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M5 12l14 0"/>
<path d="M5 12l6 6"/>
<path d="M5 12l6 -6"/>
</svg>
უკან დაბრუნება
</a>
</div>
</div>
</div>
<!-- Messages -->
<?php if ($message): ?>
<div class="alert alert-<?= $messageType ?> alert-dismissible">
<div class="d-flex">
<div><?= htmlspecialchars($message) ?></div>
</div>
<a class="btn-close" data-bs-dismiss="alert" aria-label="close"></a>
</div>
<?php endif; ?>
<div class="row">
<!-- SMTP კონფიგურაცია -->
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h3 class="card-title">SMTP სერვერის პარამეტრები</h3>
</div>
<div class="card-body">
<form method="post">
<input type="hidden" name="action" value="update_settings">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">SMTP სერვერი</label>
<input type="text" name="smtp_host" class="form-control"
value="<?= htmlspecialchars($settings['smtp_host'] ?? '') ?>"
placeholder="mail.example.com" required>
<small class="form-hint">მაგ: smtp.gmail.com, mail.yahoo.com</small>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">პორტი</label>
<input type="number" name="smtp_port" class="form-control"
value="<?= htmlspecialchars($settings['smtp_port'] ?? '587') ?>"
placeholder="587" required>
<small class="form-hint">587 (TLS) ან 465 (SSL)</small>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">დაცვა</label>
<select name="smtp_secure" class="form-select" required>
<option value="tls" <?= ($settings['smtp_secure'] ?? '') === 'tls' ? 'selected' : '' ?>>TLS</option>
<option value="ssl" <?= ($settings['smtp_secure'] ?? '') === 'ssl' ? 'selected' : '' ?>>SSL</option>
</select>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-check">
<input type="checkbox" name="smtp_auth" class="form-check-input"
<?= ($settings['smtp_auth'] ?? '0') === '1' ? 'checked' : '' ?>>
<span class="form-check-label">SMTP ავტორიზაცია</span>
</label>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">მომხმარებლის სახელი</label>
<input type="email" name="smtp_username" class="form-control"
value="<?= htmlspecialchars($settings['smtp_username'] ?? '') ?>"
placeholder="user@example.com">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">პაროლი</label>
<div class="input-group">
<input type="password" name="smtp_password" id="smtp_password" class="form-control"
value="<?= htmlspecialchars($settings['smtp_password'] ?? '') ?>"
placeholder="••••••••">
<button type="button" class="btn btn-outline-secondary" onclick="togglePassword()">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
<circle cx="12" cy="12" r="3"/>
</svg>
</button>
</div>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">გამომგზავნის ელ.ფოსტა</label>
<input type="email" name="smtp_from_email" class="form-control"
value="<?= htmlspecialchars($settings['smtp_from_email'] ?? '') ?>"
placeholder="noreply@example.com" required>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">გამომგზავნის სახელი</label>
<input type="text" name="smtp_from_name" class="form-control"
value="<?= htmlspecialchars($settings['smtp_from_name'] ?? '') ?>"
placeholder="My Website" required>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Debug რეჟიმი</label>
<select name="smtp_debug" class="form-select">
<option value="0" <?= ($settings['smtp_debug'] ?? '0') === '0' ? 'selected' : '' ?>>გამორთული</option>
<option value="1" <?= ($settings['smtp_debug'] ?? '0') === '1' ? 'selected' : '' ?>>კლიენტის შეტყობინებები</option>
<option value="2" <?= ($settings['smtp_debug'] ?? '0') === '2' ? 'selected' : '' ?>>კლიენტი + სერვერი</option>
</select>
<small class="form-hint">პროდაქშენში გამოიყენეთ 0</small>
</div>
<div class="card-actions">
<button type="submit" class="btn btn-primary">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
<polyline points="17,21 17,13 7,13 7,21"/>
<polyline points="7,3 7,8 15,8"/>
</svg>
შენახვა
</button>
</div>
</form>
</div>
</div>
</div>
<!-- SMTP ტესტი -->
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h3 class="card-title">კავშირის ტესტი</h3>
</div>
<div class="card-body">
<form method="post">
<input type="hidden" name="action" value="test_connection">
<div class="mb-3">
<label class="form-label">ტესტური ელ.ფოსტა (არასავალდებულო)</label>
<input type="email" name="test_email" class="form-control"
placeholder="test@example.com">
<small class="form-hint">თუ მითითებული იქნება, ტესტური წერილი გაიგზავნება</small>
</div>
<button type="submit" class="btn btn-success w-100">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
<polyline points="22,4 12,14.01 9,11.01"/>
</svg>
კავშირის ტესტი
</button>
</form>
</div>
</div>
<!-- მიმდინარე კონფიგურაცია -->
<div class="card mt-4">
<div class="card-header">
<h3 class="card-title">მიმდინარე კონფიგურაცია</h3>
</div>
<div class="card-body">
<div class="list-group list-group-flush">
<div class="list-group-item d-flex justify-content-between">
<strong>სერვერი:</strong>
<span class="badge bg-secondary"><?= htmlspecialchars($settings['smtp_host'] ?? 'არ არის მითითებული') ?></span>
</div>
<div class="list-group-item d-flex justify-content-between">
<strong>პორტი:</strong>
<span class="badge bg-info"><?= htmlspecialchars($settings['smtp_port'] ?? '587') ?></span>
</div>
<div class="list-group-item d-flex justify-content-between">
<strong>დაცვა:</strong>
<span class="badge bg-warning"><?= strtoupper($settings['smtp_secure'] ?? 'TLS') ?></span>
</div>
<div class="list-group-item d-flex justify-content-between">
<strong>ავტორიზაცია:</strong>
<span class="badge bg-<?= ($settings['smtp_auth'] ?? '0') === '1' ? 'success' : 'danger' ?>">
<?= ($settings['smtp_auth'] ?? '0') === '1' ? 'ჩართული' : 'გამორთული' ?>
</span>
</div>
<div class="list-group-item d-flex justify-content-between">
<strong>გამომგზავნი:</strong>
<span class="text-muted text-truncate" style="max-width: 150px;">
<?= htmlspecialchars($settings['smtp_from_email'] ?? 'არ არის მითითებული') ?>
</span>
</div>
</div>
</div>
</div>
<!-- სასარგებლო ინფორმაცია -->
<div class="card mt-4">
<div class="card-header">
<h3 class="card-title">სასარგებლო ინფორმაცია</h3>
</div>
<div class="card-body">
<div class="alert alert-info">
<h4>Gmail SMTP:</h4>
<ul class="mb-0">
<li>სერვერი: smtp.gmail.com</li>
<li>პორტი: 587 (TLS)</li>
<li>App Password საჭიროა!</li>
</ul>
</div>
<div class="alert alert-warning">
<h4>მნიშვნელოვანი!</h4>
<ul class="mb-0">
<li>პაროლი ნაჩვენებია ღია ტექსტად</li>
<li>გთხოვთ დარწმუნდით უსაფრთხოებაში</li>
<li>გამოიყენეთ App Password Gmail-ისთვის</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="../dist/js/tabler.min.js"></script>
<script>
function togglePassword() {
const passwordField = document.getElementById('smtp_password');
const type = passwordField.getAttribute('type') === 'password' ? 'text' : 'password';
passwordField.setAttribute('type', type);
}
// Form validation
document.addEventListener('DOMContentLoaded', function() {
const form = document.querySelector('form[method="post"]');
if (form) {
form.addEventListener('submit', function(e) {
const smtpHost = document.querySelector('input[name="smtp_host"]').value.trim();
const smtpPort = document.querySelector('input[name="smtp_port"]').value.trim();
const fromEmail = document.querySelector('input[name="smtp_from_email"]').value.trim();
if (!smtpHost || !smtpPort || !fromEmail) {
alert('გთხოვთ შეავსოთ ყველა სავალდებულო ველი!');
e.preventDefault();
return false;
}
if (parseInt(smtpPort) < 1 || parseInt(smtpPort) > 65535) {
alert('პორტი უნდა იყოს 1-65535 დიაპაზონში!');
e.preventDefault();
return false;
}
});
}
});
</script>
<?php require_once Config::includePath('footer.php'); ?>
@@ -0,0 +1,5 @@
<?php
// Users index controller - redirects to management
header("Location: dashboard.php?module=users&action=management");
exit;
?>
@@ -0,0 +1,200 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../models/usermodel.php';
UserModel::setDb($pdo);
$message = '';
$messageType = '';
$action = $_GET['subaction'] ?? 'list';
// POST მოთხოვნების მართვა
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$postAction = $_POST['action'] ?? '';
switch ($postAction) {
case 'add':
$data = [
'first_name' => trim($_POST['first_name'] ?? ''),
'last_name' => trim($_POST['last_name'] ?? ''),
'email' => trim($_POST['email'] ?? ''),
'password' => $_POST['password'] ?? '',
'role' => $_POST['role'] ?? 'user',
'is_active' => isset($_POST['is_active']) ? 1 : 0
];
// ვალიდაცია
$errors = [];
if (empty($data['first_name'])) $errors[] = 'სახელი სავალდებულოა';
if (empty($data['last_name'])) $errors[] = 'გვარი სავალდებულოა';
if (empty($data['email']) || !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
$errors[] = 'ვალიდური ელ.ფოსტა სავალდებულოა';
}
if (empty($data['password']) || strlen($data['password']) < 6) {
$errors[] = 'პაროლი უნდა იყოს მინიმუმ 6 სიმბოლო';
}
if (!empty($errors)) {
$message = implode('<br>', $errors);
$messageType = 'danger';
} else {
try {
UserModel::createUser($data);
$message = 'მომხმარებელი წარმატებით დაემატა';
$messageType = 'success';
$action = 'list';
} catch (Exception $e) {
$message = 'შეცდომა: ' . $e->getMessage();
$messageType = 'danger';
}
}
break;
case 'edit':
$id = (int)($_POST['id'] ?? 0);
$data = [
'first_name' => trim($_POST['first_name'] ?? ''),
'last_name' => trim($_POST['last_name'] ?? ''),
'email' => trim($_POST['email'] ?? ''),
'password' => $_POST['password'] ?? '',
'role' => $_POST['role'] ?? 'user',
'is_active' => isset($_POST['is_active']) ? 1 : 0
];
// ვალიდაცია
$errors = [];
if (empty($data['first_name'])) $errors[] = 'სახელი სავალდებულოა';
if (empty($data['last_name'])) $errors[] = 'გვარი სავალდებულოა';
if (empty($data['email']) || !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
$errors[] = 'ვალიდური ელ.ფოსტა სავალდებულოა';
}
if (!empty($data['password']) && strlen($data['password']) < 6) {
$errors[] = 'პაროლი უნდა იყოს მინიმუმ 6 სიმბოლო';
}
if (!empty($errors)) {
$message = implode('<br>', $errors);
$messageType = 'danger';
} else {
try {
UserModel::updateUser($id, $data);
$message = 'მომხმარებელი წარმატებით განახლდა';
$messageType = 'success';
$action = 'list';
} catch (Exception $e) {
$message = 'შეცდომა: ' . $e->getMessage();
$messageType = 'danger';
}
}
break;
case 'delete':
$id = (int)($_POST['id'] ?? 0);
if ($id > 0) {
try {
UserModel::deleteUser($id);
$message = 'მომხმარებელი წარმატებით წაიშალა';
$messageType = 'success';
} catch (Exception $e) {
$message = 'შეცდომა: ' . $e->getMessage();
$messageType = 'danger';
}
}
$action = 'list';
break;
case 'toggle_status':
$id = (int)($_POST['id'] ?? 0);
if ($id > 0) {
try {
UserModel::toggleUserStatus($id);
$message = 'მომხმარებლის სტატუსი შეიცვალა';
$messageType = 'success';
} catch (Exception $e) {
$message = 'შეცდომა: ' . $e->getMessage();
$messageType = 'danger';
}
}
$action = 'list';
break;
case 'change_password':
$id = (int)($_POST['id'] ?? 0);
$currentPassword = $_POST['current_password'] ?? '';
$newPassword = $_POST['new_password'] ?? '';
$confirmPassword = $_POST['confirm_password'] ?? '';
$errors = [];
if (empty($currentPassword)) $errors[] = 'მიმდინარე პაროლი სავალდებულოა';
if (empty($newPassword) || strlen($newPassword) < 6) {
$errors[] = 'ახალი პაროლი უნდა იყოს მინიმუმ 6 სიმბოლო';
}
if ($newPassword !== $confirmPassword) $errors[] = 'პაროლები არ ემთხვევა';
if (!empty($errors)) {
$message = implode('<br>', $errors);
$messageType = 'danger';
} else {
try {
UserModel::changePassword($id, $currentPassword, $newPassword);
$message = 'პაროლი წარმატებით შეიცვალა';
$messageType = 'success';
} catch (Exception $e) {
$message = 'შეცდომა: ' . $e->getMessage();
$messageType = 'danger';
}
}
break;
}
}
// მონაცემების მომზადება view-სთვის
switch ($action) {
case 'add':
case 'edit':
$availableRoles = UserModel::getAvailableRoles();
if ($action === 'edit') {
$editId = (int)($_GET['id'] ?? 0);
$editUser = UserModel::getUserById($editId);
if (!$editUser) {
$message = 'მომხმარებელი ვერ მოიძებნა';
$messageType = 'danger';
$action = 'list';
}
}
break;
case 'profile':
$profileId = (int)($_GET['id'] ?? $_SESSION['user_id'] ?? 0);
$profileUser = UserModel::getUserById($profileId);
if (!$profileUser) {
$message = 'მომხმარებელი ვერ მოიძებნა';
$messageType = 'danger';
$action = 'list';
}
break;
case 'list':
default:
// Search და Filter პარამეტრები
$search = $_GET['search'] ?? '';
$role = $_GET['role'] ?? '';
$status = $_GET['status'] ?? '';
if ($search || $role || $status !== '') {
$users = UserModel::searchUsers($search, $role, $status);
} else {
$users = UserModel::getAllUsers();
}
$userStats = UserModel::getUserStats();
$availableRoles = UserModel::getAvailableRoles();
break;
}
require_once __DIR__ . '/../views/users_management.php';
?>
+267
View File
@@ -0,0 +1,267 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
class UserModel
{
protected static $db;
public static function setDb($pdo)
{
self::$db = $pdo;
}
/**
* ყველა მომხმარებლის მიღება
*/
public static function getAllUsers()
{
$stmt = self::$db->query("SELECT * FROM users ORDER BY created_at DESC");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* კონკრეტული მომხმარებლის მიღება ID-ით
*/
public static function getUserById($id)
{
$stmt = self::$db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
/**
* მომხმარებლის მიღება ელ.ფოსტით
*/
public static function getUserByEmail($email)
{
$stmt = self::$db->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
/**
* ახალი მომხმარებლის დამატება
*/
public static function createUser($data)
{
// ვამოწმებთ არსებობს თუ არა ასეთი ელ.ფოსტა
if (self::getUserByEmail($data['email'])) {
throw new Exception("მომხმარებელი ამ ელ.ფოსტით უკვე არსებობს");
}
$hashedPassword = password_hash($data['password'], PASSWORD_DEFAULT);
$stmt = self::$db->prepare("
INSERT INTO users (first_name, last_name, email, password, role, is_active)
VALUES (?, ?, ?, ?, ?, ?)
");
return $stmt->execute([
$data['first_name'],
$data['last_name'],
$data['email'],
$hashedPassword,
$data['role'] ?? 'user',
$data['is_active'] ?? 1
]);
}
/**
* მომხმარებლის განახლება
*/
public static function updateUser($id, $data)
{
// ვამოწმებთ ელ.ფოსტის უნიკალურობას (თუ შეიცვალა)
$currentUser = self::getUserById($id);
if (!$currentUser) {
throw new Exception("მომხმარებელი ვერ მოიძებნა");
}
if ($currentUser['email'] !== $data['email']) {
$existingUser = self::getUserByEmail($data['email']);
if ($existingUser && $existingUser['id'] != $id) {
throw new Exception("მომხმარებელი ამ ელ.ფოსტით უკვე არსებობს");
}
}
$updateFields = [
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'email' => $data['email'],
'role' => $data['role'] ?? 'user',
'is_active' => $data['is_active'] ?? 1
];
// თუ ახალი პაროლი არის მითითებული
if (!empty($data['password'])) {
$updateFields['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
$sql = "UPDATE users SET first_name = ?, last_name = ?, email = ?, password = ?, role = ?, is_active = ?, updated_at = NOW() WHERE id = ?";
$params = array_values($updateFields);
$params[] = $id;
} else {
unset($updateFields['password']);
$sql = "UPDATE users SET first_name = ?, last_name = ?, email = ?, role = ?, is_active = ?, updated_at = NOW() WHERE id = ?";
$params = array_values($updateFields);
$params[] = $id;
}
$stmt = self::$db->prepare($sql);
return $stmt->execute($params);
}
/**
* მომხმარებლის წაშლა
*/
public static function deleteUser($id)
{
// ვამოწმებთ არ არის თუ არა ეს უკანასკნელი ადმინი
$admins = self::getAdminUsers();
$userToDelete = self::getUserById($id);
if ($userToDelete['role'] === 'admin' && count($admins) <= 1) {
throw new Exception("ვერ შეიძლება უკანასკნელი ადმინისტრატორის წაშლა");
}
$stmt = self::$db->prepare("DELETE FROM users WHERE id = ?");
return $stmt->execute([$id]);
}
/**
* მომხმარებლის სტატუსის ცვლილება
*/
public static function toggleUserStatus($id)
{
$user = self::getUserById($id);
if (!$user) {
throw new Exception("მომხმარებელი ვერ მოიძებნა");
}
$newStatus = $user['is_active'] ? 0 : 1;
// ვამოწმებთ ადმინების რაოდენობას
if ($user['role'] === 'admin' && $user['is_active'] && $newStatus === 0) {
$activeAdmins = self::getActiveAdminUsers();
if (count($activeAdmins) <= 1) {
throw new Exception("ვერ შეიძლება უკანასკნელი აქტიური ადმინისტრატორის გამორთვა");
}
}
$stmt = self::$db->prepare("UPDATE users SET is_active = ? WHERE id = ?");
return $stmt->execute([$newStatus, $id]);
}
/**
* პაროლის შეცვლა
*/
public static function changePassword($id, $currentPassword, $newPassword)
{
$user = self::getUserById($id);
if (!$user) {
throw new Exception("მომხმარებელი ვერ მოიძებნა");
}
if (!password_verify($currentPassword, $user['password'])) {
throw new Exception("მიმდინარე პაროლი არასწორია");
}
$hashedPassword = password_hash($newPassword, PASSWORD_DEFAULT);
$stmt = self::$db->prepare("UPDATE users SET password = ?, updated_at = NOW() WHERE id = ?");
return $stmt->execute([$hashedPassword, $id]);
}
/**
* ადმინისტრატორების მიღება
*/
public static function getAdminUsers()
{
$stmt = self::$db->prepare("SELECT * FROM users WHERE role = 'admin'");
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* აქტიური ადმინისტრატორების მიღება
*/
public static function getActiveAdminUsers()
{
$stmt = self::$db->prepare("SELECT * FROM users WHERE role = 'admin' AND is_active = 1");
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* მომხმარებლების სტატისტიკა
*/
public static function getUserStats()
{
$stats = [];
// სულ მომხმარებლები
$stmt = self::$db->query("SELECT COUNT(*) FROM users");
$stats['total'] = $stmt->fetchColumn();
// აქტიური მომხმარებლები
$stmt = self::$db->query("SELECT COUNT(*) FROM users WHERE is_active = 1");
$stats['active'] = $stmt->fetchColumn();
// ადმინისტრატორები
$stmt = self::$db->query("SELECT COUNT(*) FROM users WHERE role = 'admin'");
$stats['admins'] = $stmt->fetchColumn();
// ამ თვის ახალი მომხმარებლები
$stmt = self::$db->query("SELECT COUNT(*) FROM users WHERE created_at >= DATE_FORMAT(NOW(), '%Y-%m-01')");
$stats['this_month'] = $stmt->fetchColumn();
return $stats;
}
/**
* ხელმისაწვდომი როლები
*/
public static function getAvailableRoles()
{
return [
'admin' => 'ადმინისტრატორი',
'manager' => 'მენეჯერი',
'user' => 'მომხმარებელი',
'guest' => 'სტუმარი'
];
}
/**
* მომხმარებლის search და filter
*/
public static function searchUsers($search = '', $role = '', $status = '')
{
$sql = "SELECT * FROM users WHERE 1=1";
$params = [];
if (!empty($search)) {
$sql .= " AND (first_name LIKE ? OR last_name LIKE ? OR email LIKE ?)";
$searchTerm = "%$search%";
$params[] = $searchTerm;
$params[] = $searchTerm;
$params[] = $searchTerm;
}
if (!empty($role)) {
$sql .= " AND role = ?";
$params[] = $role;
}
if ($status !== '') {
$sql .= " AND is_active = ?";
$params[] = (int)$status;
}
$sql .= " ORDER BY created_at DESC";
$stmt = self::$db->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
?>
@@ -0,0 +1,584 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once __DIR__ . '/../../../includes/init.php';
require_once __DIR__ . '/../../../../vendor/autoload.php';
use App\Config;
?>
<?php require_once Config::includePath('head.php'); ?>
<?php require_once Config::includePath('navbar.php'); ?>
<?php require_once Config::includePath('pageheader.php'); ?>
<?php require_once Config::includePath('pagebodystart.php'); ?>
<div class="container-xl mt-4">
<!-- Page header -->
<div class="page-header d-print-none">
<div class="row align-items-center">
<div class="col">
<h2 class="page-title">
<?php
switch ($action) {
case 'add': echo 'ახალი მომხმარებლის დამატება'; break;
case 'edit': echo 'მომხმარებლის რედაქტირება'; break;
case 'profile': echo 'მომხმარებლის პროფილი'; break;
default: echo 'მომხმარებლების მართვა'; break;
}
?>
</h2>
<?php if ($action === 'list'): ?>
<div class="text-muted mt-1">სისტემის მომხმარებლების კონფიგურაცია და მართვა</div>
<?php endif; ?>
</div>
<div class="col-auto">
<div class="btn-list">
<?php if ($action === 'list'): ?>
<a href="dashboard.php?module=users&action=management&subaction=add" class="btn btn-primary">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<path d="M12 5l0 14"/>
<path d="M5 12l14 0"/>
</svg>
ახალი მომხმარებელი
</a>
<?php else: ?>
<a href="dashboard.php?module=users&action=management" class="btn btn-outline-primary">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<path d="M5 12l14 0"/>
<path d="M5 12l6 6"/>
<path d="M5 12l6 -6"/>
</svg>
უკან დაბრუნება
</a>
<?php endif; ?>
</div>
</div>
</div>
</div>
<!-- Messages -->
<?php if ($message): ?>
<div class="alert alert-<?= $messageType ?> alert-dismissible">
<div class="d-flex">
<div><?= $message ?></div>
</div>
<a class="btn-close" data-bs-dismiss="alert" aria-label="close"></a>
</div>
<?php endif; ?>
<?php if ($action === 'list'): ?>
<!-- Statistics Cards -->
<div class="row mb-4">
<div class="col-sm-6 col-lg-3">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="subheader">სულ მომხმარებლები</div>
<div class="ms-auto lh-1">
<div class="dropdown">
<a class="dropdown-toggle text-muted" href="#" data-bs-toggle="dropdown">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<circle cx="12" cy="12" r="1"/>
<circle cx="12" cy="5" r="1"/>
<circle cx="12" cy="19" r="1"/>
</svg>
</a>
<div class="dropdown-menu dropdown-menu-end">
<a class="dropdown-item" href="dashboard.php?module=users&action=management">ყველას ნახვა</a>
</div>
</div>
</div>
</div>
<div class="h1 mb-3"><?= $userStats['total'] ?></div>
<div class="d-flex mb-2">
<div>რეგისტრაცია</div>
<div class="ms-auto">
<span class="text-green d-inline-flex align-items-center lh-1">
<?= $userStats['this_month'] ?> <small class="text-muted ms-1">ამ თვეში</small>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-lg-3">
<div class="card">
<div class="card-body">
<div class="subheader">აქტიური</div>
<div class="h1 mb-3 text-green"><?= $userStats['active'] ?></div>
<div class="d-flex mb-2">
<div>უაქტივო</div>
<div class="ms-auto">
<span class="text-red"><?= $userStats['total'] - $userStats['active'] ?></span>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-lg-3">
<div class="card">
<div class="card-body">
<div class="subheader">ადმინისტრატორები</div>
<div class="h1 mb-3 text-blue"><?= $userStats['admins'] ?></div>
<div class="d-flex mb-2">
<div>სხვა როლები</div>
<div class="ms-auto">
<span class="text-muted"><?= $userStats['total'] - $userStats['admins'] ?></span>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-lg-3">
<div class="card">
<div class="card-body">
<div class="subheader">ონლაინ</div>
<div class="h1 mb-3 text-yellow">0</div>
<div class="d-flex mb-2">
<div>უკანასკნელი აქტივობა</div>
<div class="ms-auto">
<span class="text-muted">დღეს</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Search and Filter -->
<div class="card mb-4">
<div class="card-body">
<form method="get" class="row">
<input type="hidden" name="module" value="users">
<input type="hidden" name="action" value="management">
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">ძიება</label>
<input type="text" name="search" class="form-control"
value="<?= htmlspecialchars($_GET['search'] ?? '') ?>"
placeholder="სახელი, გვარი, ელ.ფოსტა...">
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">როლი</label>
<select name="role" class="form-select">
<option value="">ყველა როლი</option>
<?php foreach ($availableRoles as $roleKey => $roleLabel): ?>
<option value="<?= $roleKey ?>" <?= ($_GET['role'] ?? '') === $roleKey ? 'selected' : '' ?>>
<?= $roleLabel ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">სტატუსი</label>
<select name="status" class="form-select">
<option value="">ყველა სტატუსი</option>
<option value="1" <?= ($_GET['status'] ?? '') === '1' ? 'selected' : '' ?>>აქტიური</option>
<option value="0" <?= ($_GET['status'] ?? '') === '0' ? 'selected' : '' ?>>უაქტივო</option>
</select>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">&nbsp;</label>
<div class="btn-list">
<button type="submit" class="btn btn-primary w-100">ძიება</button>
<a href="dashboard.php?module=users&action=management" class="btn btn-outline-secondary">გასუფთავება</a>
</div>
</div>
</div>
</form>
</div>
</div>
<!-- Users List -->
<div class="card">
<div class="card-header">
<h3 class="card-title">მომხმარებლების სია</h3>
<div class="card-actions">
<span class="text-muted">სულ: <?= count($users) ?> მომხმარებელი</span>
</div>
</div>
<div class="card-body">
<?php if (empty($users)): ?>
<div class="text-center text-muted py-4">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon mb-3">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
<circle cx="12" cy="7" r="4"/>
</svg>
<h3>მომხმარებლები ვერ მოიძებნა</h3>
<p class="text-muted">შეცვალეთ ფილტრები ან დაამატეთ ახალი მომხმარებელი</p>
</div>
<?php else: ?>
<div class="table-responsive">
<table class="table table-vcenter">
<thead>
<tr>
<th>მომხმარებელი</th>
<th>ელ.ფოსტა</th>
<th>როლი</th>
<th>სტატუსი</th>
<th>რეგისტრაცია</th>
<th>მოქმედებები</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<td>
<div class="d-flex align-items-center">
<span class="avatar bg-<?= $user['role'] === 'admin' ? 'red' : ($user['is_active'] ? 'green' : 'secondary') ?>-lt">
<?= strtoupper(substr($user['first_name'], 0, 1) . substr($user['last_name'], 0, 1)) ?>
</span>
<div class="ms-2">
<div class="fw-bold"><?= htmlspecialchars($user['first_name'] . ' ' . $user['last_name']) ?></div>
<div class="text-muted small">ID: <?= $user['id'] ?></div>
</div>
</div>
</td>
<td>
<div><?= htmlspecialchars($user['email']) ?></div>
<?php if ($user['id'] == $_SESSION['user_id']): ?>
<small class="text-blue">ეს ხარ თუ</small>
<?php endif; ?>
</td>
<td>
<span class="badge bg-<?= $user['role'] === 'admin' ? 'red' : ($user['role'] === 'manager' ? 'blue' : 'secondary') ?>">
<?= $availableRoles[$user['role']] ?? $user['role'] ?>
</span>
</td>
<td>
<span class="badge bg-<?= $user['is_active'] ? 'success' : 'danger' ?>">
<?= $user['is_active'] ? 'აქტიური' : 'უაქტივო' ?>
</span>
</td>
<td>
<div><?= date('Y-m-d', strtotime($user['created_at'])) ?></div>
<small class="text-muted"><?= date('H:i', strtotime($user['created_at'])) ?></small>
</td>
<td>
<div class="btn-list">
<a href="dashboard.php?module=users&action=management&subaction=profile&id=<?= $user['id'] ?>"
class="btn btn-sm btn-outline-info" title="პროფილი">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
<circle cx="12" cy="7" r="4"/>
</svg>
</a>
<a href="dashboard.php?module=users&action=management&subaction=edit&id=<?= $user['id'] ?>"
class="btn btn-sm btn-outline-primary" title="რედაქტირება">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<path d="M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1"/>
<path d="M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415z"/>
<path d="M16 5l3 3"/>
</svg>
</a>
<form method="post" style="display: inline;">
<input type="hidden" name="action" value="toggle_status">
<input type="hidden" name="id" value="<?= $user['id'] ?>">
<button type="submit" class="btn btn-sm btn-outline-<?= $user['is_active'] ? 'warning' : 'success' ?>"
title="<?= $user['is_active'] ? 'გამორთვა' : 'ჩართვა' ?>"
onclick="return confirm('დარწმუნებული ხართ?')">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<?php if ($user['is_active']): ?>
<path d="M10 3H6a2 2 0 0 0-2 2v14c0 5.5 7.5 3 7.5 3s7.5 2.5 7.5-3V5a2 2 0 0 0-2-2h-4"/>
<path d="M8 5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2"/>
<?php else: ?>
<path d="M9 11l3 3l8 -8"/>
<?php endif; ?>
</svg>
</button>
</form>
<?php if ($user['id'] != $_SESSION['user_id']): ?>
<form method="post" style="display: inline;">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?= $user['id'] ?>">
<button type="submit" class="btn btn-sm btn-outline-danger" title="წაშლა"
onclick="return confirm('დარწმუნებული ხართ რომ გსურთ ამ მომხმარებლის წაშლა?')">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<path d="M4 7l16 0"/>
<path d="M10 11l0 6"/>
<path d="M14 11l0 6"/>
<path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12"/>
<path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3"/>
</svg>
</button>
</form>
<?php endif; ?>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
<?php elseif ($action === 'add' || $action === 'edit'): ?>
<!-- Add/Edit Form -->
<div class="card">
<div class="card-header">
<h3 class="card-title">
<?= $action === 'add' ? 'ახალი მომხმარებლის დამატება' : 'მომხმარებლის რედაქტირება' ?>
</h3>
</div>
<div class="card-body">
<form method="post">
<input type="hidden" name="action" value="<?= $action ?>">
<?php if ($action === 'edit'): ?>
<input type="hidden" name="id" value="<?= $editUser['id'] ?>">
<?php endif; ?>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">სახელი</label>
<input type="text" name="first_name" class="form-control"
value="<?= htmlspecialchars($editUser['first_name'] ?? '') ?>"
placeholder="სახელი" required>
</div>
<div class="mb-3">
<label class="form-label">გვარი</label>
<input type="text" name="last_name" class="form-control"
value="<?= htmlspecialchars($editUser['last_name'] ?? '') ?>"
placeholder="გვარი" required>
</div>
<div class="mb-3">
<label class="form-label">ელ.ფოსტა</label>
<input type="email" name="email" class="form-control"
value="<?= htmlspecialchars($editUser['email'] ?? '') ?>"
placeholder="user@example.com" required>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">პაროლი <?= $action === 'edit' ? '(დატოვეთ ცარიელი თუ არ გსურთ ცვლილება)' : '' ?></label>
<input type="password" name="password" class="form-control"
placeholder="••••••••" <?= $action === 'add' ? 'required' : '' ?>>
<?php if ($action === 'add'): ?>
<small class="form-hint">მინიმუმ 6 სიმბოლო</small>
<?php endif; ?>
</div>
<div class="mb-3">
<label class="form-label">როლი</label>
<select name="role" class="form-select" required>
<?php foreach ($availableRoles as $roleKey => $roleLabel): ?>
<option value="<?= $roleKey ?>"
<?= ($editUser['role'] ?? 'user') === $roleKey ? 'selected' : '' ?>>
<?= $roleLabel ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" name="is_active"
<?= ($editUser['is_active'] ?? 1) ? 'checked' : '' ?>>
<label class="form-check-label">აქტიური მომხმარებელი</label>
</div>
</div>
</div>
<div class="card-actions">
<button type="submit" class="btn btn-primary">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
<polyline points="17,21 17,13 7,13 7,21"/>
<polyline points="7,3 7,8 15,8"/>
</svg>
შენახვა
</button>
<a href="dashboard.php?module=users&action=management" class="btn btn-secondary">გაუქმება</a>
</div>
</form>
</div>
</div>
<?php elseif ($action === 'profile'): ?>
<!-- User Profile -->
<div class="row">
<div class="col-md-4">
<!-- Profile Info -->
<div class="card">
<div class="card-body text-center">
<span class="avatar avatar-xl bg-<?= $profileUser['role'] === 'admin' ? 'red' : ($profileUser['is_active'] ? 'green' : 'secondary') ?>-lt mb-3">
<?= strtoupper(substr($profileUser['first_name'], 0, 1) . substr($profileUser['last_name'], 0, 1)) ?>
</span>
<h3 class="m-0 mb-1"><?= htmlspecialchars($profileUser['first_name'] . ' ' . $profileUser['last_name']) ?></h3>
<div class="text-muted"><?= htmlspecialchars($profileUser['email']) ?></div>
<div class="mt-3">
<span class="badge bg-<?= $profileUser['role'] === 'admin' ? 'red' : ($profileUser['role'] === 'manager' ? 'blue' : 'secondary') ?> me-1">
<?= $availableRoles[$profileUser['role']] ?? $profileUser['role'] ?>
</span>
<span class="badge bg-<?= $profileUser['is_active'] ? 'success' : 'danger' ?>">
<?= $profileUser['is_active'] ? 'აქტიური' : 'უაქტივო' ?>
</span>
</div>
</div>
<div class="card-footer">
<div class="row text-center">
<div class="col">
<div class="h4 m-0">ID</div>
<div class="text-muted"><?= $profileUser['id'] ?></div>
</div>
<div class="col">
<div class="h4 m-0">რეგისტრაცია</div>
<div class="text-muted"><?= date('Y-m-d', strtotime($profileUser['created_at'])) ?></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-8">
<!-- Profile Details -->
<div class="card">
<div class="card-header">
<h3 class="card-title">პროფილის დეტალები</h3>
<div class="card-actions">
<a href="dashboard.php?module=users&action=management&subaction=edit&id=<?= $profileUser['id'] ?>" class="btn btn-primary btn-sm">
რედაქტირება
</a>
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">სახელი</label>
<div class="form-control-plaintext"><?= htmlspecialchars($profileUser['first_name']) ?></div>
</div>
<div class="mb-3">
<label class="form-label">ელ.ფოსტა</label>
<div class="form-control-plaintext"><?= htmlspecialchars($profileUser['email']) ?></div>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">გვარი</label>
<div class="form-control-plaintext"><?= htmlspecialchars($profileUser['last_name']) ?></div>
</div>
<div class="mb-3">
<label class="form-label">ბოლო განახლება</label>
<div class="form-control-plaintext">
<?= $profileUser['updated_at'] ? date('Y-m-d H:i', strtotime($profileUser['updated_at'])) : 'არასდროს' ?>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Password Change -->
<?php if ($profileUser['id'] == $_SESSION['user_id']): ?>
<div class="card mt-4">
<div class="card-header">
<h3 class="card-title">პაროლის შეცვლა</h3>
</div>
<div class="card-body">
<form method="post">
<input type="hidden" name="action" value="change_password">
<input type="hidden" name="id" value="<?= $profileUser['id'] ?>">
<div class="row">
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">მიმდინარე პაროლი</label>
<input type="password" name="current_password" class="form-control" required>
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">ახალი პაროლი</label>
<input type="password" name="new_password" class="form-control" required>
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">პაროლის დადასტურება</label>
<input type="password" name="confirm_password" class="form-control" required>
</div>
</div>
</div>
<button type="submit" class="btn btn-warning">პაროლის შეცვლა</button>
</form>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
</div>
<script src="../dist/js/tabler.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Form validation
const forms = document.querySelectorAll('form[method="post"]');
forms.forEach(form => {
form.addEventListener('submit', function(e) {
const action = form.querySelector('input[name="action"]')?.value;
if (action === 'add' || action === 'edit') {
const firstName = form.querySelector('input[name="first_name"]')?.value.trim();
const lastName = form.querySelector('input[name="last_name"]')?.value.trim();
const email = form.querySelector('input[name="email"]')?.value.trim();
const password = form.querySelector('input[name="password"]')?.value;
let errors = [];
if (!firstName) errors.push('სახელი სავალდებულოა');
if (!lastName) errors.push('გვარი სავალდებულოა');
if (!email || !email.includes('@')) errors.push('ვალიდური ელ.ფოსტა სავალდებულოა');
if (action === 'add' && (!password || password.length < 6)) {
errors.push('პაროლი უნდა იყოს მინიმუმ 6 სიმბოლო');
}
if (action === 'edit' && password && password.length < 6) {
errors.push('პაროლი უნდა იყოს მინიმუმ 6 სიმბოლო');
}
if (errors.length > 0) {
alert('შეცდომები:\n' + errors.join('\n'));
e.preventDefault();
return false;
}
}
if (action === 'change_password') {
const currentPassword = form.querySelector('input[name="current_password"]')?.value;
const newPassword = form.querySelector('input[name="new_password"]')?.value;
const confirmPassword = form.querySelector('input[name="confirm_password"]')?.value;
let errors = [];
if (!currentPassword) errors.push('მიმდინარე პაროლი სავალდებულოა');
if (!newPassword || newPassword.length < 6) errors.push('ახალი პაროლი უნდა იყოს მინიმუმ 6 სიმბოლო');
if (newPassword !== confirmPassword) errors.push('პაროლები არ ემთხვევა');
if (errors.length > 0) {
alert('შეცდომები:\n' + errors.join('\n'));
e.preventDefault();
return false;
}
}
});
});
});
</script>
<?php require_once Config::includePath('footer.php'); ?>