Bladeren bron

addDatabase

maximgf 2 weken geleden
bovenliggende
commit
b64dc91759

+ 77 - 0
app/Http/Controllers/Api/CommentController.php

@@ -0,0 +1,77 @@
+<?php
+
+namespace App\Http\Controllers\Api;
+
+use App\Http\Controllers\Controller;
+use App\Http\Resources\CommentResource;
+use App\Models\Comment;
+use App\Models\Submission;
+use Illuminate\Http\Request;
+use Illuminate\Http\JsonResponse;
+
+class CommentController extends Controller
+{
+    // Список комментариев для заявки
+    public function index(int $submissionId): JsonResponse
+    {
+        $submission = Submission::findOrFail($submissionId);
+        $comments = $submission->comments()->with('attachments')->latest()->get();
+        
+        return response()->json([
+            'success' => true,
+            'data' => CommentResource::collection($comments),
+        ]);
+    }
+
+    // Создание комментария
+    public function store(Request $request, int $submissionId): JsonResponse
+    {
+        $submission = Submission::findOrFail($submissionId);
+        
+        $validated = $request->validate([
+            'author' => 'required|string|max:100',
+            'content' => 'required|string',
+        ]);
+        
+        $comment = $submission->comments()->create($validated);
+        $comment->load('attachments');
+        
+        return response()->json([
+            'success' => true,
+            'message' => 'Комментарий успешно добавлен',
+            'data' => new CommentResource($comment),
+        ], 201);
+    }
+
+    // Обновление комментария
+    public function update(Request $request, int $submissionId, int $id): JsonResponse
+    {
+        $comment = Comment::where('submission_id', $submissionId)->findOrFail($id);
+        
+        $validated = $request->validate([
+            'author' => 'sometimes|required|string|max:100',
+            'content' => 'sometimes|required|string',
+        ]);
+        
+        $comment->update($validated);
+        $comment->load('attachments');
+        
+        return response()->json([
+            'success' => true,
+            'message' => 'Комментарий успешно обновлен',
+            'data' => new CommentResource($comment),
+        ]);
+    }
+
+    // Удаление комментария
+    public function destroy(int $submissionId, int $id): JsonResponse
+    {
+        $comment = Comment::where('submission_id', $submissionId)->findOrFail($id);
+        $comment->delete();
+        
+        return response()->json([
+            'success' => true,
+            'message' => 'Комментарий успешно удален',
+        ]);
+    }
+}

+ 167 - 0
app/Http/Controllers/Api/SubmissionController.php

@@ -0,0 +1,167 @@
+<?php
+
+namespace App\Http\Controllers\Api;
+
+use App\Http\Controllers\Controller;
+use App\Http\Resources\SubmissionResource;
+use App\Models\Submission;
+use Illuminate\Http\Request;
+use Illuminate\Http\JsonResponse;
+
+class SubmissionController extends Controller
+{
+    // Список всех заявок с фильтрацией
+    public function index(Request $request): JsonResponse
+    {
+        $query = Submission::query();
+        
+        // Query Scopes
+        if ($request->has('status')) {
+            if ($request->status === 'active') {
+                $query->active();
+            } elseif ($request->status === 'archived') {
+                $query->archived();
+            }
+        }
+        
+        if ($request->has('search')) {
+            $query->search($request->search);
+        }
+        
+        if ($request->has('recent')) {
+            $query->recent($request->recent);
+        }
+        
+        // Eager loading связей
+        $query->with(['comments', 'tags', 'attachments'])
+              ->withCount(['comments', 'tags']);
+        
+        // Включить удаленные записи
+        if ($request->boolean('with_trashed')) {
+            $query->withTrashed();
+        }
+        
+        $submissions = $query->latest()->paginate($request->get('per_page', 15));
+        
+        return response()->json([
+            'success' => true,
+            'data' => SubmissionResource::collection($submissions),
+            'meta' => [
+                'total' => $submissions->total(),
+                'per_page' => $submissions->perPage(),
+                'current_page' => $submissions->currentPage(),
+                'last_page' => $submissions->lastPage(),
+            ]
+        ]);
+    }
+
+    // Создание новой заявки
+    public function store(Request $request): JsonResponse
+    {
+        $validated = $request->validate([
+            'name' => 'required|string|max:100',
+            'email' => 'required|email|max:255',
+            'message' => 'nullable|string|max:1000',
+            'status' => 'nullable|in:active,archived,pending',
+            'tags' => 'nullable|array',
+            'tags.*' => 'exists:tags,id',
+        ]);
+        
+        $validated['ip_address'] = $request->ip();
+        
+        $submission = Submission::create($validated);
+        
+        // Прикрепление тегов (многие-ко-многим)
+        if ($request->has('tags')) {
+            $submission->tags()->attach($request->tags);
+        }
+        
+        $submission->load(['comments', 'tags', 'attachments']);
+        
+        return response()->json([
+            'success' => true,
+            'message' => 'Заявка успешно создана',
+            'data' => new SubmissionResource($submission),
+        ], 201);
+    }
+
+    // Просмотр одной заявки
+    public function show(int $id): JsonResponse
+    {
+        $submission = Submission::with(['comments.attachments', 'tags', 'attachments'])
+            ->withCount('comments')
+            ->findOrFail($id);
+        
+        return response()->json([
+            'success' => true,
+            'data' => new SubmissionResource($submission),
+        ]);
+    }
+
+    // Обновление заявки
+    public function update(Request $request, int $id): JsonResponse
+    {
+        $submission = Submission::findOrFail($id);
+        
+        $validated = $request->validate([
+            'name' => 'sometimes|required|string|max:100',
+            'email' => 'sometimes|required|email|max:255',
+            'message' => 'nullable|string|max:1000',
+            'status' => 'nullable|in:active,archived,pending',
+            'tags' => 'nullable|array',
+            'tags.*' => 'exists:tags,id',
+        ]);
+        
+        $submission->update($validated);
+        
+        // Синхронизация тегов
+        if ($request->has('tags')) {
+            $submission->tags()->sync($request->tags);
+        }
+        
+        $submission->load(['comments', 'tags', 'attachments']);
+        
+        return response()->json([
+            'success' => true,
+            'message' => 'Заявка успешно обновлена',
+            'data' => new SubmissionResource($submission),
+        ]);
+    }
+
+    // Мягкое удаление заявки
+    public function destroy(int $id): JsonResponse
+    {
+        $submission = Submission::findOrFail($id);
+        $submission->delete();
+        
+        return response()->json([
+            'success' => true,
+            'message' => 'Заявка успешно удалена (мягкое удаление)',
+        ]);
+    }
+
+    // Восстановление удаленной заявки
+    public function restore(int $id): JsonResponse
+    {
+        $submission = Submission::withTrashed()->findOrFail($id);
+        $submission->restore();
+        
+        return response()->json([
+            'success' => true,
+            'message' => 'Заявка успешно восстановлена',
+            'data' => new SubmissionResource($submission),
+        ]);
+    }
+
+    // Окончательное удаление
+    public function forceDelete(int $id): JsonResponse
+    {
+        $submission = Submission::withTrashed()->findOrFail($id);
+        $submission->forceDelete();
+        
+        return response()->json([
+            'success' => true,
+            'message' => 'Заявка окончательно удалена',
+        ]);
+    }
+}

+ 85 - 0
app/Http/Controllers/Api/TagController.php

@@ -0,0 +1,85 @@
+<?php
+
+namespace App\Http\Controllers\Api;
+
+use App\Http\Controllers\Controller;
+use App\Http\Resources\TagResource;
+use App\Models\Tag;
+use Illuminate\Http\Request;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Support\Str;
+
+class TagController extends Controller
+{
+    // Список всех тегов
+    public function index(): JsonResponse
+    {
+        $tags = Tag::withCount('submissions')->get();
+        
+        return response()->json([
+            'success' => true,
+            'data' => TagResource::collection($tags),
+        ]);
+    }
+
+    // Создание тега
+    public function store(Request $request): JsonResponse
+    {
+        $validated = $request->validate([
+            'name' => 'required|string|max:100|unique:tags,name',
+        ]);
+        
+        $validated['slug'] = Str::slug($validated['name']);
+        
+        $tag = Tag::create($validated);
+        
+        return response()->json([
+            'success' => true,
+            'message' => 'Тег успешно создан',
+            'data' => new TagResource($tag),
+        ], 201);
+    }
+
+    // Просмотр одного тега
+    public function show(int $id): JsonResponse
+    {
+        $tag = Tag::withCount('submissions')->findOrFail($id);
+        
+        return response()->json([
+            'success' => true,
+            'data' => new TagResource($tag),
+        ]);
+    }
+
+    // Обновление тега
+    public function update(Request $request, int $id): JsonResponse
+    {
+        $tag = Tag::findOrFail($id);
+        
+        $validated = $request->validate([
+            'name' => 'required|string|max:100|unique:tags,name,' . $id,
+        ]);
+        
+        $validated['slug'] = Str::slug($validated['name']);
+        
+        $tag->update($validated);
+        
+        return response()->json([
+            'success' => true,
+            'message' => 'Тег успешно обновлен',
+            'data' => new TagResource($tag),
+        ]);
+    }
+
+    // Удаление тега
+    public function destroy(int $id): JsonResponse
+    {
+        $tag = Tag::findOrFail($id);
+        $tag->delete();
+        
+        return response()->json([
+            'success' => true,
+            'message' => 'Тег успешно удален',
+        ]);
+    }
+}

+ 55 - 19
app/Http/Controllers/DataController.php

@@ -1,31 +1,67 @@
 <?php
+
 namespace App\Http\Controllers;
 
-use Illuminate\Support\Facades\Storage;
+use App\Models\Submission;
 
 class DataController extends Controller
 {
     public function showData()
     {
-        $allData = [];
-        
-        $files = Storage::files('submissions'); 
-
-        foreach ($files as $file) {
-            try {
-                $contents = Storage::get($file);
-                $data = json_decode($contents, true);
-                
-                if (is_array($data)) {
-                    $allData[] = $data;
-                }
-            } catch (\Exception $e) {
-            }
-        }
+        // Получаем все заявки из БД с комментариями и тегами
+        $submissions = Submission::with(['comments', 'tags'])
+            ->withCount('comments')
+            ->active() // Query Scope
+            ->latest()
+            ->get();
 
         return view('data_table', [
-            'submissions' => $allData,
-            'count' => count($allData)
+            'submissions' => $submissions,
+            'count' => $submissions->count()
+        ]);
+    }
+
+    public function show($id)
+    {
+        // Просмотр одной заявки со всеми связями
+        $submission = Submission::with(['comments.attachments', 'tags', 'attachments'])
+            ->findOrFail($id);
+
+        return view('submission_detail', [
+            'submission' => $submission
+        ]);
+    }
+
+    public function edit($id)
+    {
+        $submission = Submission::findOrFail($id);
+        
+        return view('submission_edit', [
+            'submission' => $submission
         ]);
     }
-}
+
+    public function update(Request $request, $id)
+    {
+        $submission = Submission::findOrFail($id);
+        
+        $validated = $request->validate([
+            'name' => 'required|string|max:100',
+            'email' => 'required|email|max:255',
+            'message' => 'nullable|string|max:1000',
+            'status' => 'required|in:active,archived,pending',
+        ]);
+
+        $submission->update($validated);
+
+        return redirect()->route('data.show')->with('success', 'Заявка успешно обновлена!');
+    }
+
+    public function destroy($id)
+    {
+        $submission = Submission::findOrFail($id);
+        $submission->delete(); // Мягкое удаление
+
+        return redirect()->route('data.show')->with('success', 'Заявка успешно удалена!');
+    }
+}

+ 10 - 11
app/Http/Controllers/FormController.php

@@ -1,8 +1,9 @@
 <?php
+
 namespace App\Http\Controllers;
 
+use App\Models\Submission;
 use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Storage;
 
 class FormController extends Controller
 {
@@ -23,17 +24,15 @@ class FormController extends Controller
             'email.email' => 'Введите корректный адрес электронной почты.',
         ]);
 
-        $timestamp = now()->format('Ymd_His');
-        $uniqueId = uniqid();
-        $fileName = "submission_{$timestamp}_{$uniqueId}.json";
-        
-        $dataToSave = array_merge($validated, [
-            'submitted_at' => now()->toDateTimeString(),
+        // Сохранение в БД вместо JSON файла
+        Submission::create([
+            'name' => $validated['name'],
+            'email' => $validated['email'],
+            'message' => $validated['message'] ?? null,
             'ip_address' => $request->ip(),
+            'status' => 'active',
         ]);
 
-        Storage::put('submissions/' . $fileName, json_encode($dataToSave, JSON_PRETTY_PRINT));
-
-        return redirect()->route('form.show')->with('success', 'Спасибо! Ваши данные успешно сохранены в файл.');
+        return redirect()->route('form.show')->with('success', 'Спасибо! Ваши данные успешно сохранены в базу данных.');
     }
-}
+}

+ 33 - 0
app/Http/Resources/AttachmentResource.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class AttachmentResource extends JsonResource
+{
+    public function toArray(Request $request): array
+    {
+        return [
+            'id' => $this->id,
+            'filename' => $this->filename,
+            'filepath' => $this->filepath,
+            'mime_type' => $this->mime_type,
+            'size' => $this->size,
+            'size_human' => $this->formatBytes($this->size),
+            'created_at' => $this->created_at->toISOString(),
+        ];
+    }
+
+    private function formatBytes($bytes, $precision = 2)
+    {
+        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
+        
+        for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
+            $bytes /= 1024;
+        }
+        
+        return round($bytes, $precision) . ' ' . $units[$i];
+    }
+}

+ 24 - 0
app/Http/Resources/CommentResource.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class CommentResource extends JsonResource
+{
+    public function toArray(Request $request): array
+    {
+        return [
+            'id' => $this->id,
+            'submission_id' => $this->submission_id,
+            'author' => $this->author,
+            'content' => $this->content,
+            'created_at' => $this->created_at->toISOString(),
+            'updated_at' => $this->updated_at->toISOString(),
+            
+            'submission' => new SubmissionResource($this->whenLoaded('submission')),
+            'attachments' => AttachmentResource::collection($this->whenLoaded('attachments')),
+        ];
+    }
+}

+ 33 - 0
app/Http/Resources/SubmissionResource.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class SubmissionResource extends JsonResource
+{
+    public function toArray(Request $request): array
+    {
+        return [
+            'id' => $this->id,
+            'name' => $this->name,
+            'email' => $this->email,
+            'message' => $this->message,
+            'ip_address' => $this->ip_address,
+            'status' => $this->status,
+            'created_at' => $this->created_at->toISOString(),
+            'updated_at' => $this->updated_at->toISOString(),
+            'deleted_at' => $this->deleted_at?->toISOString(),
+            
+            // Связи (загружаются только если были eager loaded)
+            'comments' => CommentResource::collection($this->whenLoaded('comments')),
+            'tags' => TagResource::collection($this->whenLoaded('tags')),
+            'attachments' => AttachmentResource::collection($this->whenLoaded('attachments')),
+            
+            // Счетчики
+            'comments_count' => $this->whenCounted('comments'),
+            'tags_count' => $this->whenCounted('tags'),
+        ];
+    }
+}

+ 22 - 0
app/Http/Resources/TagResource.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class TagResource extends JsonResource
+{
+    public function toArray(Request $request): array
+    {
+        return [
+            'id' => $this->id,
+            'name' => $this->name,
+            'slug' => $this->slug,
+            'created_at' => $this->created_at->toISOString(),
+            'updated_at' => $this->updated_at->toISOString(),
+            
+            'submissions_count' => $this->whenCounted('submissions'),
+        ];
+    }
+}

+ 32 - 0
app/Models/Attachment.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class Attachment extends Model
+{
+    use HasFactory;
+
+    protected $fillable = [
+        'attachable_type',
+        'attachable_id',
+        'filename',
+        'filepath',
+        'mime_type',
+        'size',
+    ];
+
+    protected $casts = [
+        'size' => 'integer',
+        'created_at' => 'datetime',
+        'updated_at' => 'datetime',
+    ];
+
+    // Полиморфная связь: вложение может принадлежать разным моделям
+    public function attachable()
+    {
+        return $this->morphTo();
+    }
+}

+ 36 - 0
app/Models/Comment.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
+
+class Comment extends Model
+{
+    use HasFactory, SoftDeletes;
+
+    protected $fillable = [
+        'submission_id',
+        'author',
+        'content',
+    ];
+
+    protected $casts = [
+        'created_at' => 'datetime',
+        'updated_at' => 'datetime',
+        'deleted_at' => 'datetime',
+    ];
+
+    // Связь многие-к-одному: комментарий принадлежит заявке
+    public function submission()
+    {
+        return $this->belongsTo(Submission::class);
+    }
+
+    // Полиморфная связь: комментарий может иметь вложения
+    public function attachments()
+    {
+        return $this->morphMany(Attachment::class, 'attachable');
+    }
+}

+ 72 - 0
app/Models/Submission.php

@@ -0,0 +1,72 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Database\Eloquent\Builder;
+
+class Submission extends Model
+{
+    use HasFactory, SoftDeletes;
+
+    protected $fillable = [
+        'name',
+        'email',
+        'message',
+        'ip_address',
+        'status',
+    ];
+
+    protected $casts = [
+        'created_at' => 'datetime',
+        'updated_at' => 'datetime',
+        'deleted_at' => 'datetime',
+    ];
+
+    // Связь один-ко-многим: у заявки могут быть комментарии
+    public function comments()
+    {
+        return $this->hasMany(Comment::class);
+    }
+
+    // Связь многие-ко-многим: заявка может иметь несколько тегов
+    public function tags()
+    {
+        return $this->belongsToMany(Tag::class, 'submission_tag');
+    }
+
+    // Полиморфная связь: заявка может иметь вложения
+    public function attachments()
+    {
+        return $this->morphMany(Attachment::class, 'attachable');
+    }
+
+    // Query Scope: только активные заявки
+    public function scopeActive(Builder $query)
+    {
+        return $query->where('status', 'active');
+    }
+
+    // Query Scope: только архивные заявки
+    public function scopeArchived(Builder $query)
+    {
+        return $query->where('status', 'archived');
+    }
+
+    // Query Scope: поиск по имени или email
+    public function scopeSearch(Builder $query, $search)
+    {
+        return $query->where(function ($q) use ($search) {
+            $q->where('name', 'like', "%{$search}%")
+              ->orWhere('email', 'like', "%{$search}%");
+        });
+    }
+
+    // Query Scope: недавние заявки (за последние N дней)
+    public function scopeRecent(Builder $query, $days = 7)
+    {
+        return $query->where('created_at', '>=', now()->subDays($days));
+    }
+}

+ 27 - 0
app/Models/Tag.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class Tag extends Model
+{
+    use HasFactory;
+
+    protected $fillable = [
+        'name',
+        'slug',
+    ];
+
+    protected $casts = [
+        'created_at' => 'datetime',
+        'updated_at' => 'datetime',
+    ];
+
+    // Связь многие-ко-многим: тег может быть у многих заявок
+    public function submissions()
+    {
+        return $this->belongsToMany(Submission::class, 'submission_tag');
+    }
+}

+ 31 - 0
database/migrations/2025_12_04_000003_create_submissions_table.php

@@ -0,0 +1,31 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::create('submissions', function (Blueprint $table) {
+            $table->id();
+            $table->string('name', 100);
+            $table->string('email', 255);
+            $table->text('message')->nullable();
+            $table->string('ip_address', 45)->nullable();
+            $table->enum('status', ['active', 'archived', 'pending'])->default('active');
+            $table->timestamps();
+            $table->softDeletes();
+            
+            $table->index('email');
+            $table->index('status');
+            $table->index('created_at');
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::dropIfExists('submissions');
+    }
+};

+ 27 - 0
database/migrations/2025_12_04_000004_create_comments_table.php

@@ -0,0 +1,27 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::create('comments', function (Blueprint $table) {
+            $table->id();
+            $table->foreignId('submission_id')->constrained()->onDelete('cascade');
+            $table->string('author', 100);
+            $table->text('content');
+            $table->timestamps();
+            $table->softDeletes();
+            
+            $table->index('submission_id');
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::dropIfExists('comments');
+    }
+};

+ 25 - 0
database/migrations/2025_12_04_000005_create_tags_table.php

@@ -0,0 +1,25 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::create('tags', function (Blueprint $table) {
+            $table->id();
+            $table->string('name', 100);
+            $table->string('slug', 100)->unique();
+            $table->timestamps();
+            
+            $table->index('slug');
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::dropIfExists('tags');
+    }
+};

+ 25 - 0
database/migrations/2025_12_04_000006_create_submission_tag_table.php

@@ -0,0 +1,25 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::create('submission_tag', function (Blueprint $table) {
+            $table->id();
+            $table->foreignId('submission_id')->constrained()->onDelete('cascade');
+            $table->foreignId('tag_id')->constrained()->onDelete('cascade');
+            $table->timestamps();
+            
+            $table->unique(['submission_id', 'tag_id']);
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::dropIfExists('submission_tag');
+    }
+};

+ 26 - 0
database/migrations/2025_12_04_000007_create_attachments_table.php

@@ -0,0 +1,26 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::create('attachments', function (Blueprint $table) {
+            $table->id();
+            $table->morphs('attachable');
+            $table->string('filename');
+            $table->string('filepath');
+            $table->string('mime_type', 100);
+            $table->unsignedBigInteger('size');
+            $table->timestamps();
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::dropIfExists('attachments');
+    }
+};

+ 68 - 13
database/seeders/DatabaseSeeder.php

@@ -2,24 +2,79 @@
 
 namespace Database\Seeders;
 
-use App\Models\User;
-use Illuminate\Database\Console\Seeds\WithoutModelEvents;
+use App\Models\Submission;
+use App\Models\Comment;
+use App\Models\Tag;
+use App\Models\Attachment;
 use Illuminate\Database\Seeder;
 
 class DatabaseSeeder extends Seeder
 {
-    use WithoutModelEvents;
-
-    /**
-     * Seed the application's database.
-     */
     public function run(): void
     {
-        // User::factory(10)->create();
-
-        User::factory()->create([
-            'name' => 'Test User',
-            'email' => 'test@example.com',
+        // Создаем теги
+        $tags = collect([
+            Tag::create(['name' => 'Важное', 'slug' => 'important']),
+            Tag::create(['name' => 'Срочно', 'slug' => 'urgent']),
+            Tag::create(['name' => 'Вопрос', 'slug' => 'question']),
+            Tag::create(['name' => 'Отзыв', 'slug' => 'feedback']),
+            Tag::create(['name' => 'Проблема', 'slug' => 'issue']),
         ]);
+
+        // Создаем заявки
+        for ($i = 1; $i <= 10; $i++) {
+            $submission = Submission::create([
+                'name' => 'Пользователь ' . $i,
+                'email' => 'user' . $i . '@example.com',
+                'message' => 'Это тестовое сообщение #' . $i . '. Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
+                'ip_address' => '192.168.1.' . $i,
+                'status' => ['active', 'archived', 'pending'][array_rand(['active', 'archived', 'pending'])],
+            ]);
+
+            // Прикрепляем случайные теги (многие-ко-многим)
+            $submission->tags()->attach(
+                $tags->random(rand(1, 3))->pluck('id')->toArray()
+            );
+
+            // Добавляем комментарии (один-ко-многим)
+            for ($j = 1; $j <= rand(1, 3); $j++) {
+                $comment = Comment::create([
+                    'submission_id' => $submission->id,
+                    'author' => 'Комментатор ' . $j,
+                    'content' => 'Это комментарий #' . $j . ' к заявке #' . $i,
+                ]);
+
+                // Добавляем вложение к комментарию (полиморфная связь)
+                if (rand(0, 1)) {
+                    Attachment::create([
+                        'attachable_type' => Comment::class,
+                        'attachable_id' => $comment->id,
+                        'filename' => 'comment_attachment_' . $j . '.pdf',
+                        'filepath' => '/storage/attachments/comment_' . $comment->id . '.pdf',
+                        'mime_type' => 'application/pdf',
+                        'size' => rand(1000, 50000),
+                    ]);
+                }
+            }
+
+            // Добавляем вложения к заявке (полиморфная связь)
+            if (rand(0, 1)) {
+                Attachment::create([
+                    'attachable_type' => Submission::class,
+                    'attachable_id' => $submission->id,
+                    'filename' => 'submission_file_' . $i . '.jpg',
+                    'filepath' => '/storage/attachments/submission_' . $submission->id . '.jpg',
+                    'mime_type' => 'image/jpeg',
+                    'size' => rand(50000, 200000),
+                ]);
+            }
+        }
+
+        $this->command->info('База данных успешно заполнена тестовыми данными!');
+        $this->command->info('Создано:');
+        $this->command->info('- Заявок: ' . Submission::count());
+        $this->command->info('- Комментариев: ' . Comment::count());
+        $this->command->info('- Тегов: ' . Tag::count());
+        $this->command->info('- Вложений: ' . Attachment::count());
     }
-}
+}

+ 49 - 9
resources/views/data_table.blade.php

@@ -1,4 +1,4 @@
-@extends('layout.app') {{-- Требование 5: Расширение лейаута --}}
+@extends('layout.app')
 
 @section('title', 'Просмотр сохраненных данных')
 
@@ -6,27 +6,67 @@
 
     <h1>Сохраненные данные ({{ $count }} записей)</h1>
 
-    @if (empty($submissions))
+    @if (session('success'))
+        <div class="alert-success">
+            {{ session('success') }}
+        </div>
+    @endif
+
+    @if ($submissions->isEmpty())
         <p>На данный момент нет сохраненных данных.</p>
     @else
         <table>
             <thead>
                 <tr>
+                    <th>ID</th>
                     <th>Имя</th>
                     <th>Email</th>
                     <th>Сообщение</th>
-                    <th>Дата/Время сохранения</th>
+                    <th>Статус</th>
+                    <th>Комментарии</th>
+                    <th>Теги</th>
+                    <th>Дата создания</th>
                     <th>IP-адрес</th>
+                    <th>Действия</th>
                 </tr>
             </thead>
             <tbody>
-                @foreach ($submissions as $data)
+                @foreach ($submissions as $submission)
                     <tr>
-                        <td>{{ $data['name'] ?? 'N/A' }}</td>
-                        <td>{{ $data['email'] ?? 'N/A' }}</td>
-                        <td>{{ $data['message'] ?? '-' }}</td>
-                        <td>{{ $data['submitted_at'] ?? 'Неизвестно' }}</td>
-                        <td>{{ $data['ip_address'] ?? 'Неизвестно' }}</td>
+                        <td>{{ $submission->id }}</td>
+                        <td>{{ $submission->name }}</td>
+                        <td>{{ $submission->email }}</td>
+                        <td>{{ Str::limit($submission->message ?? '-', 50) }}</td>
+                        <td>
+                            <span style="padding: 2px 8px; border-radius: 3px; background-color: 
+                                @if($submission->status === 'active') #d4edda 
+                                @elseif($submission->status === 'archived') #f8d7da 
+                                @else #fff3cd @endif">
+                                {{ ucfirst($submission->status) }}
+                            </span>
+                        </td>
+                        <td>{{ $submission->comments_count }}</td>
+                        <td>
+                            @foreach($submission->tags as $tag)
+                                <span style="display: inline-block; padding: 2px 6px; margin: 2px; background-color: #e3f2fd; border-radius: 3px; font-size: 0.85em;">
+                                    {{ $tag->name }}
+                                </span>
+                            @endforeach
+                        </td>
+                        <td>{{ $submission->created_at->format('d.m.Y H:i') }}</td>
+                        <td>{{ $submission->ip_address ?? 'Неизвестно' }}</td>
+                        <td>
+                            <a href="{{ route('submissions.show', $submission->id) }}" style="color: #3490dc; text-decoration: none; margin-right: 10px;">Просмотр</a>
+                            <a href="{{ route('submissions.edit', $submission->id) }}" style="color: #38c172; text-decoration: none; margin-right: 10px;">Редактировать</a>
+                            <form method="POST" action="{{ route('submissions.destroy', $submission->id) }}" style="display: inline;">
+                                @csrf
+                                @method('DELETE')
+                                <button type="submit" style="color: #e3342f; background: none; border: none; cursor: pointer; text-decoration: underline;" 
+                                        onclick="return confirm('Вы уверены, что хотите удалить эту заявку?')">
+                                    Удалить
+                                </button>
+                            </form>
+                        </td>
                     </tr>
                 @endforeach
             </tbody>

+ 90 - 0
resources/views/submission_detail.blade.php

@@ -0,0 +1,90 @@
+@extends('layout.app')
+
+@section('title', 'Детали заявки #' . $submission->id)
+
+@section('content')
+
+    <div style="margin-bottom: 20px;">
+        <a href="{{ route('data.show') }}" style="color: #3490dc; text-decoration: none;">← Назад к списку</a>
+    </div>
+
+    <h1>Заявка #{{ $submission->id }}</h1>
+
+    <div style="background-color: #f8f9fa; padding: 20px; border-radius: 8px; margin-bottom: 20px;">
+        <h2 style="margin-top: 0;">Основная информация</h2>
+        <p><strong>Имя:</strong> {{ $submission->name }}</p>
+        <p><strong>Email:</strong> {{ $submission->email }}</p>
+        <p><strong>Сообщение:</strong> {{ $submission->message ?? 'Отсутствует' }}</p>
+        <p><strong>Статус:</strong> {{ ucfirst($submission->status) }}</p>
+        <p><strong>IP-адрес:</strong> {{ $submission->ip_address }}</p>
+        <p><strong>Дата создания:</strong> {{ $submission->created_at->format('d.m.Y H:i:s') }}</p>
+        <p><strong>Последнее обновление:</strong> {{ $submission->updated_at->format('d.m.Y H:i:s') }}</p>
+    </div>
+
+    @if($submission->tags->isNotEmpty())
+        <div style="margin-bottom: 20px;">
+            <h2>Теги</h2>
+            @foreach($submission->tags as $tag)
+                <span style="display: inline-block; padding: 5px 12px; margin: 5px; background-color: #e3f2fd; border-radius: 5px;">
+                    {{ $tag->name }}
+                </span>
+            @endforeach
+        </div>
+    @endif
+
+    @if($submission->attachments->isNotEmpty())
+        <div style="margin-bottom: 20px;">
+            <h2>Вложения ({{ $submission->attachments->count() }})</h2>
+            <ul>
+                @foreach($submission->attachments as $attachment)
+                    <li>
+                        <strong>{{ $attachment->filename }}</strong> 
+                        ({{ round($attachment->size / 1024, 2) }} KB, {{ $attachment->mime_type }})
+                    </li>
+                @endforeach
+            </ul>
+        </div>
+    @endif
+
+    <div>
+        <h2>Комментарии ({{ $submission->comments->count() }})</h2>
+        
+        @if($submission->comments->isEmpty())
+            <p>Комментариев пока нет.</p>
+        @else
+            @foreach($submission->comments as $comment)
+                <div style="background-color: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 15px;">
+                    <p><strong>{{ $comment->author }}</strong> <small style="color: #6c757d;">{{ $comment->created_at->format('d.m.Y H:i') }}</small></p>
+                    <p>{{ $comment->content }}</p>
+                    
+                    @if($comment->attachments->isNotEmpty())
+                        <div style="margin-top: 10px;">
+                            <strong>Вложения:</strong>
+                            <ul style="margin-top: 5px;">
+                                @foreach($comment->attachments as $attachment)
+                                    <li>{{ $attachment->filename }}</li>
+                                @endforeach
+                            </ul>
+                        </div>
+                    @endif
+                </div>
+            @endforeach
+        @endif
+    </div>
+
+    <div style="margin-top: 30px;">
+        <a href="{{ route('submissions.edit', $submission->id) }}" style="padding: 10px 15px; background-color: #38c172; color: white; text-decoration: none; border-radius: 4px; margin-right: 10px;">
+            Редактировать
+        </a>
+        
+        <form method="POST" action="{{ route('submissions.destroy', $submission->id) }}" style="display: inline;">
+            @csrf
+            @method('DELETE')
+            <button type="submit" style="padding: 10px 15px; background-color: #e3342f; color: white; border: none; border-radius: 4px; cursor: pointer;"
+                    onclick="return confirm('Вы уверены, что хотите удалить эту заявку?')">
+                Удалить
+            </button>
+        </form>
+    </div>
+
+@endsection

+ 77 - 0
resources/views/submission_edit.blade.php

@@ -0,0 +1,77 @@
+@extends('layout.app')
+
+@section('title', 'Редактирование заявки #' . $submission->id)
+
+@section('content')
+
+    <div style="margin-bottom: 20px;">
+        <a href="{{ route('submissions.show', $submission->id) }}" style="color: #3490dc; text-decoration: none;">← Назад к заявке</a>
+    </div>
+
+    <h1>Редактирование заявки #{{ $submission->id }}</h1>
+
+    @if ($errors->any())
+        <div class="error-list">
+            <strong>Обнаружены ошибки:</strong>
+            <ul>
+                @foreach ($errors->all() as $error)
+                    <li>{{ $error }}</li>
+                @endforeach
+            </ul>
+        </div>
+    @endif
+
+    <form method="POST" action="{{ route('submissions.update', $submission->id) }}">
+        @csrf
+        @method('PUT')
+
+        <div style="margin-bottom: 15px;">
+            <label for="name" style="display: block; margin-bottom: 5px;">Имя:</label>
+            <input type="text" id="name" name="name" value="{{ old('name', $submission->name) }}" required 
+                   style="width: 100%; padding: 8px; box-sizing: border-box; border: 1px solid {{ $errors->has('name') ? 'red' : '#ccc' }};">
+            @error('name')
+                <small style="color: red;">{{ $message }}</small>
+            @enderror
+        </div>
+
+        <div style="margin-bottom: 15px;">
+            <label for="email" style="display: block; margin-bottom: 5px;">Email:</label>
+            <input type="email" id="email" name="email" value="{{ old('email', $submission->email) }}" required 
+                   style="width: 100%; padding: 8px; box-sizing: border-box; border: 1px solid {{ $errors->has('email') ? 'red' : '#ccc' }};">
+            @error('email')
+                <small style="color: red;">{{ $message }}</small>
+            @enderror
+        </div>
+
+        <div style="margin-bottom: 15px;">
+            <label for="message" style="display: block; margin-bottom: 5px;">Сообщение:</label>
+            <textarea id="message" name="message" rows="5" 
+                      style="width: 100%; padding: 8px; box-sizing: border-box; border: 1px solid {{ $errors->has('message') ? 'red' : '#ccc' }};">{{ old('message', $submission->message) }}</textarea>
+            @error('message')
+                <small style="color: red;">{{ $message }}</small>
+            @enderror
+        </div>
+
+        <div style="margin-bottom: 15px;">
+            <label for="status" style="display: block; margin-bottom: 5px;">Статус:</label>
+            <select id="status" name="status" required 
+                    style="width: 100%; padding: 8px; box-sizing: border-box; border: 1px solid #ccc;">
+                <option value="active" {{ old('status', $submission->status) === 'active' ? 'selected' : '' }}>Активно</option>
+                <option value="archived" {{ old('status', $submission->status) === 'archived' ? 'selected' : '' }}>Архивировано</option>
+                <option value="pending" {{ old('status', $submission->status) === 'pending' ? 'selected' : '' }}>В ожидании</option>
+            </select>
+            @error('status')
+                <small style="color: red;">{{ $message }}</small>
+            @enderror
+        </div>
+
+        <button type="submit" style="padding: 10px 15px; background-color: #3490dc; color: white; border: none; border-radius: 4px; cursor: pointer;">
+            Сохранить изменения
+        </button>
+
+        <a href="{{ route('submissions.show', $submission->id) }}" style="padding: 10px 15px; background-color: #6c757d; color: white; text-decoration: none; border-radius: 4px; margin-left: 10px; display: inline-block;">
+            Отмена
+        </a>
+    </form>
+
+@endsection

+ 28 - 0
routes/api.php

@@ -0,0 +1,28 @@
+<?php
+
+use App\Http\Controllers\Api\SubmissionController;
+use App\Http\Controllers\Api\CommentController;
+use App\Http\Controllers\Api\TagController;
+use Illuminate\Support\Facades\Route;
+
+// API маршруты для заявок
+Route::prefix('submissions')->group(function () {
+    Route::get('/', [SubmissionController::class, 'index']);
+    Route::post('/', [SubmissionController::class, 'store']);
+    Route::get('/{id}', [SubmissionController::class, 'show']);
+    Route::put('/{id}', [SubmissionController::class, 'update']);
+    Route::delete('/{id}', [SubmissionController::class, 'destroy']);
+    
+    // Дополнительные маршруты для мягкого удаления
+    Route::post('/{id}/restore', [SubmissionController::class, 'restore']);
+    Route::delete('/{id}/force', [SubmissionController::class, 'forceDelete']);
+    
+    // Комментарии к заявке
+    Route::get('/{submissionId}/comments', [CommentController::class, 'index']);
+    Route::post('/{submissionId}/comments', [CommentController::class, 'store']);
+    Route::put('/{submissionId}/comments/{id}', [CommentController::class, 'update']);
+    Route::delete('/{submissionId}/comments/{id}', [CommentController::class, 'destroy']);
+});
+
+// API маршруты для тегов
+Route::apiResource('tags', TagController::class);

+ 10 - 2
routes/web.php

@@ -1,10 +1,18 @@
 <?php
+
 use App\Http\Controllers\FormController;
 use App\Http\Controllers\DataController;
 use Illuminate\Support\Facades\Route;
 
+// Форма создания заявки
 Route::get('/', [FormController::class, 'showForm'])->name('form.show');
-
 Route::post('/submit', [FormController::class, 'submitForm'])->name('form.submit');
 
-Route::get('/data', [DataController::class, 'showData'])->name('data.show');
+// Просмотр всех данных
+Route::get('/data', [DataController::class, 'showData'])->name('data.show');
+
+// CRUD операции для заявок
+Route::get('/submissions/{id}', [DataController::class, 'show'])->name('submissions.show');
+Route::get('/submissions/{id}/edit', [DataController::class, 'edit'])->name('submissions.edit');
+Route::put('/submissions/{id}', [DataController::class, 'update'])->name('submissions.update');
+Route::delete('/submissions/{id}', [DataController::class, 'destroy'])->name('submissions.destroy');