This commit is contained in:
parent
1467e83ccf
commit
be126d506b
10
config/packages/easy_admin.yaml
Normal file
10
config/packages/easy_admin.yaml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
easy_admin:
|
||||
site_name: 'پیشخوان ادمین'
|
||||
site_url: '/admin'
|
||||
favicon_path: 'favicon/favicon.ico'
|
||||
default_locale: 'fa'
|
||||
locales: ['fa', 'en']
|
||||
format_datetime: 'Y-m-d H:i:s'
|
||||
format_date: 'Y-m-d'
|
||||
format_time: 'H:i:s'
|
||||
timezone: 'Asia/Tehran'
|
||||
|
|
@ -19,7 +19,9 @@ security:
|
|||
provider: app_user_provider
|
||||
form_login:
|
||||
login_path: login
|
||||
check_path: login
|
||||
check_path: login_check
|
||||
enable_csrf: true
|
||||
default_target_path: admin
|
||||
logout:
|
||||
path: logout
|
||||
customer:
|
||||
|
|
@ -61,6 +63,10 @@ security:
|
|||
# Easy way to control access for large sections of your site
|
||||
# Note: Only the *first* access control that matches will be used
|
||||
access_control:
|
||||
# اجازه دسترسی عمومی به صفحه ورود و خروج ادمین
|
||||
- { path: ^/admin/login$, roles: PUBLIC_ACCESS }
|
||||
- { path: ^/admin/logout$, roles: PUBLIC_ACCESS }
|
||||
# محافظت از سایر مسیرهای /admin
|
||||
- { path: ^/admin, roles: ROLE_ADMIN }
|
||||
- { path: ^/customer/dashboard, roles: ROLE_CUSTOMER }
|
||||
# - { path: ^/profile, roles: ROLE_USER }
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
# config/routes/easyadmin.yaml
|
||||
easyadmin:
|
||||
resource: .
|
||||
type: easyadmin.routes
|
||||
|
|
@ -31,3 +31,15 @@ services:
|
|||
# Markdown extension for Twig
|
||||
App\Twig\MarkdownExtension:
|
||||
tags: ['twig.extension']
|
||||
|
||||
# Jdate extension for Twig
|
||||
App\Twig\JdateExtension:
|
||||
tags: ['twig.extension']
|
||||
|
||||
# Attachment service
|
||||
App\Service\AttachmentService:
|
||||
arguments:
|
||||
$targetDirectory: '%kernel.project_dir%/public/uploads/attachments'
|
||||
|
||||
# Email notification service
|
||||
App\Service\EmailNotificationService: ~
|
||||
31
migrations/Version20250905092845.php
Normal file
31
migrations/Version20250905092845.php
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250905092845 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE comment CHANGE name name VARCHAR(255) NOT NULL, CHANGE publish publish TINYINT(1) NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE comment CHANGE name name VARCHAR(255) DEFAULT NULL, CHANGE publish publish TINYINT(1) DEFAULT NULL');
|
||||
}
|
||||
}
|
||||
34
migrations/Version20250905122328.php
Normal file
34
migrations/Version20250905122328.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250905122328 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
// First, convert existing timestamp values to datetime format
|
||||
$this->addSql('UPDATE post SET date_submit = FROM_UNIXTIME(CAST(date_submit AS UNSIGNED)) WHERE date_submit REGEXP "^[0-9]+$"');
|
||||
// Then change the column type
|
||||
$this->addSql('ALTER TABLE post CHANGE date_submit date_submit DATETIME NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE post CHANGE date_submit date_submit VARCHAR(50) NOT NULL');
|
||||
}
|
||||
}
|
||||
34
migrations/Version20250905122520.php
Normal file
34
migrations/Version20250905122520.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250905122520 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
// First, convert existing timestamp values to datetime format
|
||||
$this->addSql('UPDATE comment SET date_submit = FROM_UNIXTIME(CAST(date_submit AS UNSIGNED)) WHERE date_submit REGEXP "^[0-9]+$"');
|
||||
// Then change the column type
|
||||
$this->addSql('ALTER TABLE comment CHANGE date_submit date_submit DATETIME NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE comment CHANGE date_submit date_submit VARCHAR(255) NOT NULL');
|
||||
}
|
||||
}
|
||||
39
migrations/Version20250905124108.php
Normal file
39
migrations/Version20250905124108.php
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250905124108 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE attachment (id INT AUTO_INCREMENT NOT NULL, question_id INT DEFAULT NULL, answer_id INT DEFAULT NULL, uploaded_by_id INT NOT NULL, filename VARCHAR(255) NOT NULL, original_filename VARCHAR(255) NOT NULL, mime_type VARCHAR(100) NOT NULL, size INT NOT NULL, path VARCHAR(500) NOT NULL, uploaded_at DATETIME NOT NULL, INDEX IDX_795FD9BB1E27F6BF (question_id), INDEX IDX_795FD9BBAA334807 (answer_id), INDEX IDX_795FD9BBA2B28FE8 (uploaded_by_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('ALTER TABLE attachment ADD CONSTRAINT FK_795FD9BB1E27F6BF FOREIGN KEY (question_id) REFERENCES question (id)');
|
||||
$this->addSql('ALTER TABLE attachment ADD CONSTRAINT FK_795FD9BBAA334807 FOREIGN KEY (answer_id) REFERENCES answer (id)');
|
||||
$this->addSql('ALTER TABLE attachment ADD CONSTRAINT FK_795FD9BBA2B28FE8 FOREIGN KEY (uploaded_by_id) REFERENCES `user` (id)');
|
||||
$this->addSql('ALTER TABLE question ADD notify_on_answer TINYINT(1) NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE attachment DROP FOREIGN KEY FK_795FD9BB1E27F6BF');
|
||||
$this->addSql('ALTER TABLE attachment DROP FOREIGN KEY FK_795FD9BBAA334807');
|
||||
$this->addSql('ALTER TABLE attachment DROP FOREIGN KEY FK_795FD9BBA2B28FE8');
|
||||
$this->addSql('DROP TABLE attachment');
|
||||
$this->addSql('ALTER TABLE question DROP notify_on_answer');
|
||||
}
|
||||
}
|
||||
63
src/Controller/Admin/CommentCrudController.php
Normal file
63
src/Controller/Admin/CommentCrudController.php
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller\Admin;
|
||||
|
||||
use App\Entity\Comment;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Filters;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Field\EmailField;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Field\UrlField;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Filter\BooleanFilter;
|
||||
use EasyCorp\Bundle\EasyAdminBundle\Filter\EntityFilter;
|
||||
|
||||
class CommentCrudController extends AbstractCrudController
|
||||
{
|
||||
public static function getEntityFqcn(): string
|
||||
{
|
||||
return Comment::class;
|
||||
}
|
||||
|
||||
public function configureFields(string $pageName): iterable
|
||||
{
|
||||
return [
|
||||
IdField::new('id', 'شناسه')->hideOnForm(),
|
||||
AssociationField::new('post', 'پست مربوطه'),
|
||||
TextField::new('name', 'نام'),
|
||||
EmailField::new('email', 'ایمیل'),
|
||||
UrlField::new('website', 'وبسایت'),
|
||||
TextareaField::new('body', 'متن کامنت')
|
||||
->setMaxLength(500)
|
||||
->hideOnIndex(),
|
||||
DateTimeField::new('dateSubmit', 'تاریخ ارسال')
|
||||
->setFormat('Y/m/d H:i:s')
|
||||
->hideOnForm(),
|
||||
BooleanField::new('publish', 'تایید شده')
|
||||
->setHelp('کامنتهای تایید شده در سایت نمایش داده میشوند'),
|
||||
];
|
||||
}
|
||||
|
||||
public function configureCrud(Crud $crud): Crud
|
||||
{
|
||||
return $crud
|
||||
->setEntityLabelInSingular('کامنت')
|
||||
->setEntityLabelInPlural('کامنتها')
|
||||
->setDefaultSort(['dateSubmit' => 'DESC'])
|
||||
->setPaginatorPageSize(20)
|
||||
->setSearchFields(['name', 'email', 'body'])
|
||||
->setHelp('index', 'کامنتهای جدید نیاز به تایید دارند تا در سایت نمایش داده شوند');
|
||||
}
|
||||
|
||||
public function configureFilters(Filters $filters): Filters
|
||||
{
|
||||
return $filters
|
||||
->add(EntityFilter::new('post', 'پست مربوطه'))
|
||||
->add(BooleanFilter::new('publish', 'وضعیت تایید'));
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Controller\Admin;
|
||||
|
||||
use App\Entity\Comment;
|
||||
use App\Entity\Post;
|
||||
use App\Entity\Tree;
|
||||
use App\Entity\User;
|
||||
|
|
@ -14,8 +15,8 @@ use Symfony\Component\Routing\Attribute\Route;
|
|||
|
||||
class DashboardController extends AbstractDashboardController
|
||||
{
|
||||
#[Route('/admin/{_locale}', name: 'admin')]
|
||||
public function index($_locale = 'fa'): Response
|
||||
#[Route('/admin', name: 'admin')]
|
||||
public function index(): Response
|
||||
{
|
||||
//return parent::index();
|
||||
|
||||
|
|
@ -45,10 +46,7 @@ class DashboardController extends AbstractDashboardController
|
|||
->renderContentMaximized()
|
||||
->setDefaultColorScheme('dark')
|
||||
->generateRelativeUrls()
|
||||
->setLocales([
|
||||
'fa' => Locale::new('fa', 'فارسی', 'fa_IR'), // زبان پیشفرض
|
||||
'en' => Locale::new('en', 'English', 'en_US'),
|
||||
]);
|
||||
;
|
||||
}
|
||||
|
||||
public function configureMenuItems(): iterable
|
||||
|
|
@ -59,6 +57,7 @@ class DashboardController extends AbstractDashboardController
|
|||
MenuItem::section('پست بلاگ'),
|
||||
MenuItem::linkToCrud('دسته بندی', 'fa fa-tags', Tree::class),
|
||||
MenuItem::linkToCrud('محتوا', 'fa fa-file-text', Post::class),
|
||||
MenuItem::linkToCrud('کامنتها', 'fa fa-comments', Comment::class),
|
||||
|
||||
MenuItem::section('کاربران'),
|
||||
MenuItem::linkToCrud('کاربران', 'fa fa-user', User::class),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Cat;
|
||||
use App\Entity\Comment;
|
||||
use App\Entity\Post;
|
||||
use App\Entity\Tree;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
|
@ -10,6 +11,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
|
||||
|
||||
class PageController extends AbstractController
|
||||
|
|
@ -20,6 +22,16 @@ class PageController extends AbstractController
|
|||
$item = $entityManagerInterface->getRepository(Post::class)->findByUrlFilterCat($url, 'plain');
|
||||
if (!$item)
|
||||
throw $this->createNotFoundException();
|
||||
|
||||
// افزایش آمار بازدید
|
||||
if (!$item->getViews())
|
||||
$item->setViews(1);
|
||||
else
|
||||
$item->setViews($item->getViews() + 1);
|
||||
|
||||
$entityManagerInterface->persist($item);
|
||||
$entityManagerInterface->flush();
|
||||
|
||||
return $this->render('post/page.html.twig', [
|
||||
'item' => $item,
|
||||
]);
|
||||
|
|
@ -76,15 +88,24 @@ class PageController extends AbstractController
|
|||
$item = $entityManagerInterface->getRepository(Post::class)->findByUrlFilterCat($url, 'blog');
|
||||
if (!$item)
|
||||
throw $this->createNotFoundException();
|
||||
|
||||
// افزایش آمار بازدید
|
||||
if (!$item->getViews())
|
||||
$item->setViews(1);
|
||||
else
|
||||
$item->setViews($item->getViews() + 1);
|
||||
|
||||
$entityManagerInterface->persist($item);
|
||||
$entityManagerInterface->flush(); // ذخیره تغییرات در دیتابیس
|
||||
|
||||
// دریافت کامنتهای تایید شده
|
||||
$comments = $entityManagerInterface->getRepository(Comment::class)
|
||||
->findBy(['post' => $item, 'publish' => true], ['dateSubmit' => 'DESC']);
|
||||
|
||||
return $this->render('post/blog_post.html.twig', [
|
||||
'item' => $item,
|
||||
'posts' => $entityManagerInterface->getRepository(Post::class)->findByCat('blog',3),
|
||||
'comments' => $comments,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -95,6 +116,15 @@ class PageController extends AbstractController
|
|||
if (!$item)
|
||||
throw $this->createNotFoundException();
|
||||
|
||||
// افزایش آمار بازدید
|
||||
if (!$item->getViews())
|
||||
$item->setViews(1);
|
||||
else
|
||||
$item->setViews($item->getViews() + 1);
|
||||
|
||||
$entityManagerInterface->persist($item);
|
||||
$entityManagerInterface->flush();
|
||||
|
||||
//get list of trees
|
||||
$tress = $entityManagerInterface->getRepository(Tree::class)->findAllByCat('api');
|
||||
return $this->render('post/api_docs.html.twig', [
|
||||
|
|
@ -110,6 +140,15 @@ class PageController extends AbstractController
|
|||
if (!$item)
|
||||
throw $this->createNotFoundException();
|
||||
|
||||
// افزایش آمار بازدید
|
||||
if (!$item->getViews())
|
||||
$item->setViews(1);
|
||||
else
|
||||
$item->setViews($item->getViews() + 1);
|
||||
|
||||
$entityManagerInterface->persist($item);
|
||||
$entityManagerInterface->flush();
|
||||
|
||||
//get list of trees
|
||||
$tress = $entityManagerInterface->getRepository(Tree::class)->findAllByCat('guide');
|
||||
return $this->render('post/guide.html.twig', [
|
||||
|
|
@ -118,6 +157,39 @@ class PageController extends AbstractController
|
|||
]);
|
||||
}
|
||||
|
||||
#[Route('/blog/post/{url}/comment', name: 'app_blog_post_comment', methods: ['POST'])]
|
||||
public function app_blog_post_comment(EntityManagerInterface $entityManagerInterface, Request $request, string $url): Response
|
||||
{
|
||||
$item = $entityManagerInterface->getRepository(Post::class)->findByUrlFilterCat($url, 'blog');
|
||||
if (!$item) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
// در Symfony 6، get فقط مقدارهای اسکالر میپذیرد؛ برای آرایه باید از all استفاده شود
|
||||
$commentData = $request->request->all('comment');
|
||||
|
||||
if (!$commentData || !isset($commentData['name']) || !isset($commentData['body'])) {
|
||||
$this->addFlash('error', 'نام و متن کامنت الزامی است.');
|
||||
return $this->redirectToRoute('app_blog_post', ['url' => $url]);
|
||||
}
|
||||
|
||||
$comment = new Comment();
|
||||
$comment->setPost($item);
|
||||
$comment->setName(trim($commentData['name']));
|
||||
$comment->setBody(trim($commentData['body']));
|
||||
$comment->setEmail(trim($commentData['email'] ?? ''));
|
||||
$comment->setWebsite(trim($commentData['website'] ?? ''));
|
||||
$comment->setDateSubmit(date('Y-m-d H:i:s'));
|
||||
$comment->setPublish(false); // نیاز به تایید مدیر
|
||||
|
||||
$entityManagerInterface->persist($comment);
|
||||
$entityManagerInterface->flush();
|
||||
|
||||
$this->addFlash('success', 'نظر شما با موفقیت ارسال شد و پس از تایید مدیر نمایش داده خواهد شد.');
|
||||
|
||||
return $this->redirectToRoute('app_blog_post', ['url' => $url]);
|
||||
}
|
||||
|
||||
#[Route('/changes', name: 'app_changes')]
|
||||
public function app_changes(EntityManagerInterface $entityManagerInterface): Response
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ use App\Repository\QuestionRepository;
|
|||
use App\Repository\QuestionTagRepository;
|
||||
use App\Repository\QuestionVoteRepository;
|
||||
use App\Repository\AnswerVoteRepository;
|
||||
use App\Service\AttachmentService;
|
||||
use App\Service\EmailNotificationService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
|
@ -32,7 +34,9 @@ class QAController extends AbstractController
|
|||
private AnswerRepository $answerRepository,
|
||||
private QuestionTagRepository $tagRepository,
|
||||
private QuestionVoteRepository $questionVoteRepository,
|
||||
private AnswerVoteRepository $answerVoteRepository
|
||||
private AnswerVoteRepository $answerVoteRepository,
|
||||
private AttachmentService $attachmentService,
|
||||
private EmailNotificationService $emailNotificationService
|
||||
) {}
|
||||
|
||||
#[Route('', name: 'index', methods: ['GET'])]
|
||||
|
|
@ -148,6 +152,15 @@ class QAController extends AbstractController
|
|||
}
|
||||
}
|
||||
|
||||
// مدیریت پیوست فایلها
|
||||
$attachments = $form->get('attachments')->getData();
|
||||
if ($attachments) {
|
||||
$uploadedAttachments = $this->attachmentService->uploadAttachments($attachments, $this->getUser(), $question);
|
||||
foreach ($uploadedAttachments as $attachment) {
|
||||
$question->addAttachment($attachment);
|
||||
}
|
||||
}
|
||||
|
||||
$this->entityManager->persist($question);
|
||||
$this->entityManager->flush();
|
||||
|
||||
|
|
@ -181,9 +194,21 @@ class QAController extends AbstractController
|
|||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
// مدیریت پیوست فایلها
|
||||
$attachments = $form->get('attachments')->getData();
|
||||
if ($attachments) {
|
||||
$uploadedAttachments = $this->attachmentService->uploadAttachments($attachments, $this->getUser(), null, $answer);
|
||||
foreach ($uploadedAttachments as $attachment) {
|
||||
$answer->addAttachment($attachment);
|
||||
}
|
||||
}
|
||||
|
||||
$this->entityManager->persist($answer);
|
||||
$this->entityManager->flush();
|
||||
|
||||
// ارسال ایمیل اطلاعرسانی
|
||||
$this->emailNotificationService->sendAnswerNotification($answer);
|
||||
|
||||
$this->addFlash('success', 'پاسخ شما با موفقیت ثبت شد.');
|
||||
return $this->redirectToRoute('qa_question_show', ['id' => $question->getId()]);
|
||||
}
|
||||
|
|
@ -343,6 +368,11 @@ class QAController extends AbstractController
|
|||
|
||||
$this->entityManager->flush();
|
||||
|
||||
// ارسال ایمیل اطلاعرسانی در صورت پذیرش پاسخ
|
||||
if ($answer->isAccepted()) {
|
||||
$this->emailNotificationService->sendAnswerAcceptedNotification($answer);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'accepted' => $answer->isAccepted(),
|
||||
'solved' => $question->isSolved()
|
||||
|
|
|
|||
|
|
@ -8,30 +8,25 @@ use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
|||
|
||||
class SecurityController extends AbstractController
|
||||
{
|
||||
#[Route('/hs/login', name: 'login')]
|
||||
#[Route('/admin/login', name: 'login')]
|
||||
public function login(AuthenticationUtils $authenticationUtils): Response
|
||||
{
|
||||
$error = $authenticationUtils->getLastAuthenticationError();
|
||||
$lastUsername = $authenticationUtils->getLastUsername();
|
||||
|
||||
return $this->render('/admin/login.html.twig', [
|
||||
return $this->render('admin/login.html.twig', [
|
||||
'error' => $error,
|
||||
'last_username' => $lastUsername,
|
||||
'translation_domain' => 'admin',
|
||||
'page_title' => 'ورود',
|
||||
'csrf_token_intention' => 'authenticate',
|
||||
'target_path' => $this->generateUrl('admin', ['_locale' => 'fa']),
|
||||
'username_label' => 'پست الکترونیکی',
|
||||
'password_label' => 'کلمه عبور',
|
||||
'sign_in_label' => 'ورود',
|
||||
'forgot_password_enabled' => false,
|
||||
'remember_me_enabled' => true,
|
||||
'remember_me_checked' => true,
|
||||
'remember_me_label' => 'مرا به یاد داشته باش',
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/logout', name: 'logout')]
|
||||
#[Route('/admin/login_check', name: 'login_check')]
|
||||
public function loginCheck(): void
|
||||
{
|
||||
// This method can be blank - it will be intercepted by the logout key on your firewall.
|
||||
}
|
||||
|
||||
#[Route('/admin/logout', name: 'logout')]
|
||||
public function logout(AuthenticationUtils $authenticationUtils): Response
|
||||
{
|
||||
return $this->redirectToRoute('app_home');
|
||||
|
|
|
|||
|
|
@ -52,9 +52,16 @@ class Answer
|
|||
#[ORM\OneToMany(targetEntity: AnswerVote::class, mappedBy: 'answer', orphanRemoval: true)]
|
||||
private Collection $answerVotes;
|
||||
|
||||
/**
|
||||
* @var Collection<int, Attachment>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: Attachment::class, mappedBy: 'answer', orphanRemoval: true, cascade: ['persist'])]
|
||||
private Collection $attachments;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->answerVotes = new ArrayCollection();
|
||||
$this->attachments = new ArrayCollection();
|
||||
$this->createdAt = new \DateTime();
|
||||
}
|
||||
|
||||
|
|
@ -177,4 +184,36 @@ class Answer
|
|||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Attachment>
|
||||
*/
|
||||
public function getAttachments(): Collection
|
||||
{
|
||||
return $this->attachments;
|
||||
}
|
||||
|
||||
public function addAttachment(Attachment $attachment): static
|
||||
{
|
||||
if (!$this->attachments->contains($attachment)) {
|
||||
$this->attachments->add($attachment);
|
||||
$attachment->setAnswer($this);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeAttachment(Attachment $attachment): static
|
||||
{
|
||||
if ($this->attachments->removeElement($attachment)) {
|
||||
if ($attachment->getAnswer() === $this) {
|
||||
$attachment->setAnswer(null);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAttachmentsCount(): int
|
||||
{
|
||||
return $this->attachments->count();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
195
src/Entity/Attachment.php
Normal file
195
src/Entity/Attachment.php
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\AttachmentRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Entity(repositoryClass: AttachmentRepository::class)]
|
||||
#[ORM\Table(name: 'attachment')]
|
||||
class Attachment
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $filename = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $originalFilename = null;
|
||||
|
||||
#[ORM\Column(length: 100)]
|
||||
private ?string $mimeType = null;
|
||||
|
||||
#[ORM\Column(type: Types::INTEGER)]
|
||||
private ?int $size = null;
|
||||
|
||||
#[ORM\Column(length: 500)]
|
||||
private ?string $path = null;
|
||||
|
||||
#[ORM\Column(type: 'datetime')]
|
||||
private ?\DateTimeInterface $uploadedAt = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: Question::class, inversedBy: 'attachments')]
|
||||
#[ORM\JoinColumn(nullable: true)]
|
||||
private ?Question $question = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: Answer::class, inversedBy: 'attachments')]
|
||||
#[ORM\JoinColumn(nullable: true)]
|
||||
private ?Answer $answer = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: User::class)]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?User $uploadedBy = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->uploadedAt = new \DateTime();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getFilename(): ?string
|
||||
{
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
public function setFilename(string $filename): static
|
||||
{
|
||||
$this->filename = $filename;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOriginalFilename(): ?string
|
||||
{
|
||||
return $this->originalFilename;
|
||||
}
|
||||
|
||||
public function setOriginalFilename(string $originalFilename): static
|
||||
{
|
||||
$this->originalFilename = $originalFilename;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMimeType(): ?string
|
||||
{
|
||||
return $this->mimeType;
|
||||
}
|
||||
|
||||
public function setMimeType(string $mimeType): static
|
||||
{
|
||||
$this->mimeType = $mimeType;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSize(): ?int
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
public function setSize(int $size): static
|
||||
{
|
||||
$this->size = $size;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPath(): ?string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function setPath(string $path): static
|
||||
{
|
||||
$this->path = $path;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUploadedAt(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->uploadedAt;
|
||||
}
|
||||
|
||||
public function setUploadedAt(\DateTimeInterface $uploadedAt): static
|
||||
{
|
||||
$this->uploadedAt = $uploadedAt;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getQuestion(): ?Question
|
||||
{
|
||||
return $this->question;
|
||||
}
|
||||
|
||||
public function setQuestion(?Question $question): static
|
||||
{
|
||||
$this->question = $question;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAnswer(): ?Answer
|
||||
{
|
||||
return $this->answer;
|
||||
}
|
||||
|
||||
public function setAnswer(?Answer $answer): static
|
||||
{
|
||||
$this->answer = $answer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUploadedBy(): ?User
|
||||
{
|
||||
return $this->uploadedBy;
|
||||
}
|
||||
|
||||
public function setUploadedBy(?User $uploadedBy): static
|
||||
{
|
||||
$this->uploadedBy = $uploadedBy;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFormattedSize(): string
|
||||
{
|
||||
$bytes = $this->size;
|
||||
$units = ['B', 'KB', 'MB', 'GB'];
|
||||
|
||||
for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
|
||||
$bytes /= 1024;
|
||||
}
|
||||
|
||||
return round($bytes, 2) . ' ' . $units[$i];
|
||||
}
|
||||
|
||||
public function isImage(): bool
|
||||
{
|
||||
return str_starts_with($this->mimeType ?? '', 'image/');
|
||||
}
|
||||
|
||||
public function isPdf(): bool
|
||||
{
|
||||
return $this->mimeType === 'application/pdf';
|
||||
}
|
||||
|
||||
public function isDocument(): bool
|
||||
{
|
||||
$documentTypes = [
|
||||
'application/pdf',
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.ms-powerpoint',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'text/plain'
|
||||
];
|
||||
|
||||
return in_array($this->mimeType, $documentTypes);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ namespace App\Entity;
|
|||
use App\Repository\CommentRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Entity(repositoryClass: CommentRepository::class)]
|
||||
class Comment
|
||||
|
|
@ -19,22 +20,33 @@ class Comment
|
|||
private ?Post $post = null;
|
||||
|
||||
#[ORM\Column(type: Types::TEXT)]
|
||||
#[Assert\NotBlank(message: 'متن کامنت الزامی است')]
|
||||
#[Assert\Length(min: 10, minMessage: 'متن کامنت باید حداقل 10 کاراکتر باشد')]
|
||||
private ?string $body = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
#[ORM\Column(length: 255)]
|
||||
#[Assert\NotBlank(message: 'نام الزامی است')]
|
||||
#[Assert\Length(min: 2, max: 255, minMessage: 'نام باید حداقل 2 کاراکتر باشد', maxMessage: 'نام نمیتواند بیش از 255 کاراکتر باشد')]
|
||||
private ?string $name = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
#[Assert\Email(message: 'ایمیل معتبر نیست')]
|
||||
private ?string $email = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
#[Assert\Url(message: 'آدرس وبسایت معتبر نیست')]
|
||||
private ?string $website = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $dateSubmit = null;
|
||||
#[ORM\Column(type: 'datetime')]
|
||||
private ?\DateTimeInterface $dateSubmit = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?bool $publish = null;
|
||||
#[ORM\Column]
|
||||
private bool $publish = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->dateSubmit = new \DateTime();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
|
|
@ -101,24 +113,24 @@ class Comment
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getDateSubmit(): ?string
|
||||
public function getDateSubmit(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->dateSubmit;
|
||||
}
|
||||
|
||||
public function setDateSubmit(string $dateSubmit): static
|
||||
public function setDateSubmit(?\DateTimeInterface $dateSubmit): static
|
||||
{
|
||||
$this->dateSubmit = $dateSubmit;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isPublish(): ?bool
|
||||
public function isPublish(): bool
|
||||
{
|
||||
return $this->publish;
|
||||
}
|
||||
|
||||
public function setPublish(?bool $publish): static
|
||||
public function setPublish(bool $publish): static
|
||||
{
|
||||
$this->publish = $publish;
|
||||
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ class Post
|
|||
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||
private ?string $body = null;
|
||||
|
||||
#[ORM\Column(length: 50)]
|
||||
private ?string $dateSubmit = null;
|
||||
#[ORM\Column(type: 'datetime')]
|
||||
private ?\DateTimeInterface $dateSubmit = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?bool $publish = null;
|
||||
|
|
@ -82,6 +82,7 @@ class Post
|
|||
{
|
||||
$this->tree = new ArrayCollection();
|
||||
$this->comments = new ArrayCollection();
|
||||
$this->dateSubmit = new \DateTime();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
|
|
@ -122,12 +123,12 @@ class Post
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getDateSubmit(): ?string
|
||||
public function getDateSubmit(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->dateSubmit;
|
||||
}
|
||||
|
||||
public function setDateSubmit(string $dateSubmit): static
|
||||
public function setDateSubmit(?\DateTimeInterface $dateSubmit): static
|
||||
{
|
||||
$this->dateSubmit = $dateSubmit;
|
||||
return $this;
|
||||
|
|
|
|||
|
|
@ -50,12 +50,21 @@ class Question
|
|||
#[ORM\Column(type: 'boolean')]
|
||||
private bool $isActive = true;
|
||||
|
||||
#[ORM\Column(type: 'boolean')]
|
||||
private bool $notifyOnAnswer = false;
|
||||
|
||||
/**
|
||||
* @var Collection<int, Answer>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: Answer::class, mappedBy: 'question', orphanRemoval: true, cascade: ['persist'])]
|
||||
private Collection $answers;
|
||||
|
||||
/**
|
||||
* @var Collection<int, Attachment>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: Attachment::class, mappedBy: 'question', orphanRemoval: true, cascade: ['persist'])]
|
||||
private Collection $attachments;
|
||||
|
||||
/**
|
||||
* @var Collection<int, QuestionVote>
|
||||
*/
|
||||
|
|
@ -73,6 +82,7 @@ class Question
|
|||
$this->answers = new ArrayCollection();
|
||||
$this->questionVotes = new ArrayCollection();
|
||||
$this->tagRelations = new ArrayCollection();
|
||||
$this->attachments = new ArrayCollection();
|
||||
$this->createdAt = new \DateTime();
|
||||
}
|
||||
|
||||
|
|
@ -281,4 +291,47 @@ class Question
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isNotifyOnAnswer(): bool
|
||||
{
|
||||
return $this->notifyOnAnswer;
|
||||
}
|
||||
|
||||
public function setNotifyOnAnswer(bool $notifyOnAnswer): static
|
||||
{
|
||||
$this->notifyOnAnswer = $notifyOnAnswer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Attachment>
|
||||
*/
|
||||
public function getAttachments(): Collection
|
||||
{
|
||||
return $this->attachments;
|
||||
}
|
||||
|
||||
public function addAttachment(Attachment $attachment): static
|
||||
{
|
||||
if (!$this->attachments->contains($attachment)) {
|
||||
$this->attachments->add($attachment);
|
||||
$attachment->setQuestion($this);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeAttachment(Attachment $attachment): static
|
||||
{
|
||||
if ($this->attachments->removeElement($attachment)) {
|
||||
if ($attachment->getQuestion() === $this) {
|
||||
$attachment->setQuestion(null);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAttachmentsCount(): int
|
||||
{
|
||||
return $this->attachments->count();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ namespace App\Form\QA;
|
|||
|
||||
use App\Entity\Answer;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
|
@ -28,6 +29,43 @@ class AnswerFormType extends AbstractType
|
|||
'minMessage' => 'پاسخ باید حداقل 10 کاراکتر باشد'
|
||||
])
|
||||
]
|
||||
])
|
||||
->add('attachments', FileType::class, [
|
||||
'label' => 'پیوست فایل (حداکثر 3 فایل، هر کدام 4 مگابایت)',
|
||||
'mapped' => false,
|
||||
'multiple' => true,
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'class' => 'form-control',
|
||||
'accept' => '.jpg,.jpeg,.png,.gif,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt',
|
||||
'multiple' => true
|
||||
],
|
||||
'constraints' => [
|
||||
new Assert\Count([
|
||||
'max' => 3,
|
||||
'maxMessage' => 'حداکثر 3 فایل میتوانید پیوست کنید'
|
||||
]),
|
||||
new Assert\All([
|
||||
new Assert\File([
|
||||
'maxSize' => '4M',
|
||||
'maxSizeMessage' => 'حجم هر فایل نمیتواند بیش از 4 مگابایت باشد',
|
||||
'mimeTypes' => [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'application/pdf',
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.ms-powerpoint',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'text/plain'
|
||||
],
|
||||
'mimeTypesMessage' => 'فرمت فایل مجاز نیست. فرمتهای مجاز: JPG, PNG, GIF, PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX, TXT'
|
||||
])
|
||||
])
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ use App\Entity\Question;
|
|||
use App\Entity\QuestionTag;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
|
@ -48,6 +50,70 @@ class QuestionFormType extends AbstractType
|
|||
'minMessage' => 'متن سوال باید حداقل 20 کاراکتر باشد'
|
||||
])
|
||||
]
|
||||
])
|
||||
->add('notifyOnAnswer', CheckboxType::class, [
|
||||
'label' => 'اطلاعرسانی ایمیل هنگام دریافت پاسخ',
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'class' => 'form-check-input'
|
||||
]
|
||||
])
|
||||
->add('tags', EntityType::class, [
|
||||
'class' => QuestionTag::class,
|
||||
'choice_label' => 'name',
|
||||
'multiple' => true,
|
||||
'expanded' => false,
|
||||
'required' => false,
|
||||
'mapped' => false,
|
||||
'attr' => [
|
||||
'class' => 'form-control',
|
||||
'style' => 'display: none;' // مخفی کردن چون با JavaScript مدیریت میشود
|
||||
],
|
||||
'constraints' => [
|
||||
new Assert\Count([
|
||||
'min' => 1,
|
||||
'max' => 5,
|
||||
'minMessage' => 'حداقل 1 تگ باید انتخاب کنید',
|
||||
'maxMessage' => 'حداکثر 5 تگ میتوانید انتخاب کنید'
|
||||
])
|
||||
]
|
||||
])
|
||||
->add('attachments', FileType::class, [
|
||||
'label' => 'پیوست فایل (حداکثر 3 فایل، هر کدام 4 مگابایت)',
|
||||
'mapped' => false,
|
||||
'multiple' => true,
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'class' => 'form-control',
|
||||
'accept' => '.jpg,.jpeg,.png,.gif,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt',
|
||||
'multiple' => true
|
||||
],
|
||||
'constraints' => [
|
||||
new Assert\Count([
|
||||
'max' => 3,
|
||||
'maxMessage' => 'حداکثر 3 فایل میتوانید پیوست کنید'
|
||||
]),
|
||||
new Assert\All([
|
||||
new Assert\File([
|
||||
'maxSize' => '4M',
|
||||
'maxSizeMessage' => 'حجم هر فایل نمیتواند بیش از 4 مگابایت باشد',
|
||||
'mimeTypes' => [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'application/pdf',
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.ms-powerpoint',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'text/plain'
|
||||
],
|
||||
'mimeTypesMessage' => 'فرمت فایل مجاز نیست. فرمتهای مجاز: JPG, PNG, GIF, PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX, TXT'
|
||||
])
|
||||
])
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
69
src/Repository/AttachmentRepository.php
Normal file
69
src/Repository/AttachmentRepository.php
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Attachment;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<Attachment>
|
||||
*/
|
||||
class AttachmentRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, Attachment::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find attachments by question
|
||||
*/
|
||||
public function findByQuestion(int $questionId): array
|
||||
{
|
||||
return $this->createQueryBuilder('a')
|
||||
->where('a.question = :questionId')
|
||||
->setParameter('questionId', $questionId)
|
||||
->orderBy('a.uploadedAt', 'ASC')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find attachments by answer
|
||||
*/
|
||||
public function findByAnswer(int $answerId): array
|
||||
{
|
||||
return $this->createQueryBuilder('a')
|
||||
->where('a.answer = :answerId')
|
||||
->setParameter('answerId', $answerId)
|
||||
->orderBy('a.uploadedAt', 'ASC')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find orphaned attachments (not linked to any question or answer)
|
||||
*/
|
||||
public function findOrphanedAttachments(): array
|
||||
{
|
||||
return $this->createQueryBuilder('a')
|
||||
->where('a.question IS NULL')
|
||||
->andWhere('a.answer IS NULL')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Count attachments by user
|
||||
*/
|
||||
public function countByUser(int $userId): int
|
||||
{
|
||||
return $this->createQueryBuilder('a')
|
||||
->select('COUNT(a.id)')
|
||||
->where('a.uploadedBy = :userId')
|
||||
->setParameter('userId', $userId)
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
}
|
||||
149
src/Service/AttachmentService.php
Normal file
149
src/Service/AttachmentService.php
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\Attachment;
|
||||
use App\Entity\Answer;
|
||||
use App\Entity\Question;
|
||||
use App\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\String\Slugger\SluggerInterface;
|
||||
|
||||
class AttachmentService
|
||||
{
|
||||
private string $targetDirectory;
|
||||
private EntityManagerInterface $entityManager;
|
||||
private SluggerInterface $slugger;
|
||||
|
||||
public function __construct(
|
||||
string $targetDirectory,
|
||||
EntityManagerInterface $entityManager,
|
||||
SluggerInterface $slugger
|
||||
) {
|
||||
$this->targetDirectory = $targetDirectory;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->slugger = $slugger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload multiple files and create attachment entities
|
||||
*/
|
||||
public function uploadAttachments(array $uploadedFiles, User $user, ?Question $question = null, ?Answer $answer = null): array
|
||||
{
|
||||
$attachments = [];
|
||||
|
||||
foreach ($uploadedFiles as $uploadedFile) {
|
||||
if ($uploadedFile instanceof UploadedFile) {
|
||||
$attachment = $this->uploadSingleFile($uploadedFile, $user, $question, $answer);
|
||||
if ($attachment) {
|
||||
$attachments[] = $attachment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a single file and create attachment entity
|
||||
*/
|
||||
public function uploadSingleFile(UploadedFile $file, User $user, ?Question $question = null, ?Answer $answer = null): ?Attachment
|
||||
{
|
||||
$originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
|
||||
$safeFilename = $this->slugger->slug($originalFilename);
|
||||
$fileName = $safeFilename . '-' . uniqid() . '.' . $file->guessExtension();
|
||||
|
||||
try {
|
||||
$file->move($this->targetDirectory, $fileName);
|
||||
} catch (FileException $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$attachment = new Attachment();
|
||||
$attachment->setFilename($fileName);
|
||||
$attachment->setOriginalFilename($file->getClientOriginalName());
|
||||
$attachment->setMimeType($file->getMimeType());
|
||||
$attachment->setSize($file->getSize());
|
||||
$attachment->setPath($this->targetDirectory . '/' . $fileName);
|
||||
$attachment->setUploadedBy($user);
|
||||
|
||||
if ($question) {
|
||||
$attachment->setQuestion($question);
|
||||
}
|
||||
if ($answer) {
|
||||
$attachment->setAnswer($answer);
|
||||
}
|
||||
|
||||
$this->entityManager->persist($attachment);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete attachment file and entity
|
||||
*/
|
||||
public function deleteAttachment(Attachment $attachment): bool
|
||||
{
|
||||
try {
|
||||
// Delete file from filesystem
|
||||
if (file_exists($attachment->getPath())) {
|
||||
unlink($attachment->getPath());
|
||||
}
|
||||
|
||||
// Remove from database
|
||||
$this->entityManager->remove($attachment);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attachment URL for display
|
||||
*/
|
||||
public function getAttachmentUrl(Attachment $attachment): string
|
||||
{
|
||||
return '/uploads/attachments/' . $attachment->getFilename();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get allowed file types
|
||||
*/
|
||||
public function getAllowedMimeTypes(): array
|
||||
{
|
||||
return [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'application/pdf',
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.ms-powerpoint',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'text/plain'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get max file size in bytes
|
||||
*/
|
||||
public function getMaxFileSize(): int
|
||||
{
|
||||
return 4 * 1024 * 1024; // 4MB
|
||||
}
|
||||
|
||||
/**
|
||||
* Get max number of files
|
||||
*/
|
||||
public function getMaxFiles(): int
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
95
src/Service/EmailNotificationService.php
Normal file
95
src/Service/EmailNotificationService.php
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\Answer;
|
||||
use App\Entity\Question;
|
||||
use Symfony\Component\Mailer\MailerInterface;
|
||||
use Symfony\Component\Mime\Email;
|
||||
use Twig\Environment;
|
||||
|
||||
class EmailNotificationService
|
||||
{
|
||||
private MailerInterface $mailer;
|
||||
private Environment $twig;
|
||||
|
||||
public function __construct(MailerInterface $mailer, Environment $twig)
|
||||
{
|
||||
$this->mailer = $mailer;
|
||||
$this->twig = $twig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send email notification when a new answer is posted
|
||||
*/
|
||||
public function sendAnswerNotification(Answer $answer): bool
|
||||
{
|
||||
$question = $answer->getQuestion();
|
||||
|
||||
// Only send notification if question author wants to be notified
|
||||
if (!$question->isNotifyOnAnswer()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$questionAuthor = $question->getAuthor();
|
||||
$answerAuthor = $answer->getAuthor();
|
||||
|
||||
// Don't send notification if the answer is from the same person who asked the question
|
||||
if ($questionAuthor->getId() === $answerAuthor->getId()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$email = (new Email())
|
||||
->from('noreply@hesabix.ir')
|
||||
->to($questionAuthor->getEmail())
|
||||
->subject('پاسخ جدید برای سوال شما: ' . $question->getTitle())
|
||||
->html($this->twig->render('emails/answer_notification.html.twig', [
|
||||
'question' => $question,
|
||||
'answer' => $answer,
|
||||
'questionAuthor' => $questionAuthor,
|
||||
'answerAuthor' => $answerAuthor
|
||||
]));
|
||||
|
||||
$this->mailer->send($email);
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
// Log error if needed
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send email notification when a question is accepted
|
||||
*/
|
||||
public function sendAnswerAcceptedNotification(Answer $answer): bool
|
||||
{
|
||||
$question = $answer->getQuestion();
|
||||
$answerAuthor = $answer->getAuthor();
|
||||
$questionAuthor = $question->getAuthor();
|
||||
|
||||
// Don't send notification if the answer is from the same person who asked the question
|
||||
if ($questionAuthor->getId() === $answerAuthor->getId()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$email = (new Email())
|
||||
->from('noreply@hesabix.ir')
|
||||
->to($answerAuthor->getEmail())
|
||||
->subject('پاسخ شما پذیرفته شد: ' . $question->getTitle())
|
||||
->html($this->twig->render('emails/answer_accepted_notification.html.twig', [
|
||||
'question' => $question,
|
||||
'answer' => $answer,
|
||||
'questionAuthor' => $questionAuthor,
|
||||
'answerAuthor' => $answerAuthor
|
||||
]));
|
||||
|
||||
$this->mailer->send($email);
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
// Log error if needed
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/Twig/JdateExtension.php
Normal file
37
src/Twig/JdateExtension.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\Twig;
|
||||
|
||||
use App\Service\Jdate;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFilter;
|
||||
|
||||
class JdateExtension extends AbstractExtension
|
||||
{
|
||||
private Jdate $jdate;
|
||||
|
||||
public function __construct(Jdate $jdate)
|
||||
{
|
||||
$this->jdate = $jdate;
|
||||
}
|
||||
|
||||
public function getFilters(): array
|
||||
{
|
||||
return [
|
||||
new TwigFilter('jdate', [$this, 'formatJdate']),
|
||||
];
|
||||
}
|
||||
|
||||
public function formatJdate($timestamp, string $format = 'Y/m/d H:i'): string
|
||||
{
|
||||
if (is_string($timestamp)) {
|
||||
$timestamp = (int) $timestamp;
|
||||
}
|
||||
|
||||
if (!$timestamp) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->jdate->jdate($format, $timestamp);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,68 @@
|
|||
{# templates/easy_admin/page/login.html.twig #}
|
||||
{% extends '@!EasyAdmin/page/login.html.twig' %}
|
||||
{% extends 'customer/base.html.twig' %}
|
||||
|
||||
{% block head %}
|
||||
{{ parent() }}
|
||||
<link rel="stylesheet" href="/css/login.css">
|
||||
{% block page_title %}ورود به پنل مدیریت{% endblock %}
|
||||
{% block page_subtitle %}وارد پنل مدیریت حسابیکس شوید{% endblock %}
|
||||
|
||||
{% block auth_content %}
|
||||
{% if error %}
|
||||
<div class="mb-6 p-4 bg-red-50 border border-red-200 rounded-xl text-red-800 text-right" role="alert">
|
||||
<div class="flex items-center">
|
||||
<img src="{{ asset('/img/icons/exclamation-circle.svg') }}" alt="خطا" class="icon-svg icon-exclamation ml-2">
|
||||
{{ error.messageKey|trans(error.messageData, 'security', 'fa') }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="{{ path('login_check') }}" class="space-y-6">
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label for="inputEmail" class="block text-sm font-medium text-gray-700 mb-2">پست الکترونیکی</label>
|
||||
<input type="email"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 text-left"
|
||||
id="inputEmail"
|
||||
name="_username"
|
||||
value="{{ last_username }}"
|
||||
placeholder="ایمیل خود را وارد کنید"
|
||||
required
|
||||
autofocus
|
||||
dir="ltr">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="inputPassword" class="block text-sm font-medium text-gray-700 mb-2">کلمه عبور</label>
|
||||
<input type="password"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 text-left"
|
||||
id="inputPassword"
|
||||
name="_password"
|
||||
placeholder="کلمه عبور خود را وارد کنید"
|
||||
required
|
||||
dir="ltr">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 focus:ring-2 ml-3"
|
||||
type="checkbox"
|
||||
id="remember_me"
|
||||
name="_remember_me"
|
||||
checked>
|
||||
<label for="remember_me" class="text-sm text-gray-700">
|
||||
مرا به یاد داشته باش
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
|
||||
|
||||
<button class="w-full bg-gradient-to-r from-blue-600 to-purple-600 text-white py-3 px-6 rounded-xl font-semibold hover:from-blue-700 hover:to-purple-700 transform hover:-translate-y-0.5 transition-all duration-200 shadow-lg hover:shadow-xl flex items-center justify-center gap-2" type="submit">
|
||||
<img src="{{ asset('/img/icons/sign-in.svg') }}" alt="ورود" class="icon-svg icon-sign-in">
|
||||
ورود به پنل مدیریت
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="mt-8 text-center space-y-4">
|
||||
<a href="{{ path('app_home') }}" class="inline-flex items-center text-blue-600 hover:text-blue-700 font-medium transition-colors duration-200">
|
||||
<img src="{{ asset('/img/icons/home.svg') }}" alt="خانه" class="icon-svg icon-home ml-2">
|
||||
بازگشت به صفحه اصلی
|
||||
</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -398,6 +398,18 @@
|
|||
</div>
|
||||
<span class="text-sm">داشبورد</span>
|
||||
</a>
|
||||
{% if is_granted('ROLE_ADMIN') %}
|
||||
<a href="{{ path('admin') }}"
|
||||
class="flex items-center space-x-3 space-x-reverse px-4 py-3 text-gray-700 hover:bg-green-50 hover:text-green-600 transition-colors duration-200">
|
||||
<div class="w-7 h-7 bg-green-100 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-3.5 h-3.5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-sm">پنل مدیریت</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
<div class="px-4 py-2">
|
||||
<a href="{{ path('customer_logout') }}"
|
||||
class="flex items-center space-x-3 space-x-reverse px-3 py-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors duration-200">
|
||||
|
|
@ -470,6 +482,27 @@
|
|||
class="block px-4 py-3 text-gray-700 hover:bg-blue-50 hover:text-blue-600 rounded-lg transition-colors duration-200">
|
||||
تماس با ما
|
||||
</a>
|
||||
|
||||
{% if app.user and app.user.roles is defined and 'ROLE_CUSTOMER' in app.user.roles %}
|
||||
<div class="border-t border-gray-200 pt-4 mt-4">
|
||||
<div class="px-4 py-2 text-sm font-medium text-gray-900">{{ app.user.name }}</div>
|
||||
<div class="px-4 py-1 text-xs text-gray-500 mb-2">عضو باشگاه مشتریان</div>
|
||||
<a href="{{ path('customer_dashboard') }}"
|
||||
class="block px-4 py-3 text-gray-700 hover:bg-blue-50 hover:text-blue-600 rounded-lg transition-colors duration-200">
|
||||
داشبورد
|
||||
</a>
|
||||
{% if is_granted('ROLE_ADMIN') %}
|
||||
<a href="{{ path('admin') }}"
|
||||
class="block px-4 py-3 text-gray-700 hover:bg-green-50 hover:text-green-600 rounded-lg transition-colors duration-200">
|
||||
پنل مدیریت
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{{ path('customer_logout') }}"
|
||||
class="block px-4 py-3 text-red-600 hover:bg-red-50 rounded-lg transition-colors duration-200">
|
||||
خروج
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,194 +5,6 @@
|
|||
{% block stylesheets %}
|
||||
{{ parent() }}
|
||||
<style>
|
||||
|
||||
.customer-auth-container {
|
||||
min-height: 80vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 20px 0;
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.customer-auth-card {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.customer-auth-header {
|
||||
background: linear-gradient(135deg, #0d6efd 0%, #6610f2 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.customer-auth-header h1 {
|
||||
margin: 0;
|
||||
font-size: 1.8rem;
|
||||
font-weight: bold;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.customer-auth-header p {
|
||||
margin: 10px 0 0 0;
|
||||
opacity: 0.9;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.customer-auth-body {
|
||||
padding: 40px 30px;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.form-floating {
|
||||
margin-bottom: 20px;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.form-floating label {
|
||||
color: #6c757d;
|
||||
right: 0.75rem;
|
||||
left: auto;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
border: 2px solid #e9ecef;
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
font-size: 16px;
|
||||
transition: all 0.3s ease;
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: #0d6efd;
|
||||
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
/* فیلدهای انگلیسی (ایمیل و کلمه عبور) */
|
||||
.form-control[type="email"],
|
||||
.form-control[type="password"] {
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.form-control[type="email"] + label,
|
||||
.form-control[type="password"] + label {
|
||||
right: auto;
|
||||
left: 0.75rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #0d6efd 0%, #6610f2 100%);
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s ease;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(13, 110, 253, 0.4);
|
||||
}
|
||||
|
||||
.form-check {
|
||||
margin: 20px 0;
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.form-check-input {
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.form-check-input:checked {
|
||||
background-color: #0d6efd;
|
||||
border-color: #0d6efd;
|
||||
}
|
||||
|
||||
.form-check-label {
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.auth-links {
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.auth-links a {
|
||||
color: #0d6efd;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.auth-links a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.alert {
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
padding: 15px 20px;
|
||||
margin-bottom: 20px;
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: #d1edff;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background-color: #d1ecf1;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
.hesabix-logo {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.back-to-home {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
opacity: 0.9;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.back-to-home:hover {
|
||||
color: white;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* بهبود نمایش آیکونهای SVG */
|
||||
.icon-svg {
|
||||
width: 16px;
|
||||
|
|
@ -238,40 +50,47 @@
|
|||
.icon-svg-large.icon-heart svg { fill: #e74c3c; }
|
||||
.icon-svg-large.icon-key svg { fill: #9b59b6; }
|
||||
.icon-svg-large.icon-lock svg { fill: #95a5a6; }
|
||||
|
||||
/* بهبود نمایش متنهای فارسی */
|
||||
.text-muted {
|
||||
direction: rtl;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="customer-auth-container">
|
||||
<div class="customer-auth-card">
|
||||
<div class="customer-auth-header">
|
||||
<img src="{{ asset('/favicon/favicon.svg') }}" alt="حسابیکس" class="hesabix-logo">
|
||||
<h1>باشگاه مشتریان حسابیکس</h1>
|
||||
<p>{{ block('page_subtitle') }}</p>
|
||||
<div class="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-500 via-purple-600 to-indigo-700 py-20 px-4" dir="rtl">
|
||||
<div class="w-full max-w-md">
|
||||
<div class="bg-white rounded-2xl shadow-2xl overflow-hidden">
|
||||
<!-- هدر -->
|
||||
<div class="bg-gradient-to-r from-blue-600 to-purple-600 text-white p-8 text-center">
|
||||
<img src="{{ asset('/favicon/favicon.svg') }}" alt="حسابیکس" class="w-16 h-16 mx-auto mb-4">
|
||||
<h1 class="text-2xl font-bold mb-2">باشگاه مشتریان حسابیکس</h1>
|
||||
<p class="text-blue-100">{{ block('page_subtitle') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="customer-auth-body">
|
||||
<!-- محتوا -->
|
||||
<div class="p-8">
|
||||
<!-- پیامهای فلش -->
|
||||
{% for message in app.flashes('success') %}
|
||||
<div class="alert alert-success" role="alert">
|
||||
<img src="{{ asset('/img/icons/check-circle.svg') }}" alt="موفقیت" class="icon-svg icon-check"> {{ message }}
|
||||
<div class="mb-6 p-4 bg-green-50 border border-green-200 rounded-xl text-green-800 text-right" role="alert">
|
||||
<div class="flex items-center">
|
||||
<img src="{{ asset('/img/icons/check-circle.svg') }}" alt="موفقیت" class="icon-svg icon-check ml-2">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% for message in app.flashes('error') %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<img src="{{ asset('/img/icons/exclamation-circle.svg') }}" alt="خطا" class="icon-svg icon-exclamation"> {{ message }}
|
||||
<div class="mb-6 p-4 bg-red-50 border border-red-200 rounded-xl text-red-800 text-right" role="alert">
|
||||
<div class="flex items-center">
|
||||
<img src="{{ asset('/img/icons/exclamation-circle.svg') }}" alt="خطا" class="icon-svg icon-exclamation ml-2">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% for message in app.flashes('info') %}
|
||||
<div class="alert alert-info" role="alert">
|
||||
<img src="{{ asset('/img/icons/info-circle.svg') }}" alt="اطلاعات" class="icon-svg icon-info"> {{ message }}
|
||||
<div class="mb-6 p-4 bg-blue-50 border border-blue-200 rounded-xl text-blue-800 text-right" role="alert">
|
||||
<div class="flex items-center">
|
||||
<img src="{{ asset('/img/icons/info-circle.svg') }}" alt="اطلاعات" class="icon-svg icon-info ml-2">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
|
|
@ -279,4 +98,5 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -55,13 +55,13 @@
|
|||
|
||||
<div class="flex justify-between items-center py-4 border-b border-gray-200">
|
||||
<span class="font-semibold text-gray-700">تاریخ عضویت:</span>
|
||||
<span class="text-gray-600">{{ user.createdAt|date('Y/m/d') }}</span>
|
||||
<span class="text-gray-600">{{ user.createdAt|date('U')|jdate('Y/m/d') }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center py-4">
|
||||
<span class="font-semibold text-gray-700">آخرین ورود:</span>
|
||||
<span class="text-gray-600">
|
||||
{{ user.lastLoginAt ? user.lastLoginAt|date('Y/m/d H:i') : 'هنوز وارد نشده' }}
|
||||
{{ user.lastLoginAt ? user.lastLoginAt|date('U')|jdate('Y/m/d H:i') : 'هنوز وارد نشده' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
|
@ -97,8 +97,8 @@
|
|||
<h5 class="text-lg font-semibold mb-4">اتصال کیف پول جدید</h5>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="walletType" class="form-label">نوع کیف پول</label>
|
||||
<select class="form-control" id="walletType" data-wallet-connect-target="walletType" data-action="change->wallet-connect#onWalletTypeChange">
|
||||
<label for="walletType" class="block text-sm font-medium text-gray-700 mb-2">نوع کیف پول</label>
|
||||
<select class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200" id="walletType" data-wallet-connect-target="walletType" data-action="change->wallet-connect#onWalletTypeChange">
|
||||
<option value="">انتخاب کنید...</option>
|
||||
<option value="metamask">MetaMask</option>
|
||||
<option value="trust">Trust Wallet</option>
|
||||
|
|
@ -108,13 +108,13 @@
|
|||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">آدرس کیف پول</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">آدرس کیف پول</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||
<i class="fas fa-wallet text-gray-400"></i>
|
||||
</div>
|
||||
<input type="text"
|
||||
class="form-control pr-10"
|
||||
class="w-full px-4 py-3 pr-10 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200"
|
||||
id="walletAddress"
|
||||
data-wallet-connect-target="walletAddress"
|
||||
placeholder="آدرس کیف پول پس از اتصال نمایش داده میشود"
|
||||
|
|
@ -124,7 +124,7 @@
|
|||
</div>
|
||||
<div class="col-span-2">
|
||||
<button type="button"
|
||||
class="btn-primary w-full py-3 text-lg"
|
||||
class="w-full bg-gradient-to-r from-blue-600 to-purple-600 text-white py-3 px-6 rounded-xl font-semibold hover:from-blue-700 hover:to-purple-700 transform hover:-translate-y-0.5 transition-all duration-200 shadow-lg hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none"
|
||||
id="connectBtn"
|
||||
data-wallet-connect-target="connectBtn"
|
||||
data-action="click->wallet-connect#connectWallet"
|
||||
|
|
@ -169,28 +169,28 @@
|
|||
{{ wallet.isActive ? 'فعال' : 'غیرفعال' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ wallet.connectedAt|date('Y/m/d H:i') }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ wallet.connectedAt|date('U')|jdate('Y/m/d H:i') }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
<div class="flex gap-2">
|
||||
{% if not wallet.isPrimary and wallet.isActive %}
|
||||
<button class="btn-outline-primary text-xs px-3 py-1"
|
||||
<button class="inline-flex items-center px-3 py-1 text-xs font-medium text-blue-600 bg-blue-50 border border-blue-200 rounded-lg hover:bg-blue-100 transition-colors duration-200"
|
||||
data-wallet-id="{{ wallet.id }}"
|
||||
data-action="click->wallet-connect#setPrimaryWallet">
|
||||
<i class="fas fa-star"></i> اصلی
|
||||
<i class="fas fa-star ml-1"></i> اصلی
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
<button class="btn-outline-warning text-xs px-3 py-1"
|
||||
<button class="inline-flex items-center px-3 py-1 text-xs font-medium text-yellow-600 bg-yellow-50 border border-yellow-200 rounded-lg hover:bg-yellow-100 transition-colors duration-200"
|
||||
data-wallet-id="{{ wallet.id }}"
|
||||
data-action="click->wallet-connect#toggleWalletStatus">
|
||||
<i class="fas fa-{{ wallet.isActive ? 'pause' : 'play' }}"></i>
|
||||
<i class="fas fa-{{ wallet.isActive ? 'pause' : 'play' }} ml-1"></i>
|
||||
{{ wallet.isActive ? 'غیرفعال' : 'فعال' }}
|
||||
</button>
|
||||
|
||||
<button class="btn-outline-danger text-xs px-3 py-1"
|
||||
<button class="inline-flex items-center px-3 py-1 text-xs font-medium text-red-600 bg-red-50 border border-red-200 rounded-lg hover:bg-red-100 transition-colors duration-200"
|
||||
data-wallet-id="{{ wallet.id }}"
|
||||
data-action="click->wallet-connect#deleteWallet">
|
||||
<i class="fas fa-trash"></i> حذف
|
||||
<i class="fas fa-trash ml-1"></i> حذف
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
|
|
@ -218,15 +218,21 @@
|
|||
</h3>
|
||||
|
||||
<div class="flex flex-wrap gap-4 justify-center">
|
||||
<a href="{{ path('customer_forgot_password') }}" class="btn-primary flex items-center gap-2">
|
||||
{% if is_granted('ROLE_ADMIN') %}
|
||||
<a href="{{ path('admin') }}" class="inline-flex items-center px-6 py-3 bg-gradient-to-r from-green-600 to-emerald-600 text-white rounded-xl font-medium hover:from-green-700 hover:to-emerald-700 transform hover:-translate-y-0.5 transition-all duration-200 shadow-lg hover:shadow-xl gap-2">
|
||||
<img src="{{ asset('/img/icons/cogs.svg') }}" alt="مدیریت" class="w-4 h-4"> پنل مدیریت
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<a href="{{ path('customer_forgot_password') }}" class="inline-flex items-center px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 text-white rounded-xl font-medium hover:from-blue-700 hover:to-purple-700 transform hover:-translate-y-0.5 transition-all duration-200 shadow-lg hover:shadow-xl gap-2">
|
||||
<img src="{{ asset('/img/icons/key.svg') }}" alt="کلید" class="w-4 h-4"> بازیابی کلمه عبور
|
||||
</a>
|
||||
|
||||
<a href="{{ path('app_home') }}" class="btn-primary flex items-center gap-2">
|
||||
<a href="{{ path('app_home') }}" class="inline-flex items-center px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 text-white rounded-xl font-medium hover:from-blue-700 hover:to-purple-700 transform hover:-translate-y-0.5 transition-all duration-200 shadow-lg hover:shadow-xl gap-2">
|
||||
<img src="{{ asset('/img/icons/home.svg') }}" alt="خانه" class="w-4 h-4"> بازگشت به صفحه اصلی
|
||||
</a>
|
||||
|
||||
<a href="{{ path('customer_logout') }}" class="bg-gradient-to-r from-red-500 to-red-600 text-white px-6 py-3 rounded-lg font-medium transition-all duration-300 hover:shadow-lg hover:-translate-y-0.5 flex items-center gap-2">
|
||||
<a href="{{ path('customer_logout') }}" class="inline-flex items-center px-6 py-3 bg-gradient-to-r from-red-500 to-red-600 text-white rounded-xl font-medium hover:from-red-600 hover:to-red-700 transform hover:-translate-y-0.5 transition-all duration-200 shadow-lg hover:shadow-xl gap-2">
|
||||
<img src="{{ asset('/img/icons/sign-out.svg') }}" alt="خروج" class="w-4 h-4"> خروج
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,30 +4,39 @@
|
|||
{% block page_subtitle %}کلمه عبور خود را بازیابی کنید{% endblock %}
|
||||
|
||||
{% block auth_content %}
|
||||
<div class="text-center mb-4">
|
||||
<img src="{{ asset('/img/icons/key.svg') }}" alt="کلید" class="icon-svg-large icon-key text-primary">
|
||||
<p class="text-muted">ایمیل خود را وارد کنید تا لینک بازیابی کلمه عبور برای شما ارسال شود.</p>
|
||||
<div class="text-center mb-8">
|
||||
<img src="{{ asset('/img/icons/key.svg') }}" alt="کلید" class="icon-svg-large icon-key text-blue-600">
|
||||
<p class="text-gray-600 mt-4">ایمیل خود را وارد کنید تا لینک بازیابی کلمه عبور برای شما ارسال شود.</p>
|
||||
</div>
|
||||
|
||||
{{ form_start(form, {'attr': {'novalidate': 'novalidate'}}) }}
|
||||
{{ form_start(form, {'attr': {'novalidate': 'novalidate', 'class': 'space-y-6'}}) }}
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
{{ form_widget(form.email, {'attr': {'class': 'form-control', 'placeholder': 'ایمیل خود را وارد کنید'}}) }}
|
||||
{{ form_label(form.email) }}
|
||||
<div>
|
||||
{{ form_label(form.email, null, {'label_attr': {'class': 'block text-sm font-medium text-gray-700 mb-2'}}) }}
|
||||
{{ form_widget(form.email, {'attr': {'class': 'w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 text-left', 'placeholder': 'ایمیل خود را وارد کنید'}}) }}
|
||||
{{ form_errors(form.email) }}
|
||||
</div>
|
||||
|
||||
<button class="w-full bg-gradient-to-r from-blue-600 to-purple-600 text-white py-3 px-6 rounded-xl font-semibold hover:from-blue-700 hover:to-purple-700 transform hover:-translate-y-0.5 transition-all duration-200 shadow-lg hover:shadow-xl flex items-center justify-center gap-2" type="submit">
|
||||
<img src="{{ asset('/img/icons/key.svg') }}" alt="ارسال" class="icon-svg icon-key">
|
||||
ارسال لینک بازیابی
|
||||
</button>
|
||||
|
||||
{{ form_end(form) }}
|
||||
|
||||
<div class="auth-links">
|
||||
<a href="{{ path('customer_login') }}">
|
||||
<img src="{{ asset('/img/icons/arrow-left.svg') }}" alt="بازگشت" class="icon-svg icon-arrow-left"> بازگشت به صفحه ورود
|
||||
<div class="mt-8 text-center space-y-4">
|
||||
<a href="{{ path('customer_login') }}" class="inline-flex items-center text-blue-600 hover:text-blue-700 font-medium transition-colors duration-200">
|
||||
<img src="{{ asset('/img/icons/arrow-left.svg') }}" alt="بازگشت" class="icon-svg icon-arrow-left ml-2">
|
||||
بازگشت به صفحه ورود
|
||||
</a>
|
||||
<br><br>
|
||||
|
||||
<div class="text-gray-600">
|
||||
<p>حساب کاربری ندارید؟
|
||||
<a href="{{ path('customer_register') }}">
|
||||
<img src="{{ asset('/img/icons/user-plus.svg') }}" alt="عضویت" class="icon-svg icon-user-plus"> عضویت در باشگاه
|
||||
<a href="{{ path('customer_register') }}" class="text-blue-600 hover:text-blue-700 font-medium transition-colors duration-200">
|
||||
<img src="{{ asset('/img/icons/user-plus.svg') }}" alt="عضویت" class="icon-svg icon-user-plus ml-1">
|
||||
عضویت در باشگاه
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,20 @@
|
|||
|
||||
{% block auth_content %}
|
||||
{% if error %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<img src="{{ asset('/img/icons/exclamation-circle.svg') }}" alt="خطا" class="icon-svg icon-exclamation"> {{ error.messageKey|trans(error.messageData, 'security', 'fa') }}
|
||||
<div class="mb-6 p-4 bg-red-50 border border-red-200 rounded-xl text-red-800 text-right" role="alert">
|
||||
<div class="flex items-center">
|
||||
<img src="{{ asset('/img/icons/exclamation-circle.svg') }}" alt="خطا" class="icon-svg icon-exclamation ml-2">
|
||||
{{ error.messageKey|trans(error.messageData, 'security', 'fa') }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="{{ path('customer_login_check') }}">
|
||||
<div class="form-floating mb-3">
|
||||
<form method="post" action="{{ path('customer_login_check') }}" class="space-y-6">
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label for="inputEmail" class="block text-sm font-medium text-gray-700 mb-2">پست الکترونیکی</label>
|
||||
<input type="email"
|
||||
class="form-control"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 text-left"
|
||||
id="inputEmail"
|
||||
name="_username"
|
||||
value="{{ last_username }}"
|
||||
|
|
@ -21,43 +26,52 @@
|
|||
required
|
||||
autofocus
|
||||
dir="ltr">
|
||||
<label for="inputEmail">پست الکترونیکی</label>
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<div>
|
||||
<label for="inputPassword" class="block text-sm font-medium text-gray-700 mb-2">کلمه عبور</label>
|
||||
<input type="password"
|
||||
class="form-control"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 text-left"
|
||||
id="inputPassword"
|
||||
name="_password"
|
||||
placeholder="کلمه عبور خود را وارد کنید"
|
||||
required
|
||||
dir="ltr">
|
||||
<label for="inputPassword">کلمه عبور</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="remember_me" name="_remember_me" checked>
|
||||
<label class="form-check-label" for="remember_me">
|
||||
<div class="flex items-center">
|
||||
<input class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 focus:ring-2 ml-3"
|
||||
type="checkbox"
|
||||
id="remember_me"
|
||||
name="_remember_me"
|
||||
checked>
|
||||
<label for="remember_me" class="text-sm text-gray-700">
|
||||
مرا به یاد داشته باش
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
|
||||
|
||||
<button class="btn btn-primary w-100 mb-3" type="submit">
|
||||
<img src="{{ asset('/img/icons/sign-in.svg') }}" alt="ورود" class="icon-svg icon-sign-in"> ورود
|
||||
<button class="w-full bg-gradient-to-r from-blue-600 to-purple-600 text-white py-3 px-6 rounded-xl font-semibold hover:from-blue-700 hover:to-purple-700 transform hover:-translate-y-0.5 transition-all duration-200 shadow-lg hover:shadow-xl flex items-center justify-center gap-2" type="submit">
|
||||
<img src="{{ asset('/img/icons/sign-in.svg') }}" alt="ورود" class="icon-svg icon-sign-in">
|
||||
ورود
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="auth-links">
|
||||
<a href="{{ path('customer_forgot_password') }}">
|
||||
<img src="{{ asset('/img/icons/key.svg') }}" alt="کلید" class="icon-svg icon-key"> فراموشی کلمه عبور
|
||||
<div class="mt-8 text-center space-y-4">
|
||||
<a href="{{ path('customer_forgot_password') }}" class="inline-flex items-center text-blue-600 hover:text-blue-700 font-medium transition-colors duration-200">
|
||||
<img src="{{ asset('/img/icons/key.svg') }}" alt="کلید" class="icon-svg icon-key ml-2">
|
||||
فراموشی کلمه عبور
|
||||
</a>
|
||||
<br><br>
|
||||
|
||||
<div class="text-gray-600">
|
||||
<p>حساب کاربری ندارید؟
|
||||
<a href="{{ path('customer_register') }}">
|
||||
<img src="{{ asset('/img/icons/user-plus.svg') }}" alt="عضویت" class="icon-svg icon-user-plus"> عضویت در باشگاه
|
||||
<a href="{{ path('customer_register') }}" class="text-blue-600 hover:text-blue-700 font-medium transition-colors duration-200">
|
||||
<img src="{{ asset('/img/icons/user-plus.svg') }}" alt="عضویت" class="icon-svg icon-user-plus ml-1">
|
||||
عضویت در باشگاه
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -4,56 +4,61 @@
|
|||
{% block page_subtitle %}به باشگاه مشتریان حسابیکس بپیوندید{% endblock %}
|
||||
|
||||
{% block auth_content %}
|
||||
{{ form_start(form, {'attr': {'novalidate': 'novalidate'}}) }}
|
||||
{{ form_start(form, {'attr': {'novalidate': 'novalidate', 'class': 'space-y-6'}}) }}
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
{{ form_widget(form.name, {'attr': {'class': 'form-control', 'placeholder': 'نام و نام خانوادگی خود را وارد کنید'}}) }}
|
||||
{{ form_label(form.name) }}
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
{{ form_label(form.name, null, {'label_attr': {'class': 'block text-sm font-medium text-gray-700 mb-2'}}) }}
|
||||
{{ form_widget(form.name, {'attr': {'class': 'w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 text-right', 'placeholder': 'نام و نام خانوادگی خود را وارد کنید'}}) }}
|
||||
{{ form_errors(form.name) }}
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
{{ form_widget(form.email, {'attr': {'class': 'form-control', 'placeholder': 'example@domain.com'}}) }}
|
||||
{{ form_label(form.email) }}
|
||||
<div>
|
||||
{{ form_label(form.email, null, {'label_attr': {'class': 'block text-sm font-medium text-gray-700 mb-2'}}) }}
|
||||
{{ form_widget(form.email, {'attr': {'class': 'w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 text-left', 'placeholder': 'example@domain.com'}}) }}
|
||||
{{ form_errors(form.email) }}
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
{{ form_widget(form.phone, {'attr': {'class': 'form-control', 'placeholder': '09123456789'}}) }}
|
||||
{{ form_label(form.phone) }}
|
||||
<div>
|
||||
{{ form_label(form.phone, null, {'label_attr': {'class': 'block text-sm font-medium text-gray-700 mb-2'}}) }}
|
||||
{{ form_widget(form.phone, {'attr': {'class': 'w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 text-left', 'placeholder': '09123456789'}}) }}
|
||||
{{ form_errors(form.phone) }}
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
{{ form_widget(form.plainPassword.first, {'attr': {'class': 'form-control', 'placeholder': 'کلمه عبور خود را وارد کنید'}}) }}
|
||||
{{ form_label(form.plainPassword.first) }}
|
||||
<div>
|
||||
{{ form_label(form.plainPassword.first, null, {'label_attr': {'class': 'block text-sm font-medium text-gray-700 mb-2'}}) }}
|
||||
{{ form_widget(form.plainPassword.first, {'attr': {'class': 'w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 text-left', 'placeholder': 'کلمه عبور خود را وارد کنید'}}) }}
|
||||
{{ form_errors(form.plainPassword.first) }}
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
{{ form_widget(form.plainPassword.second, {'attr': {'class': 'form-control', 'placeholder': 'کلمه عبور را مجدداً وارد کنید'}}) }}
|
||||
{{ form_label(form.plainPassword.second) }}
|
||||
<div>
|
||||
{{ form_label(form.plainPassword.second, null, {'label_attr': {'class': 'block text-sm font-medium text-gray-700 mb-2'}}) }}
|
||||
{{ form_widget(form.plainPassword.second, {'attr': {'class': 'w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 text-left', 'placeholder': 'کلمه عبور را مجدداً وارد کنید'}}) }}
|
||||
{{ form_errors(form.plainPassword.second) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
{{ form_widget(form.agreeTerms, {'attr': {'class': 'form-check-input'}}) }}
|
||||
<label class="form-check-label" for="{{ form.agreeTerms.vars.id }}">
|
||||
<a href="{{ path('app_page', {'url': 'terms'}) }}" target="_blank">قوانین و مقررات</a> را میپذیرم
|
||||
<div class="flex items-start">
|
||||
{{ form_widget(form.agreeTerms, {'attr': {'class': 'w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 focus:ring-2 mt-1 ml-3'}}) }}
|
||||
<label class="text-sm text-gray-700" for="{{ form.agreeTerms.vars.id }}">
|
||||
<a href="{{ path('app_page', {'url': 'terms'}) }}" target="_blank" class="text-blue-600 hover:text-blue-700 font-medium">قوانین و مقررات</a> را میپذیرم
|
||||
</label>
|
||||
{{ form_errors(form.agreeTerms) }}
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary w-100 mb-3" type="submit">
|
||||
<img src="{{ asset('/img/icons/user-plus.svg') }}" alt="عضویت" class="icon-svg icon-user-plus"> عضویت در باشگاه
|
||||
<button class="w-full bg-gradient-to-r from-blue-600 to-purple-600 text-white py-3 px-6 rounded-xl font-semibold hover:from-blue-700 hover:to-purple-700 transform hover:-translate-y-0.5 transition-all duration-200 shadow-lg hover:shadow-xl flex items-center justify-center gap-2" type="submit">
|
||||
<img src="{{ asset('/img/icons/user-plus.svg') }}" alt="عضویت" class="icon-svg icon-user-plus">
|
||||
عضویت در باشگاه
|
||||
</button>
|
||||
|
||||
{{ form_end(form) }}
|
||||
|
||||
<div class="auth-links">
|
||||
<p>قبلاً عضو شدهاید؟
|
||||
<a href="{{ path('customer_login') }}">
|
||||
<img src="{{ asset('/img/icons/sign-in.svg') }}" alt="ورود" class="icon-svg icon-sign-in"> ورود
|
||||
<div class="mt-8 text-center">
|
||||
<p class="text-gray-600">
|
||||
قبلاً عضو شدهاید؟
|
||||
<a href="{{ path('customer_login') }}" class="text-blue-600 hover:text-blue-700 font-medium transition-colors duration-200">
|
||||
<img src="{{ asset('/img/icons/sign-in.svg') }}" alt="ورود" class="icon-svg icon-sign-in ml-1">
|
||||
ورود
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,30 +4,38 @@
|
|||
{% block page_subtitle %}کلمه عبور جدید خود را وارد کنید{% endblock %}
|
||||
|
||||
{% block auth_content %}
|
||||
<div class="text-center mb-4">
|
||||
<img src="{{ asset('/img/icons/lock.svg') }}" alt="قفل" class="icon-svg-large icon-lock text-primary">
|
||||
<p class="text-muted">کلمه عبور جدید خود را وارد کنید.</p>
|
||||
<div class="text-center mb-8">
|
||||
<img src="{{ asset('/img/icons/lock.svg') }}" alt="قفل" class="icon-svg-large icon-lock text-blue-600">
|
||||
<p class="text-gray-600 mt-4">کلمه عبور جدید خود را وارد کنید.</p>
|
||||
</div>
|
||||
|
||||
{{ form_start(form, {'attr': {'novalidate': 'novalidate'}}) }}
|
||||
{{ form_start(form, {'attr': {'novalidate': 'novalidate', 'class': 'space-y-6'}}) }}
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
{{ form_widget(form.plainPassword.first, {'attr': {'class': 'form-control', 'placeholder': 'کلمه عبور جدید خود را وارد کنید'}}) }}
|
||||
{{ form_label(form.plainPassword.first) }}
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
{{ form_label(form.plainPassword.first, null, {'label_attr': {'class': 'block text-sm font-medium text-gray-700 mb-2'}}) }}
|
||||
{{ form_widget(form.plainPassword.first, {'attr': {'class': 'w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 text-left', 'placeholder': 'کلمه عبور جدید خود را وارد کنید'}}) }}
|
||||
{{ form_errors(form.plainPassword.first) }}
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
{{ form_widget(form.plainPassword.second, {'attr': {'class': 'form-control', 'placeholder': 'کلمه عبور جدید را مجدداً وارد کنید'}}) }}
|
||||
{{ form_label(form.plainPassword.second) }}
|
||||
<div>
|
||||
{{ form_label(form.plainPassword.second, null, {'label_attr': {'class': 'block text-sm font-medium text-gray-700 mb-2'}}) }}
|
||||
{{ form_widget(form.plainPassword.second, {'attr': {'class': 'w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 text-left', 'placeholder': 'کلمه عبور جدید را مجدداً وارد کنید'}}) }}
|
||||
{{ form_errors(form.plainPassword.second) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="w-full bg-gradient-to-r from-blue-600 to-purple-600 text-white py-3 px-6 rounded-xl font-semibold hover:from-blue-700 hover:to-purple-700 transform hover:-translate-y-0.5 transition-all duration-200 shadow-lg hover:shadow-xl flex items-center justify-center gap-2" type="submit">
|
||||
<img src="{{ asset('/img/icons/lock.svg') }}" alt="تغییر" class="icon-svg icon-lock">
|
||||
تغییر کلمه عبور
|
||||
</button>
|
||||
|
||||
{{ form_end(form) }}
|
||||
|
||||
<div class="auth-links">
|
||||
<a href="{{ path('customer_login') }}">
|
||||
<img src="{{ asset('/img/icons/arrow-left.svg') }}" alt="بازگشت" class="icon-svg icon-arrow-left"> بازگشت به صفحه ورود
|
||||
<div class="mt-8 text-center">
|
||||
<a href="{{ path('customer_login') }}" class="inline-flex items-center text-blue-600 hover:text-blue-700 font-medium transition-colors duration-200">
|
||||
<img src="{{ asset('/img/icons/arrow-left.svg') }}" alt="بازگشت" class="icon-svg icon-arrow-left ml-2">
|
||||
بازگشت به صفحه ورود
|
||||
</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
127
templates/emails/answer_accepted_notification.html.twig
Normal file
127
templates/emails/answer_accepted_notification.html.twig
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
<!DOCTYPE html>
|
||||
<html dir="rtl" lang="fa">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>پاسخ شما پذیرفته شد</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Tahoma', 'Arial', sans-serif;
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
background-color: #f8f9fa;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background-color: #ffffff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
.header {
|
||||
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
}
|
||||
.content {
|
||||
padding: 30px;
|
||||
}
|
||||
.success-icon {
|
||||
text-align: center;
|
||||
font-size: 48px;
|
||||
color: #28a745;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.question-box {
|
||||
background-color: #f8f9fa;
|
||||
border-right: 4px solid #007bff;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.question-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.question-content {
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.answer-box {
|
||||
background-color: #e8f5e8;
|
||||
border-right: 4px solid #28a745;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.answer-content {
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.footer {
|
||||
background-color: #f8f9fa;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
.btn {
|
||||
display: inline-block;
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
padding: 12px 24px;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.btn:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>پاسخ شما پذیرفته شد</h1>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="success-icon">✓</div>
|
||||
|
||||
<p>سلام {{ answerAuthor.name }}،</p>
|
||||
|
||||
<p>تبریک! پاسخ شما برای سوال زیر به عنوان بهترین پاسخ انتخاب شده است:</p>
|
||||
|
||||
<div class="question-box">
|
||||
<div class="question-title">{{ question.title }}</div>
|
||||
<div class="question-content">{{ question.content|markdown|raw }}</div>
|
||||
</div>
|
||||
|
||||
<div class="answer-box">
|
||||
<div class="answer-content">{{ answer.content|markdown|raw }}</div>
|
||||
</div>
|
||||
|
||||
<p style="text-align: center;">
|
||||
<a href="{{ url('qa_question_show', {'id': question.id}) }}" class="btn">
|
||||
مشاهده سوال و پاسخ
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>این ایمیل به صورت خودکار ارسال شده است. لطفاً به آن پاسخ ندهید.</p>
|
||||
<p>© {{ "now"|date("Y") }} حسابیکس - سیستم حسابداری آنلاین</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
125
templates/emails/answer_notification.html.twig
Normal file
125
templates/emails/answer_notification.html.twig
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
<!DOCTYPE html>
|
||||
<html dir="rtl" lang="fa">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>پاسخ جدید برای سوال شما</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Tahoma', 'Arial', sans-serif;
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
background-color: #f8f9fa;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background-color: #ffffff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
.header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
}
|
||||
.content {
|
||||
padding: 30px;
|
||||
}
|
||||
.question-box {
|
||||
background-color: #f8f9fa;
|
||||
border-right: 4px solid #007bff;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.question-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.question-content {
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.answer-box {
|
||||
background-color: #e8f5e8;
|
||||
border-right: 4px solid #28a745;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.answer-author {
|
||||
font-weight: bold;
|
||||
color: #28a745;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.answer-content {
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.footer {
|
||||
background-color: #f8f9fa;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
.btn {
|
||||
display: inline-block;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
padding: 12px 24px;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.btn:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>پاسخ جدید برای سوال شما</h1>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<p>سلام {{ questionAuthor.name }}،</p>
|
||||
|
||||
<p>برای سوال شما پاسخ جدیدی دریافت کردهاید:</p>
|
||||
|
||||
<div class="question-box">
|
||||
<div class="question-title">{{ question.title }}</div>
|
||||
<div class="question-content">{{ question.content|markdown|raw }}</div>
|
||||
</div>
|
||||
|
||||
<div class="answer-box">
|
||||
<div class="answer-author">پاسخ از: {{ answerAuthor.name }}</div>
|
||||
<div class="answer-content">{{ answer.content|markdown|raw }}</div>
|
||||
</div>
|
||||
|
||||
<p style="text-align: center;">
|
||||
<a href="{{ url('qa_question_show', {'id': question.id}) }}" class="btn">
|
||||
مشاهده پاسخ کامل
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>این ایمیل به صورت خودکار ارسال شده است. لطفاً به آن پاسخ ندهید.</p>
|
||||
<p>© {{ "now"|date("Y") }} حسابیکس - سیستم حسابداری آنلاین</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -108,42 +108,45 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
<div class="container mx-auto bg-gray-100 rounded-2xl p-6">
|
||||
<h4 class="text-center mb-4 text-primary-600 font-bold text-xl">حامیان مالی حسابیکس</h4>
|
||||
<p class="text-center text-gray-600 mb-6">حسابیکس با مشارکت حامیان مالی و حمایت کاربران عزیز، به صورت مستمر در حال توسعه و بهبود است. هر یک از شما با استفاده از خدمات حسابیکس، در پیشرفت این پلتفرم نقش دارید.</p>
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 justify-center">
|
||||
<div class="w-full">
|
||||
<div class="card h-full hover:shadow-lg transition-shadow duration-300">
|
||||
<a href="https://parspack.com" target="_blank" class="block">
|
||||
<div class="p-4 text-center">
|
||||
<img src="{{asset('img/sp/parspack-logo.png')}}" class="w-full p-2" alt="پارس پک">
|
||||
<p class="text-gray-600 mt-2 mb-0">پارس پک</p>
|
||||
<!-- حامیان مالی -->
|
||||
<div class="w-full mb-16">
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-gray-800 mb-4">
|
||||
حامیان حسابیکس
|
||||
</h2>
|
||||
<p class="text-lg text-gray-600 max-w-3xl mx-auto">
|
||||
حسابیکس با مشارکت حامیان مالی و حمایت کاربران عزیز، به صورت مستمر در حال توسعه و بهبود است.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<!-- پارس پک -->
|
||||
<a href="https://parspack.com" target="_blank"
|
||||
class="bg-white rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 p-8 text-center hover:scale-105 cursor-pointer">
|
||||
<img src="{{asset('img/sp/parspack-logo.png')}}"
|
||||
class="w-24 h-24 mx-auto object-contain"
|
||||
alt="پارس پک">
|
||||
</a>
|
||||
|
||||
<!-- ملی پیامک -->
|
||||
<a href="https://melipayamak.com" target="_blank"
|
||||
class="bg-white rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 p-8 text-center hover:scale-105 cursor-pointer">
|
||||
<img src="{{asset('img/sp/melipayamak.png')}}"
|
||||
class="w-24 h-24 mx-auto object-contain"
|
||||
alt="ملی پیامک">
|
||||
</a>
|
||||
|
||||
<!-- ایران موتوربرق -->
|
||||
<a href="https://irmotorbargh.com" target="_blank"
|
||||
class="bg-white rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 p-8 text-center hover:scale-105 cursor-pointer">
|
||||
<img src="{{asset('img/sp/irmr.png')}}"
|
||||
class="w-24 h-24 mx-auto object-contain"
|
||||
alt="ایران موتوربرق">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class="card h-full hover:shadow-lg transition-shadow duration-300">
|
||||
<a href="https://melipayamak.com" target="_blank" class="block">
|
||||
<div class="p-4 text-center">
|
||||
<img src="{{asset('img/sp/melipayamak.png')}}" class="w-full p-2" alt="ملی پیامک">
|
||||
<p class="text-gray-600 mt-2 mb-0">ملی پیامک</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class="card h-full hover:shadow-lg transition-shadow duration-300">
|
||||
<a href="https://irmotorbargh.com" target="_blank" class="block">
|
||||
<div class="p-4 text-center">
|
||||
<img src="{{asset('img/sp/irmr.png')}}" class="w-full p-2" alt="راد دیتا">
|
||||
<p class="text-gray-600 mt-2 mb-0">ایران موتوربرق</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- Features Section با طراحی مدرن و تعاملی -->
|
||||
|
|
@ -220,16 +223,9 @@
|
|||
|
||||
<!-- Content -->
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 bg-gradient-to-r from-blue-100 to-blue-200 rounded-xl flex items-center justify-center ml-3 group-hover:scale-110 transition-transform duration-300">
|
||||
<svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-gray-800 group-hover:text-blue-600 transition-colors duration-300">
|
||||
<h3 class="text-xl font-bold text-gray-800 group-hover:text-blue-600 transition-colors duration-300 mb-3">
|
||||
ثبتنام و ساخت پنل
|
||||
</h3>
|
||||
</div>
|
||||
<p class="text-gray-600 leading-relaxed">
|
||||
در حسابیکس عضو شوید و پنل کسب و کار خود را به سادگی بسازید. هر تعداد کسبوکار که داشته باشید میتوانید از طریق یک داشبورد واحد آنها را مدیریت کنید.
|
||||
</p>
|
||||
|
|
@ -261,17 +257,9 @@
|
|||
|
||||
<!-- Content -->
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 bg-gradient-to-r from-purple-100 to-purple-200 rounded-xl flex items-center justify-center ml-3 group-hover:scale-110 transition-transform duration-300">
|
||||
<svg class="w-5 h-5 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-gray-800 group-hover:text-purple-600 transition-colors duration-300">
|
||||
<h3 class="text-xl font-bold text-gray-800 group-hover:text-purple-600 transition-colors duration-300 mb-3">
|
||||
تنظیمات و راهاندازی
|
||||
</h3>
|
||||
</div>
|
||||
<p class="text-gray-600 leading-relaxed">
|
||||
اطلاعات کسب و کار خود را وارد کنید و تنظیمات اولیه را انجام دهید. حسابیکس آماده و در اختیار شماست. در صورت نیاز به راهنمایی، تیم پشتیبانی پاسخگوی شماست.
|
||||
</p>
|
||||
|
|
@ -301,16 +289,9 @@
|
|||
|
||||
<!-- Content -->
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-10 h-10 bg-gradient-to-r from-green-100 to-green-200 rounded-xl flex items-center justify-center ml-3 group-hover:scale-110 transition-transform duration-300">
|
||||
<svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-gray-800 group-hover:text-green-600 transition-colors duration-300">
|
||||
<h3 class="text-xl font-bold text-gray-800 group-hover:text-green-600 transition-colors duration-300 mb-3">
|
||||
دسترسی همهجا
|
||||
</h3>
|
||||
</div>
|
||||
<p class="text-gray-600 leading-relaxed">
|
||||
از حسابیکس بر روی موبایل، تبلت و کامپیوتر لذت ببرید. تنها کافی است که یک اتصال اینترنت داشته باشید. حسابیکس در هر نقطهای از جهان در دسترس شما خواهد بود.
|
||||
</p>
|
||||
|
|
@ -457,7 +438,7 @@
|
|||
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
{{ Jdate.jdate('Y/n/d',post.dateSubmit) }}
|
||||
{{ Jdate.jdate('Y/n/d',post.dateSubmit|date('U')) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,87 +4,75 @@
|
|||
|
||||
{% block body %}
|
||||
<!-- Hero Section -->
|
||||
<div class="bg-primary text-white py-5">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg-8 mx-auto text-center">
|
||||
<div class="mb-4">
|
||||
<div class="d-inline-flex align-items-center justify-content-center mb-3 bg-white bg-opacity-20 rounded-circle" style="width: 120px; height: 120px;">
|
||||
<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="text-warning">
|
||||
<div class="bg-gradient-to-br from-blue-600 via-purple-600 to-indigo-700 text-white py-16">
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="max-w-4xl mx-auto text-center">
|
||||
<div class="mb-8">
|
||||
<div class="inline-flex items-center justify-center mb-6 bg-white bg-opacity-20 rounded-full w-32 h-32">
|
||||
<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="text-yellow-400">
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="display-4 fw-bold mb-3">پشتیبانی تخصصی حسابیکس</h1>
|
||||
<p class="mb-4">نرم افزار حسابداری ابری، متن باز و رایگان با پشتیبانی حرفهای برای کسب و کارها</p>
|
||||
</div>
|
||||
<h1 class="text-4xl md:text-5xl font-bold mb-6">پشتیبانی تخصصی حسابیکس</h1>
|
||||
<p class="text-xl text-blue-100 mb-8">نرم افزار حسابداری ابری، متن باز و رایگان با پشتیبانی حرفهای برای کسب و کارها</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="container py-5">
|
||||
<div class="container mx-auto px-4 py-16">
|
||||
<!-- Open Source Section -->
|
||||
<div class="row mb-5">
|
||||
<div class="col-lg-10 mx-auto">
|
||||
<div class="card border-0 shadow-lg">
|
||||
<div class="card-body p-5">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-6 text-center mb-4 mb-md-0">
|
||||
<div class="bg-success bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 120px; height: 120px;">
|
||||
<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="text-success">
|
||||
<div class="mb-16">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div class="bg-white rounded-3xl shadow-2xl overflow-hidden">
|
||||
<div class="p-8 md:p-12">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 items-center">
|
||||
<div class="text-center lg:text-right">
|
||||
<div class="bg-green-100 rounded-full inline-flex items-center justify-center mb-6 w-32 h-32">
|
||||
<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="text-green-600">
|
||||
<polyline points="16,18 22,12 16,6"></polyline>
|
||||
<polyline points="8,6 2,12 8,18"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="h3 text-success fw-bold mb-3">حسابیکس - نرم افزار متن باز</h2>
|
||||
<p class="text-muted">حسابیکس یک نرم افزار حسابداری ابری، متن باز و کاملاً رایگان است که شما میتوانید از آن برای مدیریت مالی کسب و کار خود استفاده کنید.</p>
|
||||
<h2 class="text-3xl font-bold text-green-600 mb-4">حسابیکس - نرم افزار متن باز</h2>
|
||||
<p class="text-gray-600 text-lg leading-relaxed">حسابیکس یک نرم افزار حسابداری ابری، متن باز و کاملاً رایگان است که شما میتوانید از آن برای مدیریت مالی کسب و کار خود استفاده کنید.</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="row g-3">
|
||||
<div class="col-6">
|
||||
<div class="text-center p-3 bg-light rounded-3">
|
||||
<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="text-primary mb-2">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="text-center p-4 bg-gray-50 rounded-2xl hover:bg-gray-100 transition-colors duration-200">
|
||||
<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="text-blue-600 mb-2 mx-auto">
|
||||
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
|
||||
<circle cx="9" cy="7" r="4"></circle>
|
||||
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
|
||||
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
|
||||
</svg>
|
||||
<h6 class="fw-bold mb-1">جامعه فعال</h6>
|
||||
<small class="text-muted">توسعهدهندگان</small>
|
||||
<h6 class="font-bold mb-1 text-gray-800">جامعه فعال</h6>
|
||||
<small class="text-gray-500">توسعهدهندگان</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="text-center p-3 bg-light rounded-3">
|
||||
<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="text-success mb-2">
|
||||
<div class="text-center p-4 bg-gray-50 rounded-2xl hover:bg-gray-100 transition-colors duration-200">
|
||||
<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="text-green-600 mb-2 mx-auto">
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
|
||||
</svg>
|
||||
<h6 class="fw-bold mb-1">امنیت بالا</h6>
|
||||
<small class="text-muted">کدهای باز</small>
|
||||
<h6 class="font-bold mb-1 text-gray-800">امنیت بالا</h6>
|
||||
<small class="text-gray-500">کدهای باز</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="text-center p-3 bg-light rounded-3">
|
||||
<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="text-info mb-2">
|
||||
<div class="text-center p-4 bg-gray-50 rounded-2xl hover:bg-gray-100 transition-colors duration-200">
|
||||
<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="text-blue-500 mb-2 mx-auto">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="7,10 12,15 17,10"></polyline>
|
||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||
</svg>
|
||||
<h6 class="fw-bold mb-1">کاملاً رایگان</h6>
|
||||
<small class="text-muted">بدون هزینه</small>
|
||||
<h6 class="font-bold mb-1 text-gray-800">کاملاً رایگان</h6>
|
||||
<small class="text-gray-500">بدون هزینه</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="text-center p-3 bg-light rounded-3">
|
||||
<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="text-warning mb-2">
|
||||
<div class="text-center p-4 bg-gray-50 rounded-2xl hover:bg-gray-100 transition-colors duration-200">
|
||||
<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="text-yellow-500 mb-2 mx-auto">
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
<path d="M12 1v6m0 6v6"></path>
|
||||
<path d="M21 12h-6m-6 0H3"></path>
|
||||
</svg>
|
||||
<h6 class="fw-bold mb-1">قابل سفارشی</h6>
|
||||
<small class="text-muted">انعطافپذیر</small>
|
||||
</div>
|
||||
</div>
|
||||
<h6 class="font-bold mb-1 text-gray-800">قابل سفارشی</h6>
|
||||
<small class="text-gray-500">انعطافپذیر</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -94,55 +82,55 @@
|
|||
</div>
|
||||
|
||||
<!-- Professional Services Section -->
|
||||
<div class="row mb-5">
|
||||
<div class="col-lg-10 mx-auto">
|
||||
<div class="text-center mb-5">
|
||||
<h2 class="display-6 fw-bold text-primary mb-3">پشتیبانی تخصصی برای کسب و کارها</h2>
|
||||
<p class="text-muted">اگر شما یک شرکت یا کسب و کار دارید و نیاز به پشتیبانی حرفهای، قابلیتهای سفارشی یا خدمات تخصصی برای نرم افزار حسابداری حسابیکس دارید، ما آماده ارائه خدمات زیر هستیم:</p>
|
||||
<div class="mb-16">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-4xl md:text-5xl font-bold text-blue-600 mb-6">پشتیبانی تخصصی برای کسب و کارها</h2>
|
||||
<p class="text-gray-600 text-lg max-w-4xl mx-auto leading-relaxed">اگر شما یک شرکت یا کسب و کار دارید و نیاز به پشتیبانی حرفهای، قابلیتهای سفارشی یا خدمات تخصصی برای نرم افزار حسابداری حسابیکس دارید، ما آماده ارائه خدمات زیر هستیم:</p>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<!-- Custom Development -->
|
||||
<div class="col-lg-6">
|
||||
<div class="card h-100 border-0 shadow-sm hover-card">
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="bg-primary bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 60px; height: 60px;">
|
||||
<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="text-primary">
|
||||
<div class="group">
|
||||
<div class="bg-white rounded-3xl shadow-xl hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2 h-full">
|
||||
<div class="p-8">
|
||||
<div class="flex items-center mb-6">
|
||||
<div class="bg-blue-100 rounded-full flex items-center justify-center w-16 h-16 ml-4">
|
||||
<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="text-blue-600">
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
<path d="M12 1v6m0 6v6"></path>
|
||||
<path d="M21 12h-6m-6 0H3"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="fw-bold mb-1">توسعه قابلیتهای سفارشی</h4>
|
||||
<p class="text-muted mb-0">توسعه ماژولهای حسابداری خاص مطابق با نیازهای کسب و کار شما</p>
|
||||
<h4 class="text-xl font-bold text-gray-800 mb-2">توسعه قابلیتهای سفارشی</h4>
|
||||
<p class="text-gray-600">توسعه ماژولهای حسابداری خاص مطابق با نیازهای کسب و کار شما</p>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-2">
|
||||
<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="text-success me-2">
|
||||
<ul class="space-y-3">
|
||||
<li class="flex items-center">
|
||||
<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="text-green-600 ml-3">
|
||||
<polyline points="20,6 9,17 4,12"></polyline>
|
||||
</svg>
|
||||
تحلیل نیازمندیهای حسابداری
|
||||
<span class="text-gray-700">تحلیل نیازمندیهای حسابداری</span>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<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="text-success me-2">
|
||||
<li class="flex items-center">
|
||||
<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="text-green-600 ml-3">
|
||||
<polyline points="20,6 9,17 4,12"></polyline>
|
||||
</svg>
|
||||
طراحی ماژولهای اختصاصی
|
||||
<span class="text-gray-700">طراحی ماژولهای اختصاصی</span>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<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="text-success me-2">
|
||||
<li class="flex items-center">
|
||||
<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="text-green-600 ml-3">
|
||||
<polyline points="20,6 9,17 4,12"></polyline>
|
||||
</svg>
|
||||
یکپارچهسازی با سیستمهای موجود
|
||||
<span class="text-gray-700">یکپارچهسازی با سیستمهای موجود</span>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<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="text-success me-2">
|
||||
<li class="flex items-center">
|
||||
<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="text-green-600 ml-3">
|
||||
<polyline points="20,6 9,17 4,12"></polyline>
|
||||
</svg>
|
||||
تست و تضمین کیفیت
|
||||
<span class="text-gray-700">تست و تضمین کیفیت</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -150,44 +138,44 @@
|
|||
</div>
|
||||
|
||||
<!-- Technical Support -->
|
||||
<div class="col-lg-6">
|
||||
<div class="card h-100 border-0 shadow-sm hover-card">
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="bg-success bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 60px; height: 60px;">
|
||||
<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="text-success">
|
||||
<div class="group">
|
||||
<div class="bg-white rounded-3xl shadow-xl hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2 h-full">
|
||||
<div class="p-8">
|
||||
<div class="flex items-center mb-6">
|
||||
<div class="bg-green-100 rounded-full flex items-center justify-center w-16 h-16 ml-4">
|
||||
<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="text-green-600">
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="fw-bold mb-1">پشتیبانی فنی ۲۴/۷</h4>
|
||||
<p class="text-muted mb-0">پشتیبانی فنی شبانهروزی برای حل مشکلات حسابداری و پاسخ به سوالات</p>
|
||||
<h4 class="text-xl font-bold text-gray-800 mb-2">پشتیبانی فنی ۲۴/۷</h4>
|
||||
<p class="text-gray-600">پشتیبانی فنی شبانهروزی برای حل مشکلات حسابداری و پاسخ به سوالات</p>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-2">
|
||||
<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="text-success me-2">
|
||||
<ul class="space-y-3">
|
||||
<li class="flex items-center">
|
||||
<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="text-green-600 ml-3">
|
||||
<polyline points="20,6 9,17 4,12"></polyline>
|
||||
</svg>
|
||||
پاسخ سریع به مشکلات فنی
|
||||
<span class="text-gray-700">پاسخ سریع به مشکلات فنی</span>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<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="text-success me-2">
|
||||
<li class="flex items-center">
|
||||
<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="text-green-600 ml-3">
|
||||
<polyline points="20,6 9,17 4,12"></polyline>
|
||||
</svg>
|
||||
راهنمایی تخصصی حسابداری
|
||||
<span class="text-gray-700">راهنمایی تخصصی حسابداری</span>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<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="text-success me-2">
|
||||
<li class="flex items-center">
|
||||
<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="text-green-600 ml-3">
|
||||
<polyline points="20,6 9,17 4,12"></polyline>
|
||||
</svg>
|
||||
حل مشکلات از راه دور
|
||||
<span class="text-gray-700">حل مشکلات از راه دور</span>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<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="text-success me-2">
|
||||
<li class="flex items-center">
|
||||
<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="text-green-600 ml-3">
|
||||
<polyline points="20,6 9,17 4,12"></polyline>
|
||||
</svg>
|
||||
پشتیبانی تلفنی و آنلاین
|
||||
<span class="text-gray-700">پشتیبانی تلفنی و آنلاین</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -195,45 +183,45 @@
|
|||
</div>
|
||||
|
||||
<!-- Training -->
|
||||
<div class="col-lg-6">
|
||||
<div class="card h-100 border-0 shadow-sm hover-card">
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="bg-info bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 60px; height: 60px;">
|
||||
<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="text-info">
|
||||
<div class="group">
|
||||
<div class="bg-white rounded-3xl shadow-xl hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2 h-full">
|
||||
<div class="p-8">
|
||||
<div class="flex items-center mb-6">
|
||||
<div class="bg-blue-100 rounded-full flex items-center justify-center w-16 h-16 ml-4">
|
||||
<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="text-blue-600">
|
||||
<path d="M22 10v6M2 10l10-5 10 5-10 5z"></path>
|
||||
<path d="M6 12v5c3 3 9 3 12 0v-5"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="fw-bold mb-1">آموزش و راهنمایی</h4>
|
||||
<p class="text-muted mb-0">آموزش کارکنان در استفاده بهینه از نرم افزار حسابداری حسابیکس</p>
|
||||
<h4 class="text-xl font-bold text-gray-800 mb-2">آموزش و راهنمایی</h4>
|
||||
<p class="text-gray-600">آموزش کارکنان در استفاده بهینه از نرم افزار حسابداری حسابیکس</p>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-2">
|
||||
<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="text-success me-2">
|
||||
<ul class="space-y-3">
|
||||
<li class="flex items-center">
|
||||
<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="text-green-600 ml-3">
|
||||
<polyline points="20,6 9,17 4,12"></polyline>
|
||||
</svg>
|
||||
آموزش حضوری و آنلاین
|
||||
<span class="text-gray-700">آموزش حضوری و آنلاین</span>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<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="text-success me-2">
|
||||
<li class="flex items-center">
|
||||
<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="text-green-600 ml-3">
|
||||
<polyline points="20,6 9,17 4,12"></polyline>
|
||||
</svg>
|
||||
مستندات آموزشی اختصاصی
|
||||
<span class="text-gray-700">مستندات آموزشی اختصاصی</span>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<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="text-success me-2">
|
||||
<li class="flex items-center">
|
||||
<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="text-green-600 ml-3">
|
||||
<polyline points="20,6 9,17 4,12"></polyline>
|
||||
</svg>
|
||||
ویدیوهای آموزشی حسابداری
|
||||
<span class="text-gray-700">ویدیوهای آموزشی حسابداری</span>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<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="text-success me-2">
|
||||
<li class="flex items-center">
|
||||
<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="text-green-600 ml-3">
|
||||
<polyline points="20,6 9,17 4,12"></polyline>
|
||||
</svg>
|
||||
پشتیبانی آموزشی مداوم
|
||||
<span class="text-gray-700">پشتیبانی آموزشی مداوم</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -241,46 +229,46 @@
|
|||
</div>
|
||||
|
||||
<!-- Installation -->
|
||||
<div class="col-lg-6">
|
||||
<div class="card h-100 border-0 shadow-sm hover-card">
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="bg-warning bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 60px; height: 60px;">
|
||||
<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="text-warning">
|
||||
<div class="group">
|
||||
<div class="bg-white rounded-3xl shadow-xl hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2 h-full">
|
||||
<div class="p-8">
|
||||
<div class="flex items-center mb-6">
|
||||
<div class="bg-yellow-100 rounded-full flex items-center justify-center w-16 h-16 ml-4">
|
||||
<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="text-yellow-600">
|
||||
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
|
||||
<line x1="8" y1="21" x2="16" y2="21"></line>
|
||||
<line x1="12" y1="17" x2="12" y2="21"></line>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="fw-bold mb-1">نصب و راهاندازی</h4>
|
||||
<p class="text-muted mb-0">نصب، پیکربندی و راهاندازی نرم افزار حسابداری در محیط شما</p>
|
||||
<h4 class="text-xl font-bold text-gray-800 mb-2">نصب و راهاندازی</h4>
|
||||
<p class="text-gray-600">نصب، پیکربندی و راهاندازی نرم افزار حسابداری در محیط شما</p>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-2">
|
||||
<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="text-success me-2">
|
||||
<ul class="space-y-3">
|
||||
<li class="flex items-center">
|
||||
<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="text-green-600 ml-3">
|
||||
<polyline points="20,6 9,17 4,12"></polyline>
|
||||
</svg>
|
||||
نصب روی سرور اختصاصی
|
||||
<span class="text-gray-700">نصب روی سرور اختصاصی</span>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<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="text-success me-2">
|
||||
<li class="flex items-center">
|
||||
<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="text-green-600 ml-3">
|
||||
<polyline points="20,6 9,17 4,12"></polyline>
|
||||
</svg>
|
||||
پیکربندی امنیتی
|
||||
<span class="text-gray-700">پیکربندی امنیتی</span>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<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="text-success me-2">
|
||||
<li class="flex items-center">
|
||||
<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="text-green-600 ml-3">
|
||||
<polyline points="20,6 9,17 4,12"></polyline>
|
||||
</svg>
|
||||
تنظیمات پایگاه داده
|
||||
<span class="text-gray-700">تنظیمات پایگاه داده</span>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<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="text-success me-2">
|
||||
<li class="flex items-center">
|
||||
<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="text-green-600 ml-3">
|
||||
<polyline points="20,6 9,17 4,12"></polyline>
|
||||
</svg>
|
||||
تضمین عملکرد بهینه
|
||||
<span class="text-gray-700">تضمین عملکرد بهینه</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -291,51 +279,45 @@
|
|||
</div>
|
||||
|
||||
<!-- Contact CTA Section -->
|
||||
<div class="row mb-5">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<div class="card border-0 shadow-lg bg-primary text-white">
|
||||
<div class="card-body p-5 text-center">
|
||||
<div class="mb-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="text-warning mb-3">
|
||||
<div class="mb-16">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="bg-gradient-to-br from-blue-600 via-purple-600 to-indigo-700 text-white rounded-3xl shadow-2xl overflow-hidden">
|
||||
<div class="p-8 md:p-12 text-center">
|
||||
<div class="mb-8">
|
||||
<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="text-yellow-400 mb-6 mx-auto">
|
||||
<polygon points="13,2 3,14 12,14 11,22 21,10 12,10 13,2"></polygon>
|
||||
</svg>
|
||||
<h3 class="fw-bold mb-3">آماده شروع هستید؟</h3>
|
||||
<p class="mb-4">برای دریافت خدمات تخصصی و مشاوره رایگان درباره نرم افزار حسابداری حسابیکس، همین حالا با ما تماس بگیرید</p>
|
||||
<h3 class="text-3xl font-bold mb-4">آماده شروع هستید؟</h3>
|
||||
<p class="text-xl text-blue-100 mb-8">برای دریافت خدمات تخصصی و مشاوره رایگان درباره نرم افزار حسابداری حسابیکس، همین حالا با ما تماس بگیرید</p>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 mb-8">
|
||||
<div class="text-center">
|
||||
<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="mb-2">
|
||||
<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="mb-3 mx-auto text-yellow-400">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<polyline points="12,6 12,12 16,14"></polyline>
|
||||
</svg>
|
||||
<h6 class="fw-bold">پاسخ سریع</h6>
|
||||
<small>حداکثر ۲۴ ساعت</small>
|
||||
<h6 class="text-lg font-bold mb-2">پاسخ سریع</h6>
|
||||
<small class="text-blue-200">حداکثر ۲۴ ساعت</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="text-center">
|
||||
<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="mb-2">
|
||||
<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="mb-3 mx-auto text-yellow-400">
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
||||
</svg>
|
||||
<h6 class="fw-bold">پشتیبانی ۲۴/۷</h6>
|
||||
<small>همیشه در دسترس</small>
|
||||
<h6 class="text-lg font-bold mb-2">پشتیبانی ۲۴/۷</h6>
|
||||
<small class="text-blue-200">همیشه در دسترس</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="text-center">
|
||||
<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="mb-2">
|
||||
<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="mb-3 mx-auto text-yellow-400">
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
|
||||
</svg>
|
||||
<h6 class="fw-bold">تضمین کیفیت</h6>
|
||||
<small>خدمات تضمین شده</small>
|
||||
</div>
|
||||
<h6 class="text-lg font-bold mb-2">تضمین کیفیت</h6>
|
||||
<small class="text-blue-200">خدمات تضمین شده</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="{{ path('app_page', {'url': 'contact'}) }}" class="btn btn-warning btn-lg px-5 py-3 fw-bold transition-all">
|
||||
<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="me-2">
|
||||
<a href="{{ path('app_page', {'url': 'contact'}) }}" class="inline-flex items-center px-8 py-4 bg-yellow-500 text-gray-900 rounded-2xl hover:bg-yellow-400 transform hover:-translate-y-1 transition-all duration-300 shadow-lg hover:shadow-xl font-bold text-lg">
|
||||
<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="ml-2">
|
||||
<line x1="22" y1="2" x2="11" y2="13"></line>
|
||||
<polygon points="22,2 15,2 11,6 2,15 2,22 9,22 13,18 22,9 22,2"></polygon>
|
||||
</svg>
|
||||
|
|
@ -347,106 +329,79 @@
|
|||
</div>
|
||||
|
||||
<!-- Benefits Section -->
|
||||
<div class="row">
|
||||
<div class="col-lg-10 mx-auto">
|
||||
<div class="text-center mb-5">
|
||||
<h2 class="display-6 fw-bold text-primary mb-3">مزایای پشتیبانی تخصصی حسابیکس</h2>
|
||||
<p class="text-muted">چرا باید از خدمات پشتیبانی تخصصی ما برای نرم افزار حسابداری حسابیکس استفاده کنید؟</p>
|
||||
<div>
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-4xl md:text-5xl font-bold text-blue-600 mb-6">مزایای پشتیبانی تخصصی حسابیکس</h2>
|
||||
<p class="text-gray-600 text-lg max-w-4xl mx-auto leading-relaxed">چرا باید از خدمات پشتیبانی تخصصی ما برای نرم افزار حسابداری حسابیکس استفاده کنید؟</p>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-md-4">
|
||||
<div class="text-center p-4">
|
||||
<div class="bg-primary bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 80px; height: 80px;">
|
||||
<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="text-primary">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
<div class="text-center p-6 group">
|
||||
<div class="bg-blue-100 rounded-full inline-flex items-center justify-center mb-6 w-20 h-20 group-hover:scale-110 transition-transform duration-300">
|
||||
<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="text-blue-600">
|
||||
<polygon points="13,2 3,14 12,14 11,22 21,10 12,10 13,2"></polygon>
|
||||
</svg>
|
||||
</div>
|
||||
<h5 class="fw-bold mb-3">پاسخ سریع</h5>
|
||||
<p class="text-muted">دریافت پاسخ تخصصی در کمترین زمان ممکن</p>
|
||||
<h5 class="text-xl font-bold mb-3 text-gray-800">پاسخ سریع</h5>
|
||||
<p class="text-gray-600">دریافت پاسخ تخصصی در کمترین زمان ممکن</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="text-center p-4">
|
||||
<div class="bg-success bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 80px; height: 80px;">
|
||||
<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="text-success">
|
||||
<div class="text-center p-6 group">
|
||||
<div class="bg-green-100 rounded-full inline-flex items-center justify-center mb-6 w-20 h-20 group-hover:scale-110 transition-transform duration-300">
|
||||
<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="text-green-600">
|
||||
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h5 class="fw-bold mb-3">حل مشکلات</h5>
|
||||
<p class="text-muted">حل مشکلات پیچیده حسابداری در کمترین زمان</p>
|
||||
<h5 class="text-xl font-bold mb-3 text-gray-800">حل مشکلات</h5>
|
||||
<p class="text-gray-600">حل مشکلات پیچیده حسابداری در کمترین زمان</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="text-center p-4">
|
||||
<div class="bg-info bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 80px; height: 80px;">
|
||||
<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="text-info">
|
||||
<div class="text-center p-6 group">
|
||||
<div class="bg-blue-100 rounded-full inline-flex items-center justify-center mb-6 w-20 h-20 group-hover:scale-110 transition-transform duration-300">
|
||||
<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="text-blue-600">
|
||||
<polyline points="16,18 22,12 16,6"></polyline>
|
||||
<polyline points="8,6 2,12 8,18"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
<h5 class="fw-bold mb-3">توسعه سفارشی</h5>
|
||||
<p class="text-muted">توسعه قابلیتهای اختصاصی برای کسب و کار شما</p>
|
||||
<h5 class="text-xl font-bold mb-3 text-gray-800">توسعه سفارشی</h5>
|
||||
<p class="text-gray-600">توسعه قابلیتهای اختصاصی برای کسب و کار شما</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="text-center p-4">
|
||||
<div class="bg-warning bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 80px; height: 80px;">
|
||||
<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="text-warning">
|
||||
<div class="text-center p-6 group">
|
||||
<div class="bg-yellow-100 rounded-full inline-flex items-center justify-center mb-6 w-20 h-20 group-hover:scale-110 transition-transform duration-300">
|
||||
<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="text-yellow-600">
|
||||
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
|
||||
<circle cx="9" cy="7" r="4"></circle>
|
||||
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
|
||||
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h5 class="fw-bold mb-3">آموزش کارکنان</h5>
|
||||
<p class="text-muted">آموزش کامل کارکنان برای استفاده بهینه از حسابیکس</p>
|
||||
<h5 class="text-xl font-bold mb-3 text-gray-800">آموزش کارکنان</h5>
|
||||
<p class="text-gray-600">آموزش کامل کارکنان برای استفاده بهینه از حسابیکس</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="text-center p-4">
|
||||
<div class="bg-danger bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 80px; height: 80px;">
|
||||
<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="text-danger">
|
||||
<div class="text-center p-6 group">
|
||||
<div class="bg-red-100 rounded-full inline-flex items-center justify-center mb-6 w-20 h-20 group-hover:scale-110 transition-transform duration-300">
|
||||
<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="text-red-600">
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
<path d="M12 1v6m0 6v6"></path>
|
||||
<path d="M21 12h-6m-6 0H3"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h5 class="fw-bold mb-3">پشتیبانی از راه دور</h5>
|
||||
<p class="text-muted">حل مشکلات بدون نیاز به حضور فیزیکی</p>
|
||||
<h5 class="text-xl font-bold mb-3 text-gray-800">پشتیبانی از راه دور</h5>
|
||||
<p class="text-gray-600">حل مشکلات بدون نیاز به حضور فیزیکی</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="text-center p-4">
|
||||
<div class="bg-secondary bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 80px; height: 80px;">
|
||||
<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="text-secondary">
|
||||
<div class="text-center p-6 group">
|
||||
<div class="bg-gray-100 rounded-full inline-flex items-center justify-center mb-6 w-20 h-20 group-hover:scale-110 transition-transform duration-300">
|
||||
<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="text-gray-600">
|
||||
<polyline points="23,4 23,10 17,10"></polyline>
|
||||
<polyline points="1,20 1,14 7,14"></polyline>
|
||||
<path d="M20 21C20 19.6044 20 18.4067 20 17.4069C20 16.4072 20 15.6081 20 15.0096C20 14.4111 20 14.012 20 13.8125C20 13.6129 20 13.5132 20 13.5132C20 13.5132 20 13.6129 20 13.8125C20 14.012 20 14.4111 20 15.0096C20 15.6081 20 16.4072 20 17.4069C20 18.4067 20 19.6044 20 21"></path>
|
||||
<path d="M4 3C4 4.3956 4 5.5933 4 6.5931C4 7.5928 4 8.3919 4 8.9904C4 9.5889 4 9.988 4 10.1875C4 10.3871 4 10.4868 4 10.4868C4 10.4868 4 10.3871 4 10.1875C4 9.988 4 9.5889 4 8.9904C4 8.3919 4 7.5928 4 6.5931C4 5.5933 4 4.3956 4 3"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h5 class="fw-bold mb-3">بهروزرسانی مداوم</h5>
|
||||
<p class="text-muted">بهروزرسانی و نگهداری منظم نرم افزار حسابداری</p>
|
||||
<h5 class="text-xl font-bold mb-3 text-gray-800">بهروزرسانی مداوم</h5>
|
||||
<p class="text-gray-600">بهروزرسانی و نگهداری منظم نرم افزار حسابداری</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.hover-card {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.hover-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 25px rgba(0,0,0,0.1) !important;
|
||||
}
|
||||
|
||||
.transition-all {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
|
@ -2,155 +2,6 @@
|
|||
|
||||
{% block title %}نصب حسابیکس{% endblock %}
|
||||
|
||||
{% block stylesheets %}
|
||||
{{ parent() }}
|
||||
<style>
|
||||
.installation-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.step-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.step-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
background: rgba(255,255,255,0.2);
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
background: #f0f0f0;
|
||||
border-radius: 10px;
|
||||
height: 8px;
|
||||
margin: 20px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.status-message {
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.status-success {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.status-error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.status-info {
|
||||
background: #d1ecf1;
|
||||
color: #0c5460;
|
||||
border: 1px solid #bee5eb;
|
||||
}
|
||||
|
||||
.step-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #667eea;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="installation-container" data-controller="installation">
|
||||
<h1 class="text-center mb-4">نصب خودکار حسابیکس</h1>
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@
|
|||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
<span>{{ Jdate.jdate('Y/n/d',post.dateSubmit) }}</span>
|
||||
<span>{{ Jdate.jdate('Y/n/d',post.dateSubmit|date('U')) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 space-x-reverse">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
<span>{{Jdate.jdate('Y/n/d',item.dateSubmit)}}</span>
|
||||
<span>{{Jdate.jdate('Y/n/d',item.dateSubmit|date('U'))}}</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 space-x-reverse">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
@ -90,39 +90,153 @@
|
|||
{{ item.body | raw }}
|
||||
</div>
|
||||
|
||||
</article>
|
||||
|
||||
<!-- اشتراکگذاری -->
|
||||
<div class="mt-12 pt-8 border-t border-gray-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">این مطلب را به اشتراک بگذارید</h3>
|
||||
<div class="flex space-x-3 space-x-reverse">
|
||||
<div class="text-center">
|
||||
<h3 class="text-xl font-bold text-gray-900 mb-6 flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-blue-600 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.367 2.684 3 3 0 00-5.367-2.684z"></path>
|
||||
</svg>
|
||||
این مطلب را به اشتراک بگذارید
|
||||
</h3>
|
||||
<div class="flex flex-wrap justify-center gap-4">
|
||||
<!-- توییتر -->
|
||||
<a href="https://twitter.com/intent/tweet?text={{item.title|url_encode}}&url={{app.request.uri|url_encode}}"
|
||||
target="_blank"
|
||||
class="flex items-center space-x-2 space-x-reverse px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
class="group flex items-center space-x-2 space-x-reverse px-6 py-3 bg-gradient-to-r from-blue-400 to-blue-500 text-white rounded-xl hover:from-blue-500 hover:to-blue-600 transition-all duration-300 transform hover:scale-105 hover:shadow-lg">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/>
|
||||
</svg>
|
||||
<span>توییتر</span>
|
||||
<span class="font-medium">توییتر</span>
|
||||
</a>
|
||||
|
||||
<!-- لینکدین -->
|
||||
<a href="https://www.linkedin.com/sharing/share-offsite/?url={{app.request.uri|url_encode}}"
|
||||
target="_blank"
|
||||
class="flex items-center space-x-2 space-x-reverse px-4 py-2 bg-blue-700 text-white rounded-lg hover:bg-blue-800 transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
class="group flex items-center space-x-2 space-x-reverse px-6 py-3 bg-gradient-to-r from-blue-600 to-blue-700 text-white rounded-xl hover:from-blue-700 hover:to-blue-800 transition-all duration-300 transform hover:scale-105 hover:shadow-lg">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
|
||||
</svg>
|
||||
<span>لینکدین</span>
|
||||
<span class="font-medium">لینکدین</span>
|
||||
</a>
|
||||
|
||||
<!-- تلگرام -->
|
||||
<a href="https://t.me/share/url?url={{app.request.uri|url_encode}}&text={{item.title|url_encode}}"
|
||||
target="_blank"
|
||||
class="flex items-center space-x-2 space-x-reverse px-4 py-2 bg-blue-400 text-white rounded-lg hover:bg-blue-500 transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
class="group flex items-center space-x-2 space-x-reverse px-6 py-3 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-xl hover:from-blue-600 hover:to-blue-700 transition-all duration-300 transform hover:scale-105 hover:shadow-lg">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"/>
|
||||
</svg>
|
||||
<span>تلگرام</span>
|
||||
<span class="font-medium">تلگرام</span>
|
||||
</a>
|
||||
|
||||
<!-- واتساپ -->
|
||||
<a href="https://wa.me/?text={{item.title|url_encode}}%20{{app.request.uri|url_encode}}"
|
||||
target="_blank"
|
||||
class="group flex items-center space-x-2 space-x-reverse px-6 py-3 bg-gradient-to-r from-green-500 to-green-600 text-white rounded-xl hover:from-green-600 hover:to-green-700 transition-all duration-300 transform hover:scale-105 hover:shadow-lg">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893A11.821 11.821 0 0020.885 3.488"/>
|
||||
</svg>
|
||||
<span class="font-medium">واتساپ</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- بخش کامنتها -->
|
||||
<div class="mt-4">
|
||||
<!-- نمایش کامنتهای موجود -->
|
||||
{% if comments|length > 0 %}
|
||||
<div class="mb-12">
|
||||
<h3 class="text-2xl font-bold text-gray-900 mb-8 flex items-center">
|
||||
<svg class="w-6 h-6 text-blue-600 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
|
||||
</svg>
|
||||
نظرات ({{ comments|length }})
|
||||
</h3>
|
||||
<div class="space-y-6">
|
||||
{% for comment in comments %}
|
||||
<div class="bg-white rounded-lg border border-gray-200 p-6">
|
||||
<div class="flex items-start space-x-4 space-x-reverse">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span class="text-blue-600 font-semibold text-sm">
|
||||
{{ comment.name|slice(0, 1)|upper }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center space-x-2 space-x-reverse mb-3">
|
||||
<h4 class="text-sm font-semibold text-gray-900">{{ comment.name }}</h4>
|
||||
<span class="text-xs text-gray-500">{{ Jdate.jdate('Y/n/d H:i', comment.dateSubmit|date('U')) }}</span>
|
||||
{% if comment.website %}
|
||||
<a href="{{ comment.website }}" target="_blank" class="text-blue-500 hover:text-blue-700 text-xs">
|
||||
وبسایت
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<p class="text-gray-700 leading-relaxed">{{ comment.body }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- فرم ارسال کامنت -->
|
||||
<div class="bg-gray-50 rounded-lg p-8">
|
||||
<h3 class="text-xl font-bold text-gray-900 mb-6">نظر خود را بنویسید</h3>
|
||||
<form method="post" action="{{ path('app_blog_post_comment', {'url': item.url}) }}" class="space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label for="comment_name" class="block text-sm font-medium text-gray-700 mb-2">نام *</label>
|
||||
<input type="text"
|
||||
id="comment_name"
|
||||
name="comment[name]"
|
||||
required
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors duration-200"
|
||||
placeholder="نام شما">
|
||||
</div>
|
||||
<div>
|
||||
<label for="comment_email" class="block text-sm font-medium text-gray-700 mb-2">ایمیل</label>
|
||||
<input type="email"
|
||||
id="comment_email"
|
||||
name="comment[email]"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors duration-200"
|
||||
placeholder="ایمیل شما (اختیاری)">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="comment_website" class="block text-sm font-medium text-gray-700 mb-2">وبسایت</label>
|
||||
<input type="url"
|
||||
id="comment_website"
|
||||
name="comment[website]"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors duration-200"
|
||||
placeholder="آدرس وبسایت شما (اختیاری)">
|
||||
</div>
|
||||
<div>
|
||||
<label for="comment_body" class="block text-sm font-medium text-gray-700 mb-2">نظر *</label>
|
||||
<textarea id="comment_body"
|
||||
name="comment[body]"
|
||||
required
|
||||
rows="5"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors duration-200 resize-none"
|
||||
placeholder="نظر خود را اینجا بنویسید..."></textarea>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-sm text-gray-500">
|
||||
نظر شما پس از تایید مدیر نمایش داده خواهد شد
|
||||
</p>
|
||||
<button type="submit"
|
||||
class="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors duration-200 font-medium">
|
||||
ارسال نظر
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
|
@ -152,7 +266,7 @@
|
|||
{{post.title}}
|
||||
</h4>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
{{ Jdate.jdate('Y/n/d',post.dateSubmit) }}
|
||||
{{ Jdate.jdate('Y/n/d',post.dateSubmit|date('U')) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,68 +3,74 @@
|
|||
{% block title %}پاسخ به سوال: {{ question.title }} - پرسش و پاسخ{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container my-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<main class="min-h-screen bg-gray-50">
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<!-- نمایش سوال -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h4 class="mb-0">سوال:</h4>
|
||||
<div class="bg-white rounded-2xl shadow-soft mb-8 overflow-hidden">
|
||||
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 px-8 py-6 border-b border-gray-200">
|
||||
<h2 class="text-2xl font-bold text-gray-900 flex items-center">
|
||||
<svg class="w-6 h-6 text-blue-600 ml-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
سوال:
|
||||
</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ question.title }}</h5>
|
||||
<div class="question-content">
|
||||
{{ question.content|nl2br }}
|
||||
<div class="p-8">
|
||||
<h3 class="text-2xl font-bold text-gray-900 mb-4">{{ question.title }}</h3>
|
||||
<div class="prose prose-lg max-w-none text-gray-800 leading-relaxed mb-6">
|
||||
{{ question.content|markdown|raw }}
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<div class="tags">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% for tagRelation in question.tagRelations %}
|
||||
<span class="badge bg-light text-dark me-1">
|
||||
<a href="{{ path('qa_tag_questions', {'name': tagRelation.tag.name}) }}"
|
||||
class="inline-flex items-center px-3 py-1 bg-blue-100 text-blue-800 text-sm font-medium rounded-full hover:bg-blue-200 transition-colors duration-200">
|
||||
{{ tagRelation.tag.name }}
|
||||
</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- فرم پاسخ -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="mb-0">
|
||||
<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="me-2">
|
||||
<div class="bg-white rounded-2xl shadow-soft overflow-hidden">
|
||||
<div class="bg-gradient-to-r from-green-50 to-emerald-50 px-8 py-6 border-b border-gray-200">
|
||||
<h2 class="text-2xl font-bold text-gray-900 flex items-center">
|
||||
<svg class="w-6 h-6 text-green-600 ml-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<polyline points="9,17 4,12 9,7"></polyline>
|
||||
<path d="M20 18v-2a4 4 0 0 0-4-4H4"></path>
|
||||
</svg>پاسخ شما
|
||||
</h4>
|
||||
</svg>
|
||||
پاسخ شما
|
||||
</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{ form_start(form, {'attr': {'novalidate': 'novalidate'}}) }}
|
||||
<div class="p-8">
|
||||
{{ form_start(form, {'attr': {'novalidate': 'novalidate', 'class': 'space-y-6'}}) }}
|
||||
|
||||
<div class="mb-4">
|
||||
{{ form_label(form.content) }}
|
||||
<div class="editor-toolbar mb-2">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="formatText('bold')" 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">
|
||||
<path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"></path>
|
||||
<path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"></path>
|
||||
<div>
|
||||
{{ form_label(form.content, null, {'label_attr': {'class': 'block text-sm font-medium text-gray-700 mb-2'}}) }}
|
||||
<div class="qa-editor-toolbar border border-gray-300 border-b-0 rounded-t-xl p-3 bg-gray-50">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button type="button" class="px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all duration-200" onclick="formatText('bold')" title="پررنگ">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="formatText('italic')" 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">
|
||||
<button type="button" class="px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all duration-200" onclick="formatText('italic')" title="کج">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<line x1="19" y1="4" x2="10" y2="4"></line>
|
||||
<line x1="14" y1="20" x2="5" y2="20"></line>
|
||||
<line x1="15" y1="4" x2="9" y2="20"></line>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="formatText('code')" 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">
|
||||
<button type="button" class="px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all duration-200" onclick="formatText('code')" title="کد">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<polyline points="16,18 22,12 16,6"></polyline>
|
||||
<polyline points="8,6 2,12 8,18"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="insertList()" 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">
|
||||
<button type="button" class="px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all duration-200" onclick="insertList()" title="لیست">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<line x1="8" y1="6" x2="21" y2="6"></line>
|
||||
<line x1="8" y1="12" x2="21" y2="12"></line>
|
||||
<line x1="8" y1="18" x2="21" y2="18"></line>
|
||||
|
|
@ -74,20 +80,60 @@
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{{ form_widget(form.content, {'attr': {'class': 'form-control editor-textarea', 'rows': 8}}) }}
|
||||
</div>
|
||||
{{ form_widget(form.content, {'attr': {'class': 'w-full px-4 py-3 border border-gray-300 border-t-0 rounded-b-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200', 'rows': 8}}) }}
|
||||
{{ form_errors(form.content) }}
|
||||
<div class="form-text">
|
||||
<div class="mt-2 text-sm text-gray-600">
|
||||
پاسخ خود را به صورت کامل و مفصل ارائه دهید. هرچه پاسخ شما دقیقتر باشد، برای دیگران مفیدتر خواهد بود.
|
||||
<br><small class="text-muted">میتوانید از دکمههای بالا برای فرمت کردن متن استفاده کنید.</small>
|
||||
<br><span class="text-gray-500">میتوانید از دکمههای بالا برای فرمت کردن متن استفاده کنید.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{{ path('qa_question_show', {'id': question.id}) }}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-right me-2"></i>انصراف
|
||||
<!-- پیوست فایل -->
|
||||
<div>
|
||||
<label for="{{ form.attachments.vars.id }}" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
پیوست فایل
|
||||
<span class="text-gray-500">(اختیاری)</span>
|
||||
</label>
|
||||
<div class="border-2 border-dashed border-gray-300 rounded-xl p-6 bg-gray-50 hover:bg-gray-100 transition-colors duration-200">
|
||||
{{ form_widget(form.attachments, {'attr': {'class': 'w-full', 'id': 'file-input'}}) }}
|
||||
{{ form_errors(form.attachments) }}
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<div class="flex items-center justify-center space-x-2 space-x-reverse text-gray-500">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
|
||||
</svg>
|
||||
<span class="text-sm">فایلهای خود را اینجا بکشید یا کلیک کنید</span>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 mt-2">
|
||||
فرمتهای مجاز: JPG, PNG, GIF, PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX, TXT
|
||||
<br>حداکثر 3 فایل، هر کدام 4 مگابایت
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- نمایش فایلهای انتخاب شده -->
|
||||
<div id="selected-files" class="mt-4 hidden">
|
||||
<h4 class="text-sm font-medium text-gray-700 mb-2">فایلهای انتخاب شده:</h4>
|
||||
<div id="file-list" class="space-y-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center pt-6">
|
||||
<a href="{{ path('qa_question_show', {'id': question.id}) }}"
|
||||
class="inline-flex items-center px-6 py-3 bg-gray-100 text-gray-700 rounded-xl hover:bg-gray-200 transition-all duration-200 font-medium">
|
||||
<svg class="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
|
||||
</svg>
|
||||
انصراف
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-paper-plane me-2"></i>ارسال پاسخ
|
||||
<button type="submit"
|
||||
class="inline-flex items-center px-8 py-3 bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition-all duration-200 font-medium shadow-lg hover:shadow-xl transform hover:-translate-y-0.5">
|
||||
<svg class="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path>
|
||||
</svg>
|
||||
ارسال پاسخ
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -96,30 +142,83 @@
|
|||
</div>
|
||||
|
||||
<!-- راهنمای پاسخ دادن -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-lightbulb me-2"></i>راهنمای پاسخ دادن
|
||||
</h5>
|
||||
<div class="bg-white rounded-2xl shadow-soft mt-8 overflow-hidden">
|
||||
<div class="bg-gradient-to-r from-yellow-50 to-orange-50 px-8 py-6 border-b border-gray-200">
|
||||
<h3 class="text-xl font-bold text-gray-900 flex items-center">
|
||||
<svg class="w-6 h-6 text-yellow-600 ml-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"></path>
|
||||
</svg>
|
||||
راهنمای پاسخ دادن
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6 class="text-success">✅ پاسخ خوب:</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><i class="fas fa-check text-success me-2"></i>مستقیماً به سوال پاسخ دهید</li>
|
||||
<li><i class="fas fa-check text-success me-2"></i>از مثالهای عملی استفاده کنید</li>
|
||||
<li><i class="fas fa-check text-success me-2"></i>منابع و لینکهای مفید ارائه دهید</li>
|
||||
<li><i class="fas fa-check text-success me-2"></i>کد و نمونههای کد ارائه دهید</li>
|
||||
<div class="p-8">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<h4 class="text-lg font-semibold text-green-700 mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 text-green-600 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
پاسخ خوب:
|
||||
</h4>
|
||||
<ul class="space-y-3">
|
||||
<li class="flex items-start">
|
||||
<svg class="w-5 h-5 text-green-600 ml-3 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
<span class="text-gray-700">مستقیماً به سوال پاسخ دهید</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<svg class="w-5 h-5 text-green-600 ml-3 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
<span class="text-gray-700">از مثالهای عملی استفاده کنید</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<svg class="w-5 h-5 text-green-600 ml-3 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
<span class="text-gray-700">منابع و لینکهای مفید ارائه دهید</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<svg class="w-5 h-5 text-green-600 ml-3 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
<span class="text-gray-700">کد و نمونههای کد ارائه دهید</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="text-danger">❌ پاسخ ضعیف:</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><i class="fas fa-times text-danger me-2"></i>پاسخ مبهم و کلی</li>
|
||||
<li><i class="fas fa-times text-danger me-2"></i>عدم ارائه راهحل عملی</li>
|
||||
<li><i class="fas fa-times text-danger me-2"></i>تکرار پاسخهای قبلی</li>
|
||||
<li><i class="fas fa-times text-danger me-2"></i>پاسخ نامربوط</li>
|
||||
<div>
|
||||
<h4 class="text-lg font-semibold text-red-700 mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 text-red-600 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
پاسخ ضعیف:
|
||||
</h4>
|
||||
<ul class="space-y-3">
|
||||
<li class="flex items-start">
|
||||
<svg class="w-5 h-5 text-red-600 ml-3 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
<span class="text-gray-700">پاسخ مبهم و کلی</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<svg class="w-5 h-5 text-red-600 ml-3 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
<span class="text-gray-700">عدم ارائه راهحل عملی</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<svg class="w-5 h-5 text-red-600 ml-3 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
<span class="text-gray-700">تکرار پاسخهای قبلی</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<svg class="w-5 h-5 text-red-600 ml-3 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
<span class="text-gray-700">پاسخ نامربوط</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -127,60 +226,13 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.form-control:focus {
|
||||
border-color: #0d6efd;
|
||||
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
.card {
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.question-content {
|
||||
line-height: 1.6;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.form-text {
|
||||
font-size: 0.9rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.list-unstyled li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.tags .badge {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.editor-toolbar {
|
||||
border: 1px solid #dee2e6;
|
||||
border-bottom: none;
|
||||
border-radius: 0.375rem 0.375rem 0 0;
|
||||
padding: 0.5rem;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.editor-textarea {
|
||||
border-radius: 0 0 0.375rem 0.375rem;
|
||||
border-top: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// توابع ادیتور متن
|
||||
function formatText(command) {
|
||||
const textarea = document.querySelector('.editor-textarea');
|
||||
const textarea = document.querySelector('textarea[name="answer[content]"]');
|
||||
const start = textarea.selectionStart;
|
||||
const end = textarea.selectionEnd;
|
||||
const selectedText = textarea.value.substring(start, end);
|
||||
|
|
@ -205,7 +257,7 @@ function formatText(command) {
|
|||
}
|
||||
|
||||
function insertList() {
|
||||
const textarea = document.querySelector('.editor-textarea');
|
||||
const textarea = document.querySelector('textarea[name="answer[content]"]');
|
||||
const start = textarea.selectionStart;
|
||||
const end = textarea.selectionEnd;
|
||||
const selectedText = textarea.value.substring(start, end);
|
||||
|
|
@ -214,5 +266,193 @@ function insertList() {
|
|||
textarea.value = textarea.value.substring(0, start) + listText + textarea.value.substring(end);
|
||||
textarea.focus();
|
||||
}
|
||||
|
||||
// مدیریت فایلها
|
||||
class FileManager {
|
||||
constructor() {
|
||||
this.maxFiles = 3;
|
||||
this.maxFileSize = 4 * 1024 * 1024; // 4MB
|
||||
this.allowedTypes = [
|
||||
'image/jpeg', 'image/png', 'image/gif',
|
||||
'application/pdf',
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.ms-powerpoint',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'text/plain'
|
||||
];
|
||||
this.selectedFiles = [];
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.fileInput = document.getElementById('file-input');
|
||||
this.selectedFilesDiv = document.getElementById('selected-files');
|
||||
this.fileListDiv = document.getElementById('file-list');
|
||||
|
||||
if (!this.fileInput) return;
|
||||
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.fileInput.addEventListener('change', (e) => this.handleFileSelect(e));
|
||||
|
||||
// Drag and drop
|
||||
const dropZone = this.fileInput.closest('.border-dashed');
|
||||
if (dropZone) {
|
||||
dropZone.addEventListener('dragover', (e) => this.handleDragOver(e));
|
||||
dropZone.addEventListener('drop', (e) => this.handleDrop(e));
|
||||
dropZone.addEventListener('dragleave', (e) => this.handleDragLeave(e));
|
||||
}
|
||||
}
|
||||
|
||||
handleFileSelect(event) {
|
||||
const files = Array.from(event.target.files);
|
||||
this.processFiles(files);
|
||||
}
|
||||
|
||||
handleDragOver(event) {
|
||||
event.preventDefault();
|
||||
event.currentTarget.classList.add('bg-blue-50', 'border-blue-400');
|
||||
}
|
||||
|
||||
handleDragLeave(event) {
|
||||
event.preventDefault();
|
||||
event.currentTarget.classList.remove('bg-blue-50', 'border-blue-400');
|
||||
}
|
||||
|
||||
handleDrop(event) {
|
||||
event.preventDefault();
|
||||
event.currentTarget.classList.remove('bg-blue-50', 'border-blue-400');
|
||||
|
||||
const files = Array.from(event.dataTransfer.files);
|
||||
this.processFiles(files);
|
||||
}
|
||||
|
||||
processFiles(files) {
|
||||
files.forEach(file => {
|
||||
if (this.validateFile(file)) {
|
||||
this.addFile(file);
|
||||
}
|
||||
});
|
||||
this.updateFileInput();
|
||||
}
|
||||
|
||||
validateFile(file) {
|
||||
// بررسی تعداد فایلها
|
||||
if (this.selectedFiles.length >= this.maxFiles) {
|
||||
alert(`حداکثر ${this.maxFiles} فایل میتوانید انتخاب کنید`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// بررسی نوع فایل
|
||||
if (!this.allowedTypes.includes(file.type)) {
|
||||
alert('فرمت فایل مجاز نیست');
|
||||
return false;
|
||||
}
|
||||
|
||||
// بررسی حجم فایل
|
||||
if (file.size > this.maxFileSize) {
|
||||
alert('حجم فایل نمیتواند بیش از 4 مگابایت باشد');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
addFile(file) {
|
||||
const fileId = Date.now() + Math.random();
|
||||
this.selectedFiles.push({
|
||||
id: fileId,
|
||||
file: file,
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type
|
||||
});
|
||||
|
||||
this.renderFiles();
|
||||
}
|
||||
|
||||
removeFile(fileId) {
|
||||
this.selectedFiles = this.selectedFiles.filter(f => f.id !== fileId);
|
||||
this.renderFiles();
|
||||
this.updateFileInput();
|
||||
}
|
||||
|
||||
renderFiles() {
|
||||
if (this.selectedFiles.length === 0) {
|
||||
this.selectedFilesDiv.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedFilesDiv.classList.remove('hidden');
|
||||
this.fileListDiv.innerHTML = '';
|
||||
|
||||
this.selectedFiles.forEach(fileData => {
|
||||
const fileElement = document.createElement('div');
|
||||
fileElement.className = 'flex items-center justify-between p-3 bg-white rounded-lg border border-gray-200';
|
||||
fileElement.innerHTML = `
|
||||
<div class="flex items-center space-x-3 space-x-reverse">
|
||||
<div class="flex-shrink-0">
|
||||
${this.getFileIcon(fileData.type)}
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-gray-900 truncate">${fileData.name}</p>
|
||||
<p class="text-xs text-gray-500">${this.formatFileSize(fileData.size)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button"
|
||||
class="flex-shrink-0 text-red-600 hover:text-red-800 transition-colors duration-200"
|
||||
onclick="fileManager.removeFile(${fileData.id})">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
`;
|
||||
this.fileListDiv.appendChild(fileElement);
|
||||
});
|
||||
}
|
||||
|
||||
getFileIcon(type) {
|
||||
if (type.startsWith('image/')) {
|
||||
return '<svg class="w-6 h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21,15 16,10 5,21"></polyline></svg>';
|
||||
} else if (type === 'application/pdf') {
|
||||
return '<svg class="w-6 h-6 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path></svg>';
|
||||
} else if (type.includes('word') || type.includes('document')) {
|
||||
return '<svg class="w-6 h-6 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path></svg>';
|
||||
} else {
|
||||
return '<svg class="w-6 h-6 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path></svg>';
|
||||
}
|
||||
}
|
||||
|
||||
formatFileSize(bytes) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
updateFileInput() {
|
||||
// ایجاد DataTransfer object جدید
|
||||
const dt = new DataTransfer();
|
||||
this.selectedFiles.forEach(fileData => {
|
||||
dt.items.add(fileData.file);
|
||||
});
|
||||
this.fileInput.files = dt.files;
|
||||
}
|
||||
}
|
||||
|
||||
// متغیرهای سراسری
|
||||
let fileManager;
|
||||
|
||||
// مقداردهی اولیه
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
fileManager = new FileManager();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<!-- فرم ایجاد سوال -->
|
||||
<div class="bg-white rounded-2xl shadow-soft overflow-hidden">
|
||||
<div class="p-8">
|
||||
{{ form_start(form, {'attr': {'novalidate': 'novalidate', 'class': 'space-y-8'}}) }}
|
||||
{{ form_start(form, {'attr': {'novalidate': 'novalidate', 'class': 'space-y-8', 'data-turbo': 'false'}}) }}
|
||||
|
||||
<!-- عنوان سوال -->
|
||||
<div>
|
||||
|
|
@ -175,6 +175,50 @@
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<!-- اطلاعرسانی ایمیل -->
|
||||
<div>
|
||||
<div class="flex items-center space-x-3 space-x-reverse">
|
||||
{{ form_widget(form.notifyOnAnswer, {'attr': {'class': 'form-check-input'}}) }}
|
||||
<label for="{{ form.notifyOnAnswer.vars.id }}" class="text-sm font-medium text-gray-700">
|
||||
اطلاعرسانی ایمیل هنگام دریافت پاسخ
|
||||
</label>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-600">
|
||||
در صورت انتخاب این گزینه، هر زمان که برای سوال شما پاسخی ارسال شود، از طریق ایمیل مطلع خواهید شد.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- پیوست فایل -->
|
||||
<div>
|
||||
<label for="{{ form.attachments.vars.id }}" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
پیوست فایل
|
||||
<span class="text-gray-500">(اختیاری)</span>
|
||||
</label>
|
||||
<div class="border-2 border-dashed border-gray-300 rounded-xl p-6 bg-gray-50 hover:bg-gray-100 transition-colors duration-200">
|
||||
{{ form_widget(form.attachments, {'attr': {'class': 'w-full', 'id': 'file-input'}}) }}
|
||||
{{ form_errors(form.attachments) }}
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<div class="flex items-center justify-center space-x-2 space-x-reverse text-gray-500">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
|
||||
</svg>
|
||||
<span class="text-sm">فایلهای خود را اینجا بکشید یا کلیک کنید</span>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 mt-2">
|
||||
فرمتهای مجاز: JPG, PNG, GIF, PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX, TXT
|
||||
<br>حداکثر 3 فایل، هر کدام 4 مگابایت
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- نمایش فایلهای انتخاب شده -->
|
||||
<div id="selected-files" class="mt-4 hidden">
|
||||
<h4 class="text-sm font-medium text-gray-700 mb-2">فایلهای انتخاب شده:</h4>
|
||||
<div id="file-list" class="space-y-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- دکمههای فرم -->
|
||||
<div class="flex justify-between items-center pt-6 border-t border-gray-200">
|
||||
<a href="{{ path('qa_index') }}"
|
||||
|
|
@ -293,7 +337,12 @@
|
|||
|
||||
<script>
|
||||
// سیستم مدیریت تگها
|
||||
class TagManager {
|
||||
if (typeof window.QuestionFormClasses === 'undefined') {
|
||||
window.QuestionFormClasses = {};
|
||||
}
|
||||
|
||||
if (typeof window.QuestionFormClasses.TagManager === 'undefined') {
|
||||
window.QuestionFormClasses.TagManager = class TagManager {
|
||||
constructor() {
|
||||
this.selectedTags = [];
|
||||
this.maxTags = 5;
|
||||
|
|
@ -320,19 +369,17 @@ class TagManager {
|
|||
}
|
||||
|
||||
bindEvents() {
|
||||
// حذف event listener های قبلی
|
||||
this.removeEventListeners();
|
||||
|
||||
// افزودن تگ با دکمه
|
||||
this.addTagBtn.addEventListener('click', () => this.addTagFromInput());
|
||||
this.addTagBtn.addEventListener('click', this.handleAddTagClick.bind(this));
|
||||
|
||||
// افزودن تگ با Enter
|
||||
this.tagInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.addTagFromInput();
|
||||
}
|
||||
});
|
||||
this.tagInput.addEventListener('keypress', this.handleKeyPress.bind(this));
|
||||
|
||||
// جستجوی تگها
|
||||
this.tagInput.addEventListener('input', () => this.filterSuggestions());
|
||||
this.tagInput.addEventListener('input', this.handleInputChange.bind(this));
|
||||
|
||||
// کلیک روی تگهای پیشنهادی
|
||||
this.tagSuggestions.forEach(suggestion => {
|
||||
|
|
@ -341,10 +388,42 @@ class TagManager {
|
|||
|
||||
// نمایش همه تگها
|
||||
if (this.showAllTagsBtn) {
|
||||
this.showAllTagsBtn.addEventListener('click', () => this.showAllTags());
|
||||
this.showAllTagsBtn.addEventListener('click', this.handleShowAllTags.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
removeEventListeners() {
|
||||
if (this.addTagBtn) {
|
||||
this.addTagBtn.removeEventListener('click', this.handleAddTagClick);
|
||||
}
|
||||
if (this.tagInput) {
|
||||
this.tagInput.removeEventListener('keypress', this.handleKeyPress);
|
||||
this.tagInput.removeEventListener('input', this.handleInputChange);
|
||||
}
|
||||
if (this.showAllTagsBtn) {
|
||||
this.showAllTagsBtn.removeEventListener('click', this.handleShowAllTags);
|
||||
}
|
||||
}
|
||||
|
||||
handleAddTagClick() {
|
||||
this.addTagFromInput();
|
||||
}
|
||||
|
||||
handleKeyPress(e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.addTagFromInput();
|
||||
}
|
||||
}
|
||||
|
||||
handleInputChange() {
|
||||
this.filterSuggestions();
|
||||
}
|
||||
|
||||
handleShowAllTags() {
|
||||
this.showAllTags();
|
||||
}
|
||||
|
||||
addTag(tagId, tagName) {
|
||||
// بررسی محدودیت تعداد تگها
|
||||
if (this.selectedTags.length >= this.maxTags) {
|
||||
|
|
@ -363,13 +442,19 @@ class TagManager {
|
|||
}
|
||||
|
||||
this.selectedTags.push({ id: tagId, name: tagName });
|
||||
|
||||
this.updateTagCount();
|
||||
this.renderSelectedTags();
|
||||
this.updateHiddenInput();
|
||||
this.updateSuggestionState(tagId, true);
|
||||
|
||||
if (window.notification) {
|
||||
// نمایش پیام موفقیت فقط یک بار
|
||||
if (window.notification && !this._showingMessage) {
|
||||
this._showingMessage = true;
|
||||
window.notification.success(`تگ "${tagName}" اضافه شد`);
|
||||
setTimeout(() => {
|
||||
this._showingMessage = false;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -595,17 +680,54 @@ class TagManager {
|
|||
|
||||
validateTags() {
|
||||
if (this.selectedTags.length < this.minTags) {
|
||||
if (window.notification) {
|
||||
window.notification.error(`حداقل ${this.minTags} تگ باید انتخاب کنید`);
|
||||
}
|
||||
// نمایش پیام خطای زیبا
|
||||
this.showValidationError(`حداقل ${this.minTags} تگ باید انتخاب کنید`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
showValidationError(message) {
|
||||
// حذف پیام خطای قبلی اگر وجود دارد
|
||||
const existingError = document.querySelector('.tag-validation-error');
|
||||
if (existingError) {
|
||||
existingError.remove();
|
||||
}
|
||||
|
||||
// ایجاد پیام خطای زیبا
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'tag-validation-error mt-4 p-4 bg-red-50 border border-red-200 rounded-lg';
|
||||
errorDiv.innerHTML = `
|
||||
<div class="flex items-center">
|
||||
<svg class="w-5 h-5 text-red-500 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<span class="text-red-700 font-medium">${message}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// اضافه کردن پیام خطا به فرم
|
||||
const form = document.querySelector('form');
|
||||
if (form) {
|
||||
const submitButton = form.querySelector('button[type="submit"]');
|
||||
if (submitButton) {
|
||||
submitButton.parentNode.insertBefore(errorDiv, submitButton);
|
||||
}
|
||||
}
|
||||
|
||||
// حذف خودکار پیام خطا بعد از 5 ثانیه
|
||||
setTimeout(() => {
|
||||
if (errorDiv.parentNode) {
|
||||
errorDiv.remove();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ادیتور متن
|
||||
class TextEditor {
|
||||
if (typeof window.QuestionFormClasses.TextEditor === 'undefined') {
|
||||
window.QuestionFormClasses.TextEditor = class TextEditor {
|
||||
constructor() {
|
||||
this.textarea = document.querySelector('textarea[name="question[content]"]');
|
||||
}
|
||||
|
|
@ -648,21 +770,216 @@ class TextEditor {
|
|||
this.textarea.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// متغیرهای سراسری
|
||||
let tagManager;
|
||||
let textEditor;
|
||||
// مدیریت فایلها
|
||||
if (typeof window.QuestionFormClasses.FileManager === 'undefined') {
|
||||
window.QuestionFormClasses.FileManager = class FileManager {
|
||||
constructor() {
|
||||
this.maxFiles = 3;
|
||||
this.maxFileSize = 4 * 1024 * 1024; // 4MB
|
||||
this.allowedTypes = [
|
||||
'image/jpeg', 'image/png', 'image/gif',
|
||||
'application/pdf',
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.ms-powerpoint',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'text/plain'
|
||||
];
|
||||
this.selectedFiles = [];
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.fileInput = document.getElementById('file-input');
|
||||
this.selectedFilesDiv = document.getElementById('selected-files');
|
||||
this.fileListDiv = document.getElementById('file-list');
|
||||
|
||||
if (!this.fileInput) return;
|
||||
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.fileInput.addEventListener('change', (e) => this.handleFileSelect(e));
|
||||
|
||||
// Drag and drop
|
||||
const dropZone = this.fileInput.closest('.border-dashed');
|
||||
if (dropZone) {
|
||||
dropZone.addEventListener('dragover', (e) => this.handleDragOver(e));
|
||||
dropZone.addEventListener('drop', (e) => this.handleDrop(e));
|
||||
dropZone.addEventListener('dragleave', (e) => this.handleDragLeave(e));
|
||||
}
|
||||
}
|
||||
|
||||
handleFileSelect(event) {
|
||||
const files = Array.from(event.target.files);
|
||||
this.processFiles(files);
|
||||
}
|
||||
|
||||
handleDragOver(event) {
|
||||
event.preventDefault();
|
||||
event.currentTarget.classList.add('bg-blue-50', 'border-blue-400');
|
||||
}
|
||||
|
||||
handleDragLeave(event) {
|
||||
event.preventDefault();
|
||||
event.currentTarget.classList.remove('bg-blue-50', 'border-blue-400');
|
||||
}
|
||||
|
||||
handleDrop(event) {
|
||||
event.preventDefault();
|
||||
event.currentTarget.classList.remove('bg-blue-50', 'border-blue-400');
|
||||
|
||||
const files = Array.from(event.dataTransfer.files);
|
||||
this.processFiles(files);
|
||||
}
|
||||
|
||||
processFiles(files) {
|
||||
files.forEach(file => {
|
||||
if (this.validateFile(file)) {
|
||||
this.addFile(file);
|
||||
}
|
||||
});
|
||||
this.updateFileInput();
|
||||
}
|
||||
|
||||
validateFile(file) {
|
||||
// بررسی تعداد فایلها
|
||||
if (this.selectedFiles.length >= this.maxFiles) {
|
||||
if (window.notification) {
|
||||
window.notification.warning(`حداکثر ${this.maxFiles} فایل میتوانید انتخاب کنید`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// بررسی نوع فایل
|
||||
if (!this.allowedTypes.includes(file.type)) {
|
||||
if (window.notification) {
|
||||
window.notification.error('فرمت فایل مجاز نیست');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// بررسی حجم فایل
|
||||
if (file.size > this.maxFileSize) {
|
||||
if (window.notification) {
|
||||
window.notification.error('حجم فایل نمیتواند بیش از 4 مگابایت باشد');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
addFile(file) {
|
||||
const fileId = Date.now() + Math.random();
|
||||
this.selectedFiles.push({
|
||||
id: fileId,
|
||||
file: file,
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type
|
||||
});
|
||||
|
||||
this.renderFiles();
|
||||
}
|
||||
|
||||
removeFile(fileId) {
|
||||
this.selectedFiles = this.selectedFiles.filter(f => f.id !== fileId);
|
||||
this.renderFiles();
|
||||
this.updateFileInput();
|
||||
}
|
||||
|
||||
renderFiles() {
|
||||
if (this.selectedFiles.length === 0) {
|
||||
this.selectedFilesDiv.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedFilesDiv.classList.remove('hidden');
|
||||
this.fileListDiv.innerHTML = '';
|
||||
|
||||
this.selectedFiles.forEach(fileData => {
|
||||
const fileElement = document.createElement('div');
|
||||
fileElement.className = 'flex items-center justify-between p-3 bg-white rounded-lg border border-gray-200';
|
||||
fileElement.innerHTML = `
|
||||
<div class="flex items-center space-x-3 space-x-reverse">
|
||||
<div class="flex-shrink-0">
|
||||
${this.getFileIcon(fileData.type)}
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-gray-900 truncate">${fileData.name}</p>
|
||||
<p class="text-xs text-gray-500">${this.formatFileSize(fileData.size)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button"
|
||||
class="flex-shrink-0 text-red-600 hover:text-red-800 transition-colors duration-200"
|
||||
onclick="fileManager.removeFile(${fileData.id})">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
`;
|
||||
this.fileListDiv.appendChild(fileElement);
|
||||
});
|
||||
}
|
||||
|
||||
getFileIcon(type) {
|
||||
if (type.startsWith('image/')) {
|
||||
return '<svg class="w-6 h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21,15 16,10 5,21"></polyline></svg>';
|
||||
} else if (type === 'application/pdf') {
|
||||
return '<svg class="w-6 h-6 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path></svg>';
|
||||
} else if (type.includes('word') || type.includes('document')) {
|
||||
return '<svg class="w-6 h-6 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path></svg>';
|
||||
} else {
|
||||
return '<svg class="w-6 h-6 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path></svg>';
|
||||
}
|
||||
}
|
||||
|
||||
formatFileSize(bytes) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
updateFileInput() {
|
||||
// ایجاد DataTransfer object جدید
|
||||
const dt = new DataTransfer();
|
||||
this.selectedFiles.forEach(fileData => {
|
||||
dt.items.add(fileData.file);
|
||||
});
|
||||
this.fileInput.files = dt.files;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// متغیرهای سراسری - استفاده از window object
|
||||
if (!window.questionFormManager) {
|
||||
window.questionFormManager = {
|
||||
tagManager: null,
|
||||
textEditor: null,
|
||||
fileManager: null,
|
||||
initialized: false
|
||||
};
|
||||
}
|
||||
|
||||
// توابع سراسری برای HTML
|
||||
window.formatText = function(command) {
|
||||
if (textEditor) {
|
||||
textEditor.formatText(command);
|
||||
if (window.questionFormManager.textEditor) {
|
||||
window.questionFormManager.textEditor.formatText(command);
|
||||
}
|
||||
};
|
||||
|
||||
window.insertList = function() {
|
||||
if (textEditor) {
|
||||
textEditor.insertList();
|
||||
if (window.questionFormManager.textEditor) {
|
||||
window.questionFormManager.textEditor.insertList();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -673,23 +990,67 @@ function initializeQuestionForm() {
|
|||
return;
|
||||
}
|
||||
|
||||
tagManager = new TagManager();
|
||||
textEditor = new TextEditor();
|
||||
// جلوگیری از مقداردهی مجدد
|
||||
if (window.questionFormManager.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ایجاد instance های جدید
|
||||
if (typeof window.QuestionFormClasses.TagManager !== 'undefined') {
|
||||
window.questionFormManager.tagManager = new window.QuestionFormClasses.TagManager();
|
||||
}
|
||||
if (typeof window.QuestionFormClasses.TextEditor !== 'undefined') {
|
||||
window.questionFormManager.textEditor = new window.QuestionFormClasses.TextEditor();
|
||||
}
|
||||
if (typeof window.QuestionFormClasses.FileManager !== 'undefined') {
|
||||
window.questionFormManager.fileManager = new window.QuestionFormClasses.FileManager();
|
||||
}
|
||||
|
||||
// اعتبارسنجی فرم
|
||||
const form = document.querySelector('form');
|
||||
if (form) {
|
||||
form.addEventListener('submit', function(e) {
|
||||
if (!tagManager.validateTags()) {
|
||||
// حذف event listener قبلی اگر وجود دارد
|
||||
form.removeEventListener('submit', handleFormSubmit);
|
||||
form.addEventListener('submit', handleFormSubmit, false);
|
||||
|
||||
// اطمینان از اینکه form دارای data-turbo="false" است
|
||||
form.setAttribute('data-turbo', 'false');
|
||||
}
|
||||
|
||||
window.questionFormManager.initialized = true;
|
||||
}
|
||||
|
||||
function handleFormSubmit(e) {
|
||||
// اطمینان از اینکه تگها به فرم اضافه شدهاند
|
||||
if (window.questionFormManager.tagManager) {
|
||||
window.questionFormManager.tagManager.updateHiddenInput();
|
||||
|
||||
// اعتبارسنجی تگها
|
||||
if (!window.questionFormManager.tagManager.validateTags()) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// غیرفعال کردن Turbo برای این فرم
|
||||
const form = e.target;
|
||||
if (form) {
|
||||
form.setAttribute('data-turbo', 'false');
|
||||
}
|
||||
}
|
||||
|
||||
// اجرا در بارگذاری صفحه
|
||||
document.addEventListener('DOMContentLoaded', initializeQuestionForm);
|
||||
document.addEventListener('turbo:load', initializeQuestionForm);
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Reset initialization state
|
||||
window.questionFormManager.initialized = false;
|
||||
initializeQuestionForm();
|
||||
});
|
||||
|
||||
document.addEventListener('turbo:load', function() {
|
||||
// Reset initialization state for Turbo navigation
|
||||
window.questionFormManager.initialized = false;
|
||||
initializeQuestionForm();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@
|
|||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<span>{{ question.createdAt|date('Y/m/d H:i') }}</span>
|
||||
<span>{{ question.createdAt|date('U')|jdate('Y/m/d H:i') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1 space-x-reverse">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
|
|||
|
|
@ -52,6 +52,32 @@
|
|||
<div class="prose prose-lg max-w-none text-gray-800 leading-relaxed mb-6">
|
||||
{{ question.content|markdown|raw }}
|
||||
</div>
|
||||
{% if question.attachments is defined and question.attachments|length > 0 %}
|
||||
<div class="mt-4 border-t border-gray-200 pt-4">
|
||||
<h3 class="text-sm font-medium text-gray-700 mb-3">پیوستها</h3>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{% for attachment in question.attachments %}
|
||||
<a href="/uploads/attachments/{{ attachment.filename }}" target="_blank" class="block group">
|
||||
<div class="flex items-center space-x-3 space-x-reverse p-3 bg-gray-50 rounded-xl border border-gray-200 group-hover:bg-gray-100 transition-colors duration-200">
|
||||
{% if attachment.isImage() %}
|
||||
<img src="/uploads/attachments/{{ attachment.filename }}" alt="{{ attachment.originalFilename }}" class="w-12 h-12 rounded-lg object-cover">
|
||||
{% else %}
|
||||
<div class="w-12 h-12 flex items-center justify-center rounded-lg bg-white border border-gray-200">
|
||||
<svg class="w-6 h-6 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-medium text-gray-900 truncate">{{ attachment.originalFilename }}</p>
|
||||
<p class="text-xs text-gray-500">{{ attachment.getFormattedSize() }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="flex flex-wrap items-center justify-between gap-4 pt-6 border-t border-gray-200">
|
||||
<!-- تگها -->
|
||||
|
|
@ -76,7 +102,7 @@
|
|||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<span>{{ question.createdAt|date('Y/m/d H:i') }}</span>
|
||||
<span>{{ question.createdAt|date('U')|jdate('Y/m/d H:i') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1 space-x-reverse">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
@ -182,6 +208,32 @@
|
|||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if answer.attachments is defined and answer.attachments|length > 0 %}
|
||||
<div class="mt-4 border-t border-gray-200 pt-4">
|
||||
<h4 class="text-sm font-medium text-gray-700 mb-3">پیوستهای پاسخ</h4>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{% for attachment in answer.attachments %}
|
||||
<a href="/uploads/attachments/{{ attachment.filename }}" target="_blank" class="block group">
|
||||
<div class="flex items-center space-x-3 space-x-reverse p-3 bg-gray-50 rounded-xl border border-gray-200 group-hover:bg-gray-100 transition-colors duration-200">
|
||||
{% if attachment.isImage() %}
|
||||
<img src="/uploads/attachments/{{ attachment.filename }}" alt="{{ attachment.originalFilename }}" class="w-12 h-12 rounded-lg object-cover">
|
||||
{% else %}
|
||||
<div class="w-12 h-12 flex items-center justify-center rounded-lg bg-white border border-gray-200">
|
||||
<svg class="w-6 h-6 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-medium text-gray-900 truncate">{{ attachment.originalFilename }}</p>
|
||||
<p class="text-xs text-gray-500">{{ attachment.getFormattedSize() }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="flex items-center justify-between pt-4 border-t border-gray-200">
|
||||
<div class="flex items-center space-x-4 space-x-reverse text-sm text-gray-500">
|
||||
|
|
@ -195,7 +247,7 @@
|
|||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<span>{{ answer.createdAt|date('Y/m/d H:i') }}</span>
|
||||
<span>{{ answer.createdAt|date('U')|jdate('Y/m/d H:i') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,73 +3,98 @@
|
|||
{% block title %}سوالات تگ {{ tag.name }} - پرسش و پاسخ{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container my-4">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<main class="min-h-screen bg-gray-50">
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<!-- هدر -->
|
||||
<div class="bg-gradient-to-r from-blue-600 to-indigo-700 rounded-2xl p-8 mb-8 text-white">
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<h1 class="text-primary fw-bold mb-2">
|
||||
<i class="fas fa-tag me-2"></i>سوالات تگ "{{ tag.name }}"
|
||||
<h1 class="text-3xl font-bold mb-2 flex items-center">
|
||||
<svg class="w-8 h-8 ml-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
||||
</svg>
|
||||
سوالات تگ "{{ tag.name }}"
|
||||
</h1>
|
||||
{% if tag.description %}
|
||||
<p class="text-muted mb-0">{{ tag.description }}</p>
|
||||
<p class="text-blue-100 text-lg">{{ tag.description }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<a href="{{ path('qa_index') }}" class="btn btn-outline-primary">
|
||||
<i class="fas fa-arrow-right me-2"></i>بازگشت به همه سوالات
|
||||
<a href="{{ path('qa_index') }}"
|
||||
class="mt-4 md:mt-0 inline-flex items-center px-6 py-3 bg-white bg-opacity-20 text-white rounded-xl hover:bg-opacity-30 transition-all duration-200 font-medium backdrop-blur-sm">
|
||||
<svg class="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
|
||||
</svg>
|
||||
بازگشت به همه سوالات
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div class="lg:col-span-2">
|
||||
{% if questions is empty %}
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-5">
|
||||
<i class="fas fa-question-circle fa-3x text-muted mb-3"></i>
|
||||
<h4 class="text-muted">سوالی یافت نشد</h4>
|
||||
<p class="text-muted">هنوز سوالی با تگ "{{ tag.name }}" وجود ندارد.</p>
|
||||
<div class="bg-white rounded-2xl shadow-soft p-12 text-center">
|
||||
<div class="max-w-md mx-auto">
|
||||
<svg class="w-16 h-16 text-gray-400 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">سوالی یافت نشد</h3>
|
||||
<p class="text-gray-600 mb-6">هنوز سوالی با تگ "{{ tag.name }}" وجود ندارد.</p>
|
||||
{% if app.user and 'ROLE_CUSTOMER' in app.user.roles %}
|
||||
<a href="{{ path('qa_ask') }}" class="btn btn-primary">اولین سوال را بپرسید</a>
|
||||
<a href="{{ path('qa_ask') }}"
|
||||
class="inline-flex items-center px-6 py-3 bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition-all duration-200 font-medium shadow-lg hover:shadow-xl transform hover:-translate-y-0.5">
|
||||
<svg class="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
||||
</svg>
|
||||
اولین سوال را بپرسید
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="space-y-6">
|
||||
{% for question in questions %}
|
||||
<div class="card mb-3 question-card">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-2 col-md-1 text-center">
|
||||
<div class="d-flex flex-column align-items-center">
|
||||
<div class="vote-count {{ question.votes > 0 ? 'text-success' : (question.votes < 0 ? 'text-danger' : 'text-muted') }}">
|
||||
<div class="bg-white rounded-2xl shadow-soft overflow-hidden qa-card-hover">
|
||||
<div class="p-6">
|
||||
<div class="flex gap-6">
|
||||
<!-- آمار رای و پاسخ -->
|
||||
<div class="flex-shrink-0">
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<div class="text-center">
|
||||
<div class="text-2xl font-bold {{ question.votes > 0 ? 'text-green-600' : (question.votes < 0 ? 'text-red-600' : 'text-gray-500') }}">
|
||||
{{ question.votes }}
|
||||
</div>
|
||||
<small class="text-muted">رای</small>
|
||||
<div class="text-xs text-gray-500">رای</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-2 col-md-1 text-center">
|
||||
<div class="d-flex flex-column align-items-center">
|
||||
<div class="answer-count {{ question.answers|length > 0 ? 'text-success' : 'text-muted' }}">
|
||||
<div class="text-center">
|
||||
<div class="text-2xl font-bold {{ question.answers|length > 0 ? 'text-green-600' : 'text-gray-500' }}">
|
||||
{{ question.answers|length }}
|
||||
</div>
|
||||
<small class="text-muted">پاسخ</small>
|
||||
<div class="text-xs text-gray-500">پاسخ</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-8 col-md-10">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<h5 class="card-title mb-0">
|
||||
</div>
|
||||
|
||||
<!-- محتوای سوال -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-start justify-between mb-3">
|
||||
<h3 class="text-lg font-semibold text-gray-900 leading-tight">
|
||||
<a href="{{ path('qa_question_show', {'id': question.id}) }}"
|
||||
class="text-decoration-none text-dark">
|
||||
class="hover:text-blue-600 transition-colors duration-200">
|
||||
{{ question.title }}
|
||||
</a>
|
||||
</h5>
|
||||
</h3>
|
||||
{% if question.isSolved %}
|
||||
<span class="badge bg-success">
|
||||
<i class="fas fa-check me-1"></i>حل شده
|
||||
<span class="inline-flex items-center px-3 py-1 bg-green-100 text-green-800 text-sm font-medium rounded-full">
|
||||
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
حل شده
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<p class="card-text text-muted mb-2">
|
||||
<div class="prose prose-sm max-w-none text-gray-600 mb-4">
|
||||
{% set content = question.content|markdown|raw %}
|
||||
{% set plainText = content|striptags %}
|
||||
{% if plainText|length > 200 %}
|
||||
|
|
@ -77,21 +102,37 @@
|
|||
{% else %}
|
||||
{{ plainText }}
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="tags">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% for tagRelation in question.tagRelations %}
|
||||
<a href="{{ path('qa_tag_questions', {'name': tagRelation.tag.name}) }}"
|
||||
class="badge bg-light text-dark text-decoration-none me-1 {{ tagRelation.tag.name == tag.name ? 'bg-primary text-white' : '' }}">
|
||||
class="inline-flex items-center px-3 py-1 text-sm font-medium rounded-full transition-all duration-200 {{ tagRelation.tag.name == tag.name ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }}">
|
||||
{{ tagRelation.tag.name }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="text-muted small">
|
||||
<i class="fas fa-user me-1"></i>{{ question.author.name }}
|
||||
<i class="fas fa-clock ms-3 me-1"></i>{{ question.createdAt|date('Y/m/d H:i') }}
|
||||
<i class="fas fa-eye ms-3 me-1"></i>{{ question.views }}
|
||||
<div class="flex items-center space-x-4 space-x-reverse text-sm text-gray-500">
|
||||
<div class="flex items-center">
|
||||
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
||||
</svg>
|
||||
{{ question.author.name }}
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
{{ question.createdAt|date('U')|jdate('Y/m/d H:i') }}
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
|
||||
</svg>
|
||||
{{ question.views }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -99,86 +140,111 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- صفحهبندی -->
|
||||
{% if totalPages > 1 %}
|
||||
<nav aria-label="صفحهبندی سوالات">
|
||||
<ul class="pagination justify-content-center">
|
||||
<div class="mt-8">
|
||||
<nav class="flex justify-center" aria-label="صفحهبندی سوالات">
|
||||
<div class="flex items-center space-x-2 space-x-reverse">
|
||||
{% if currentPage > 1 %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ currentPage - 1 }}">قبلی</a>
|
||||
</li>
|
||||
<a href="?page={{ currentPage - 1 }}"
|
||||
class="flex items-center space-x-2 space-x-reverse px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 hover:text-gray-700 transition-all duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
||||
</svg>
|
||||
<span>قبلی</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<div class="flex items-center space-x-1 space-x-reverse">
|
||||
{% for page in 1..totalPages %}
|
||||
{% if page == currentPage %}
|
||||
<li class="page-item active">
|
||||
<span class="page-link">{{ page }}</span>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page }}">{{ page }}</a>
|
||||
</li>
|
||||
<span class="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-blue-600 rounded-lg">
|
||||
{{ page }}
|
||||
</span>
|
||||
{% elseif page <= currentPage + 2 and page >= currentPage - 2 %}
|
||||
<a href="?page={{ page }}"
|
||||
class="px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 hover:text-gray-700 transition-all duration-200">
|
||||
{{ page }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if currentPage < totalPages %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ currentPage + 1 }}">بعدی</a>
|
||||
</li>
|
||||
<a href="?page={{ currentPage + 1 }}"
|
||||
class="flex items-center space-x-2 space-x-reverse px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 hover:text-gray-700 transition-all duration-200">
|
||||
<span>بعدی</span>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- سایدبار -->
|
||||
<div class="col-lg-4">
|
||||
<div class="space-y-6">
|
||||
<!-- اطلاعات تگ -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-info-circle me-2"></i>اطلاعات تگ
|
||||
</h6>
|
||||
<div class="bg-white rounded-2xl shadow-soft overflow-hidden">
|
||||
<div class="bg-gradient-to-r from-gray-50 to-gray-100 px-6 py-4 border-b border-gray-200">
|
||||
<h3 class="text-lg font-semibold text-gray-900 flex items-center">
|
||||
<svg class="w-5 h-5 text-gray-600 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
اطلاعات تگ
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<span class="badge bg-primary fs-6 me-2">{{ tag.name }}</span>
|
||||
<div class="p-6">
|
||||
<div class="flex items-center mb-4">
|
||||
<span class="inline-flex items-center px-4 py-2 bg-blue-600 text-white text-lg font-medium rounded-full mr-3">
|
||||
{{ tag.name }}
|
||||
</span>
|
||||
{% if tag.color %}
|
||||
<div class="tag-color" style="background-color: {{ tag.color }}; width: 20px; height: 20px; border-radius: 50%; border: 2px solid #dee2e6;"></div>
|
||||
<div class="w-6 h-6 rounded-full border-2 border-gray-300" style="background-color: {{ tag.color }};"></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if tag.description %}
|
||||
<p class="text-muted mb-3">{{ tag.description }}</p>
|
||||
<p class="text-gray-600 mb-6">{{ tag.description }}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="row text-center">
|
||||
<div class="col-6">
|
||||
<div class="h4 text-primary">{{ questions|length }}</div>
|
||||
<small class="text-muted">سوال</small>
|
||||
<div class="grid grid-cols-2 gap-4 text-center">
|
||||
<div class="bg-blue-50 rounded-xl p-4">
|
||||
<div class="text-2xl font-bold text-blue-600">{{ questions|length }}</div>
|
||||
<div class="text-sm text-gray-600">سوال</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="h4 text-success">{{ tag.usageCount }}</div>
|
||||
<small class="text-muted">استفاده</small>
|
||||
<div class="bg-green-50 rounded-xl p-4">
|
||||
<div class="text-2xl font-bold text-green-600">{{ tag.usageCount }}</div>
|
||||
<div class="text-sm text-gray-600">استفاده</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- تگهای مرتبط -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-tags me-2"></i>تگهای مرتبط
|
||||
</h6>
|
||||
<div class="bg-white rounded-2xl shadow-soft overflow-hidden">
|
||||
<div class="bg-gradient-to-r from-gray-50 to-gray-100 px-6 py-4 border-b border-gray-200">
|
||||
<h3 class="text-lg font-semibold text-gray-900 flex items-center">
|
||||
<svg class="w-5 h-5 text-gray-600 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
||||
</svg>
|
||||
تگهای مرتبط
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted small mb-3">تگهای مشابه که ممکن است برای شما مفید باشند:</p>
|
||||
<!-- در اینجا میتوانید تگهای مرتبط را نمایش دهید -->
|
||||
<div class="p-6">
|
||||
<p class="text-gray-600 text-sm mb-4">تگهای مشابه که ممکن است برای شما مفید باشند:</p>
|
||||
<div class="text-center">
|
||||
<a href="{{ path('qa_tags') }}" class="btn btn-outline-primary btn-sm">
|
||||
<a href="{{ path('qa_tags') }}"
|
||||
class="inline-flex items-center px-4 py-2 bg-blue-100 text-blue-700 rounded-lg hover:bg-blue-200 transition-all duration-200 font-medium">
|
||||
<svg class="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
||||
</svg>
|
||||
مشاهده همه تگها
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -188,50 +254,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.question-card {
|
||||
transition: all 0.3s ease;
|
||||
border-left: 4px solid transparent;
|
||||
}
|
||||
|
||||
.question-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
border-left-color: #0d6efd;
|
||||
}
|
||||
|
||||
.vote-count, .answer-count {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tags .badge {
|
||||
font-size: 0.8rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tags .badge:hover {
|
||||
background-color: #0d6efd !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.card-title a:hover {
|
||||
color: #0d6efd !important;
|
||||
}
|
||||
|
||||
.tag-color {
|
||||
border: 2px solid #dee2e6;
|
||||
}
|
||||
|
||||
.question-preview {
|
||||
line-height: 1.6;
|
||||
max-height: 4.8em; /* حدود 3 خط */
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -3,85 +3,84 @@
|
|||
{% block title %}تگها - پرسش و پاسخ{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container my-4">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="text-primary fw-bold">
|
||||
<i class="fas fa-tags me-2"></i>تگها
|
||||
<main class="min-h-screen bg-gray-50">
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<!-- هدر -->
|
||||
<div class="bg-gradient-to-r from-purple-600 to-indigo-700 rounded-2xl p-8 mb-8 text-white">
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold mb-2 flex items-center">
|
||||
<svg class="w-8 h-8 ml-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
||||
</svg>
|
||||
تگها
|
||||
</h1>
|
||||
<a href="{{ path('qa_index') }}" class="btn btn-outline-primary">
|
||||
<i class="fas fa-arrow-right me-2"></i>بازگشت به سوالات
|
||||
<p class="text-purple-100 text-lg">تمام تگهای موجود در سیستم پرسش و پاسخ</p>
|
||||
</div>
|
||||
<a href="{{ path('qa_index') }}"
|
||||
class="mt-4 md:mt-0 inline-flex items-center px-6 py-3 bg-white bg-opacity-20 text-white rounded-xl hover:bg-opacity-30 transition-all duration-200 font-medium backdrop-blur-sm">
|
||||
<svg class="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
|
||||
</svg>
|
||||
بازگشت به سوالات
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if tags is empty %}
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-5">
|
||||
<i class="fas fa-tags fa-3x text-muted mb-3"></i>
|
||||
<h4 class="text-muted">تگی یافت نشد</h4>
|
||||
<p class="text-muted">هنوز تگی در سیستم ثبت نشده است.</p>
|
||||
<div class="bg-white rounded-2xl shadow-soft p-12 text-center">
|
||||
<div class="max-w-md mx-auto">
|
||||
<svg class="w-16 h-16 text-gray-400 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
||||
</svg>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">تگی یافت نشد</h3>
|
||||
<p class="text-gray-600">هنوز تگی در سیستم ثبت نشده است.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{% for tag in tags %}
|
||||
<div class="col-lg-4 col-md-6 mb-4">
|
||||
<div class="card tag-card h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
||||
<h5 class="card-title mb-0">
|
||||
<div class="bg-white rounded-2xl shadow-soft overflow-hidden qa-card-hover">
|
||||
<div class="p-6">
|
||||
<div class="flex items-start justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
<a href="{{ path('qa_tag_questions', {'name': tag.name}) }}"
|
||||
class="text-decoration-none text-dark">
|
||||
class="hover:text-blue-600 transition-colors duration-200">
|
||||
{{ tag.name }}
|
||||
</a>
|
||||
</h5>
|
||||
<span class="badge bg-primary">{{ tag.usageCount }}</span>
|
||||
</h3>
|
||||
<span class="inline-flex items-center px-3 py-1 bg-blue-100 text-blue-800 text-sm font-medium rounded-full">
|
||||
{{ tag.usageCount }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{% if tag.description %}
|
||||
<p class="card-text text-muted mb-3">
|
||||
<p class="text-gray-600 mb-4 text-sm leading-relaxed">
|
||||
{{ tag.description }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="flex items-center justify-between">
|
||||
<a href="{{ path('qa_tag_questions', {'name': tag.name}) }}"
|
||||
class="btn btn-outline-primary btn-sm">
|
||||
<i class="fas fa-eye me-1"></i>مشاهده سوالات
|
||||
class="inline-flex items-center px-4 py-2 bg-blue-100 text-blue-700 rounded-lg hover:bg-blue-200 transition-all duration-200 font-medium text-sm">
|
||||
<svg class="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
|
||||
</svg>
|
||||
مشاهده سوالات
|
||||
</a>
|
||||
{% if tag.color %}
|
||||
<div class="tag-color" style="background-color: {{ tag.color }}; width: 20px; height: 20px; border-radius: 50%;"></div>
|
||||
<div class="w-6 h-6 rounded-full border-2 border-gray-300" style="background-color: {{ tag.color }};"></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.tag-card {
|
||||
transition: all 0.3s ease;
|
||||
border-left: 4px solid transparent;
|
||||
}
|
||||
|
||||
.tag-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
border-left-color: #0d6efd;
|
||||
}
|
||||
|
||||
.tag-card .card-title a:hover {
|
||||
color: #0d6efd !important;
|
||||
}
|
||||
|
||||
.tag-color {
|
||||
border: 2px solid #dee2e6;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
|
|
|||
Loading…
Reference in a new issue