diff --git a/Documentation/Config/config.php b/Documentation/Config/config.php deleted file mode 100644 index 4cc6008f..00000000 --- a/Documentation/Config/config.php +++ /dev/null @@ -1,25 +0,0 @@ - 'Documentation', - 'route_prefix' => 'documentation', - 'middleware' => ['web', 'auth'], - 'storage_disk' => env('DOCUMENTATION_DISK', 'public'), - 'upload_path' => env('DOCUMENTATION_UPLOAD_PATH', 'documentation'), - 'allowed_upload_mimes' => [ - 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'md', 'csv', 'png', 'jpg', 'jpeg', 'webp', - ], - 'max_upload_kb' => 20480, - 'import_markdown_attachments' => true, - 'max_markdown_import_bytes' => 1048576, - 'editor' => [ - 'markdown_toolbar' => true, - 'supports_code_blocks' => true, - 'supports_tables' => true, - ], - 'statuses' => [ - 'draft' => 'Draft', - 'published' => 'Published', - 'archived' => 'Archived', - ], -]; diff --git a/Documentation/Database/Migrations/2026_06_12_000000_create_documentation_categories_table.php b/Documentation/Database/Migrations/2026_06_12_000000_create_documentation_categories_table.php deleted file mode 100644 index 4d932cff..00000000 --- a/Documentation/Database/Migrations/2026_06_12_000000_create_documentation_categories_table.php +++ /dev/null @@ -1,35 +0,0 @@ -id(); - $table->string('name')->comment('Human-readable category name.'); - $table->string('slug')->unique()->comment('URL-safe category identifier.'); - $table->string('icon')->nullable()->comment('Optional UI icon class or key.'); - $table->string('colour', 32)->default('emerald')->comment('Display colour token.'); - $table->text('description')->nullable()->comment('Category description shown in the documentation UI.'); - $table->unsignedInteger('sort_order')->default(0)->index()->comment('Manual display order.'); - $table->boolean('is_active')->default(true)->index()->comment('Whether the category is visible and usable.'); - $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete()->comment('User that created the category.'); - $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete()->comment('Last user that updated the category.'); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('documentation_categories'); - } -}; diff --git a/Documentation/Database/Migrations/2026_06_12_000010_create_documentation_documents_table.php b/Documentation/Database/Migrations/2026_06_12_000010_create_documentation_documents_table.php deleted file mode 100644 index 5a05c111..00000000 --- a/Documentation/Database/Migrations/2026_06_12_000010_create_documentation_documents_table.php +++ /dev/null @@ -1,47 +0,0 @@ -id(); - $table->foreignId('documentation_category_id')->nullable()->constrained('documentation_categories')->nullOnDelete()->comment('Optional owning documentation category.'); - $table->string('title')->comment('Document title.'); - $table->string('slug')->unique()->comment('URL-safe document identifier.'); - $table->string('summary', 500)->nullable()->comment('Short document summary.'); - $table->longText('body')->nullable()->comment('Full document body, markdown or HTML-safe text depending on editor implementation.'); - $table->string('status', 32)->default('draft')->index()->comment('Workflow status: draft, published, or archived.'); - $table->boolean('is_featured')->default(false)->index()->comment('Whether this document is highlighted on the documentation dashboard.'); - $table->string('version', 50)->default('1.0.0')->comment('Document version label.'); - $table->string('reference', 100)->nullable()->index()->comment('Optional business reference or document code.'); - $table->string('attachment_disk')->nullable()->comment('Storage disk used for the attached file.'); - $table->string('attachment_path')->nullable()->comment('Storage path for the attached file.'); - $table->string('attachment_name')->nullable()->comment('Original uploaded file name.'); - $table->string('attachment_mime')->nullable()->comment('Uploaded file MIME type.'); - $table->unsignedBigInteger('attachment_size')->nullable()->comment('Uploaded file size in bytes.'); - $table->timestamp('published_at')->nullable()->index()->comment('Date/time the document was published.'); - $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete()->comment('User that created the document.'); - $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete()->comment('Last user that updated the document.'); - $table->timestamps(); - $table->softDeletes(); - - $table->index(['status', 'is_featured']); - $table->index(['documentation_category_id', 'status']); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('documentation_documents'); - } -}; diff --git a/Documentation/Database/Migrations/2026_06_12_000020_create_documentation_attachments_table.php b/Documentation/Database/Migrations/2026_06_12_000020_create_documentation_attachments_table.php deleted file mode 100644 index c0764708..00000000 --- a/Documentation/Database/Migrations/2026_06_12_000020_create_documentation_attachments_table.php +++ /dev/null @@ -1,35 +0,0 @@ -id(); - $table->foreignId('documentation_document_id')->constrained('documentation_documents')->cascadeOnDelete()->comment('Owning documentation document.'); - $table->string('disk')->comment('Storage disk used for the uploaded attachment.'); - $table->string('path')->comment('Storage path for the uploaded attachment.'); - $table->string('original_name')->comment('Original uploaded file name.'); - $table->string('mime_type')->nullable()->comment('Uploaded file MIME type.'); - $table->unsignedBigInteger('size')->nullable()->comment('Uploaded file size in bytes.'); - $table->foreignId('uploaded_by')->nullable()->constrained('users')->nullOnDelete()->comment('User that uploaded the attachment.'); - $table->timestamps(); - - $table->index(['documentation_document_id', 'created_at']); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('documentation_attachments'); - } -}; diff --git a/Documentation/Http/Controllers/DocumentationCategoryController.php b/Documentation/Http/Controllers/DocumentationCategoryController.php deleted file mode 100644 index ff4e8e63..00000000 --- a/Documentation/Http/Controllers/DocumentationCategoryController.php +++ /dev/null @@ -1,82 +0,0 @@ - DocumentationCategory::query()->withCount('documents')->ordered()->paginate(15), - ]); - } - - /** - * Show create form. - */ - public function create(): View - { - return view('documentation::categories.create', [ - 'category' => new DocumentationCategory(['colour' => 'emerald', 'is_active' => true]), - ]); - } - - /** - * Store a category. - */ - public function store(StoreDocumentationCategoryRequest $request): RedirectResponse - { - $data = $request->validated(); - $data['is_active'] = $request->boolean('is_active', true); - $data['created_by'] = $request->user()?->id; - $data['updated_by'] = $request->user()?->id; - - DocumentationCategory::query()->create($data); - - return redirect()->route('documentation.categories.index')->with('status', 'Category created successfully.'); - } - - /** - * Show edit form. - */ - public function edit(DocumentationCategory $category): View - { - return view('documentation::categories.edit', [ - 'category' => $category, - ]); - } - - /** - * Update a category. - */ - public function update(UpdateDocumentationCategoryRequest $request, DocumentationCategory $category): RedirectResponse - { - $data = $request->validated(); - $data['is_active'] = $request->boolean('is_active'); - $data['updated_by'] = $request->user()?->id; - - $category->update($data); - - return redirect()->route('documentation.categories.index')->with('status', 'Category updated successfully.'); - } - - /** - * Delete a category. - */ - public function destroy(DocumentationCategory $category): RedirectResponse - { - $category->delete(); - - return redirect()->route('documentation.categories.index')->with('status', 'Category deleted successfully.'); - } -} diff --git a/Documentation/Http/Controllers/DocumentationController.php b/Documentation/Http/Controllers/DocumentationController.php deleted file mode 100644 index 62071bea..00000000 --- a/Documentation/Http/Controllers/DocumentationController.php +++ /dev/null @@ -1,320 +0,0 @@ -with(['category', 'attachments'])->latest(); - - if ($request->filled('status')) { - $query->where('status', $request->string('status')->toString()); - } - - if ($request->filled('category')) { - $query->where('documentation_category_id', $request->integer('category')); - } - - if ($request->filled('search')) { - $search = '%' . $request->string('search')->trim()->toString() . '%'; - $query->where(function ($builder) use ($search): void { - $builder->where('title', 'like', $search) - ->orWhere('summary', 'like', $search) - ->orWhere('reference', 'like', $search) - ->orWhere('body', 'like', $search); - }); - } - - return view('documentation::documents.index', [ - 'documents' => $query->paginate(15)->withQueryString(), - 'categories' => DocumentationCategory::query()->ordered()->get(), - 'featuredDocuments' => DocumentationDocument::query()->with('category')->featured()->latest()->limit(4)->get(), - 'stats' => [ - 'total' => DocumentationDocument::query()->count(), - 'published' => DocumentationDocument::query()->published()->count(), - 'drafts' => DocumentationDocument::query()->where('status', DocumentationDocument::STATUS_DRAFT)->count(), - 'categories' => DocumentationCategory::query()->count(), - ], - 'statuses' => config('documentation.statuses', []), - ]); - } - - /** - * Show the create document form. - */ - public function create(): View - { - return view('documentation::documents.create', [ - 'document' => new DocumentationDocument(['status' => DocumentationDocument::STATUS_DRAFT, 'version' => '1.0.0']), - 'categories' => DocumentationCategory::query()->active()->ordered()->get(), - 'statuses' => config('documentation.statuses', []), - ]); - } - - /** - * Store a newly created document. - */ - public function store(StoreDocumentationDocumentRequest $request): RedirectResponse - { - $data = $request->validated(); - $data['is_featured'] = $request->boolean('is_featured'); - $data['created_by'] = $request->user()?->id; - $data['updated_by'] = $request->user()?->id; - - if ($request->hasFile('attachment')) { - if (blank($data['body'] ?? null)) { - $data['body'] = $this->extractMarkdownBody($request) ?: ($data['body'] ?? null); - } - - $data = array_merge($data, $this->storeAttachment($request)); - } - - unset($data['attachment'], $data['attachments']); - - $document = DocumentationDocument::query()->create($data); - $this->storeAttachments($request, $document); - - return redirect()->route('documentation.documents.show', $document)->with('status', 'Document created successfully.'); - } - - /** - * Display a document. - */ - public function show(DocumentationDocument $document): View - { - return view('documentation::documents.show', [ - 'document' => $document->load(['category', 'attachments']), - ]); - } - - /** - * Show the edit document form. - */ - public function edit(DocumentationDocument $document): View - { - return view('documentation::documents.edit', [ - 'document' => $document->load('attachments'), - 'categories' => DocumentationCategory::query()->active()->ordered()->get(), - 'statuses' => config('documentation.statuses', []), - ]); - } - - /** - * Update an existing document. - */ - public function update(UpdateDocumentationDocumentRequest $request, DocumentationDocument $document): RedirectResponse - { - $data = $request->validated(); - $data['is_featured'] = $request->boolean('is_featured'); - $data['updated_by'] = $request->user()?->id; - - if ($request->boolean('remove_attachment')) { - $this->deleteAttachment($document); - $data = array_merge($data, $this->emptyAttachmentData()); - } - - if ($request->hasFile('attachment')) { - $this->deleteAttachment($document); - - if (blank($data['body'] ?? null)) { - $data['body'] = $this->extractMarkdownBody($request) ?: ($data['body'] ?? null); - } - - $data = array_merge($data, $this->storeAttachment($request)); - } - - unset($data['attachment'], $data['attachments'], $data['remove_attachment']); - $document->update($data); - $this->storeAttachments($request, $document); - - return redirect()->route('documentation.documents.show', $document)->with('status', 'Document updated successfully.'); - } - - /** - * Remove a document. - */ - public function destroy(DocumentationDocument $document): RedirectResponse - { - $document->load('attachments'); - $this->deleteAttachment($document); - - foreach ($document->attachments as $attachment) { - Storage::disk($attachment->disk)->delete($attachment->path); - $attachment->delete(); - } - - $document->delete(); - - return redirect()->route('documentation.documents.index')->with('status', 'Document archived successfully.'); - } - - /** - * Download the attached document file. - */ - public function download(DocumentationDocument $document) - { - abort_if(blank($document->attachment_path), 404); - - return Storage::disk($document->attachment_disk ?: config('documentation.storage_disk', 'public')) - ->download($document->attachment_path, $document->attachment_name); - } - - /** - * Download one of the document's additional attachments. - */ - public function downloadAttachment(DocumentationDocument $document, DocumentationAttachment $attachment) - { - abort_if($attachment->documentation_document_id !== $document->id, 404); - - return Storage::disk($attachment->disk)->download($attachment->path, $attachment->original_name); - } - - /** - * Delete one of the document's additional attachments. - */ - public function destroyAttachment(DocumentationDocument $document, DocumentationAttachment $attachment): RedirectResponse - { - abort_if($attachment->documentation_document_id !== $document->id, 404); - - Storage::disk($attachment->disk)->delete($attachment->path); - $attachment->delete(); - - return redirect()->route('documentation.documents.edit', $document)->with('status', 'Attachment removed successfully.'); - } - - - /** - * Extract body content from an uploaded Markdown file when requested. - */ - private function extractMarkdownBody(Request $request): ?string - { - if (! config('documentation.import_markdown_attachments', true)) { - return null; - } - - $file = $request->file('attachment'); - - if ($file === null || ! $this->isMarkdownUpload($request)) { - return null; - } - - if ($file->getSize() > (int) config('documentation.max_markdown_import_bytes', 1048576)) { - return null; - } - - $content = file_get_contents($file->getRealPath()); - - if ($content === false) { - return null; - } - - return trim($content); - } - - /** - * Determine whether the uploaded attachment is a Markdown file. - */ - private function isMarkdownUpload(Request $request): bool - { - $file = $request->file('attachment'); - - if ($file === null) { - return false; - } - - $extension = strtolower($file->getClientOriginalExtension()); - $mime = strtolower((string) $file->getMimeType()); - - return $extension === 'md' || in_array($mime, ['text/markdown', 'text/x-markdown'], true); - } - - /** - * Store an uploaded attachment and return persisted metadata. - * - * @return array - */ - private function storeAttachment(Request $request): array - { - $file = $request->file('attachment'); - $disk = config('documentation.storage_disk', 'public'); - $path = $file->store(config('documentation.upload_path', 'documentation'), $disk); - - return [ - 'attachment_disk' => $disk, - 'attachment_path' => $path, - 'attachment_name' => $file->getClientOriginalName(), - 'attachment_mime' => $file->getMimeType(), - 'attachment_size' => $file->getSize(), - ]; - } - - /** - * Store all additional uploaded attachments for a document. - */ - private function storeAttachments(Request $request, DocumentationDocument $document): void - { - if (! $request->hasFile('attachments')) { - return; - } - - $disk = config('documentation.storage_disk', 'public'); - - foreach ((array) $request->file('attachments') as $file) { - if ($file === null || ! $file->isValid()) { - continue; - } - - $path = $file->store(config('documentation.upload_path', 'documentation'), $disk); - - $document->attachments()->create([ - 'disk' => $disk, - 'path' => $path, - 'original_name' => $file->getClientOriginalName(), - 'mime_type' => $file->getMimeType(), - 'size' => $file->getSize(), - 'uploaded_by' => $request->user()?->id, - ]); - } - } - - /** - * Delete a stored attachment if it exists. - */ - private function deleteAttachment(DocumentationDocument $document): void - { - if (filled($document->attachment_path)) { - Storage::disk($document->attachment_disk ?: config('documentation.storage_disk', 'public'))->delete($document->attachment_path); - } - } - - /** - * Empty attachment metadata. - * - * @return array - */ - private function emptyAttachmentData(): array - { - return [ - 'attachment_disk' => null, - 'attachment_path' => null, - 'attachment_name' => null, - 'attachment_mime' => null, - 'attachment_size' => null, - ]; - } -} diff --git a/Documentation/Http/Requests/StoreDocumentationCategoryRequest.php b/Documentation/Http/Requests/StoreDocumentationCategoryRequest.php deleted file mode 100644 index e098b99d..00000000 --- a/Documentation/Http/Requests/StoreDocumentationCategoryRequest.php +++ /dev/null @@ -1,35 +0,0 @@ - - */ - public function rules(): array - { - return [ - 'name' => ['required', 'string', 'max:150'], - 'slug' => ['nullable', 'string', 'max:180', Rule::unique('documentation_categories', 'slug')], - 'icon' => ['nullable', 'string', 'max:80'], - 'colour' => ['required', 'string', 'max:32'], - 'description' => ['nullable', 'string', 'max:2000'], - 'sort_order' => ['nullable', 'integer', 'min:0', 'max:999999'], - 'is_active' => ['nullable', 'boolean'], - ]; - } -} diff --git a/Documentation/Http/Requests/StoreDocumentationDocumentRequest.php b/Documentation/Http/Requests/StoreDocumentationDocumentRequest.php deleted file mode 100644 index b2931ecf..00000000 --- a/Documentation/Http/Requests/StoreDocumentationDocumentRequest.php +++ /dev/null @@ -1,41 +0,0 @@ - - */ - public function rules(): array - { - return [ - 'documentation_category_id' => ['nullable', 'integer', 'exists:documentation_categories,id'], - 'title' => ['required', 'string', 'max:220'], - 'slug' => ['nullable', 'string', 'max:240', Rule::unique('documentation_documents', 'slug')], - 'summary' => ['nullable', 'string', 'max:500'], - 'body' => ['nullable', 'string'], - 'status' => ['required', Rule::in([DocumentationDocument::STATUS_DRAFT, DocumentationDocument::STATUS_PUBLISHED, DocumentationDocument::STATUS_ARCHIVED])], - 'is_featured' => ['nullable', 'boolean'], - 'version' => ['required', 'string', 'max:50'], - 'reference' => ['nullable', 'string', 'max:100'], - 'attachment' => ['nullable', 'file', 'max:' . config('documentation.max_upload_kb', 20480), 'mimes:' . implode(',', config('documentation.allowed_upload_mimes', []))], - 'attachments' => ['nullable', 'array'], - 'attachments.*' => ['file', 'max:' . config('documentation.max_upload_kb', 20480), 'mimes:' . implode(',', config('documentation.allowed_upload_mimes', []))], - ]; - } -} diff --git a/Documentation/Http/Requests/UpdateDocumentationCategoryRequest.php b/Documentation/Http/Requests/UpdateDocumentationCategoryRequest.php deleted file mode 100644 index b003465d..00000000 --- a/Documentation/Http/Requests/UpdateDocumentationCategoryRequest.php +++ /dev/null @@ -1,39 +0,0 @@ - - */ - public function rules(): array - { - /** @var DocumentationCategory|null $category */ - $category = $this->route('category'); - - return [ - 'name' => ['required', 'string', 'max:150'], - 'slug' => ['nullable', 'string', 'max:180', Rule::unique('documentation_categories', 'slug')->ignore($category?->id)], - 'icon' => ['nullable', 'string', 'max:80'], - 'colour' => ['required', 'string', 'max:32'], - 'description' => ['nullable', 'string', 'max:2000'], - 'sort_order' => ['nullable', 'integer', 'min:0', 'max:999999'], - 'is_active' => ['nullable', 'boolean'], - ]; - } -} diff --git a/Documentation/Http/Requests/UpdateDocumentationDocumentRequest.php b/Documentation/Http/Requests/UpdateDocumentationDocumentRequest.php deleted file mode 100644 index 6d64014f..00000000 --- a/Documentation/Http/Requests/UpdateDocumentationDocumentRequest.php +++ /dev/null @@ -1,45 +0,0 @@ - - */ - public function rules(): array - { - /** @var DocumentationDocument|null $document */ - $document = $this->route('document'); - - return [ - 'documentation_category_id' => ['nullable', 'integer', 'exists:documentation_categories,id'], - 'title' => ['required', 'string', 'max:220'], - 'slug' => ['nullable', 'string', 'max:240', Rule::unique('documentation_documents', 'slug')->ignore($document?->id)], - 'summary' => ['nullable', 'string', 'max:500'], - 'body' => ['nullable', 'string'], - 'status' => ['required', Rule::in([DocumentationDocument::STATUS_DRAFT, DocumentationDocument::STATUS_PUBLISHED, DocumentationDocument::STATUS_ARCHIVED])], - 'is_featured' => ['nullable', 'boolean'], - 'version' => ['required', 'string', 'max:50'], - 'reference' => ['nullable', 'string', 'max:100'], - 'attachment' => ['nullable', 'file', 'max:' . config('documentation.max_upload_kb', 20480), 'mimes:' . implode(',', config('documentation.allowed_upload_mimes', []))], - 'attachments' => ['nullable', 'array'], - 'attachments.*' => ['file', 'max:' . config('documentation.max_upload_kb', 20480), 'mimes:' . implode(',', config('documentation.allowed_upload_mimes', []))], - 'remove_attachment' => ['nullable', 'boolean'], - ]; - } -} diff --git a/Documentation/Models/DocumentationAttachment.php b/Documentation/Models/DocumentationAttachment.php deleted file mode 100644 index 9f36876f..00000000 --- a/Documentation/Models/DocumentationAttachment.php +++ /dev/null @@ -1,69 +0,0 @@ - */ - protected $fillable = [ - 'documentation_document_id', - 'disk', - 'path', - 'original_name', - 'mime_type', - 'size', - 'uploaded_by', - ]; - - /** @var array */ - protected $casts = [ - 'size' => 'integer', - ]; - - /** - * Owning document. - * - * @return BelongsTo - */ - public function document(): BelongsTo - { - return $this->belongsTo(DocumentationDocument::class, 'documentation_document_id'); - } - - /** - * Return a readable file size label. - */ - public function getSizeLabelAttribute(): string - { - $bytes = (int) $this->size; - - if ($bytes <= 0) { - return 'Unknown size'; - } - - if ($bytes < 1024) { - return $bytes . ' B'; - } - - if ($bytes < 1048576) { - return number_format($bytes / 1024, 1) . ' KB'; - } - - return number_format($bytes / 1048576, 1) . ' MB'; - } -} diff --git a/Documentation/Models/DocumentationCategory.php b/Documentation/Models/DocumentationCategory.php deleted file mode 100644 index 3b74f2db..00000000 --- a/Documentation/Models/DocumentationCategory.php +++ /dev/null @@ -1,87 +0,0 @@ - */ - protected $fillable = [ - 'name', - 'slug', - 'icon', - 'colour', - 'description', - 'sort_order', - 'is_active', - 'created_by', - 'updated_by', - ]; - - /** @var array */ - protected $casts = [ - 'is_active' => 'boolean', - 'sort_order' => 'integer', - ]; - - /** - * Bootstrap the model. - */ - protected static function booted(): void - { - static::saving(function (DocumentationCategory $category): void { - if (blank($category->slug)) { - $category->slug = Str::slug($category->name); - } - }); - } - - /** - * Documents assigned to this category. - * - * @return HasMany - */ - public function documents(): HasMany - { - return $this->hasMany(DocumentationDocument::class, 'documentation_category_id'); - } - - /** - * Active category scope. - * - * @param Builder $query - * @return Builder - */ - public function scopeActive(Builder $query): Builder - { - return $query->where('is_active', true); - } - - /** - * Ordered category scope. - * - * @param Builder $query - * @return Builder - */ - public function scopeOrdered(Builder $query): Builder - { - return $query->orderBy('sort_order')->orderBy('name'); - } -} diff --git a/Documentation/Models/DocumentationDocument.php b/Documentation/Models/DocumentationDocument.php deleted file mode 100644 index 77136f91..00000000 --- a/Documentation/Models/DocumentationDocument.php +++ /dev/null @@ -1,152 +0,0 @@ - */ - protected $fillable = [ - 'documentation_category_id', - 'title', - 'slug', - 'summary', - 'body', - 'status', - 'is_featured', - 'version', - 'reference', - 'attachment_disk', - 'attachment_path', - 'attachment_name', - 'attachment_mime', - 'attachment_size', - 'published_at', - 'created_by', - 'updated_by', - ]; - - /** @var array */ - protected $casts = [ - 'is_featured' => 'boolean', - 'published_at' => 'datetime', - 'attachment_size' => 'integer', - ]; - - /** - * Bootstrap the model. - */ - protected static function booted(): void - { - static::saving(function (DocumentationDocument $document): void { - if (blank($document->slug)) { - $document->slug = Str::slug($document->title); - } - - if ($document->status === self::STATUS_PUBLISHED && blank($document->published_at)) { - $document->published_at = now(); - } - }); - } - - /** - * Owning category. - * - * @return BelongsTo - */ - public function category(): BelongsTo - { - return $this->belongsTo(DocumentationCategory::class, 'documentation_category_id'); - } - - /** - * Document attachments. - * - * @return HasMany - */ - public function attachments(): HasMany - { - return $this->hasMany(DocumentationAttachment::class, 'documentation_document_id')->latest(); - } - - /** - * Published document scope. - * - * @param Builder $query - * @return Builder - */ - public function scopePublished(Builder $query): Builder - { - return $query->where('status', self::STATUS_PUBLISHED); - } - - /** - * Featured document scope. - * - * @param Builder $query - * @return Builder - */ - public function scopeFeatured(Builder $query): Builder - { - return $query->where('is_featured', true); - } - /** - * Render the document body as safe Markdown HTML for display. - */ - public function getRenderedBodyAttribute(): string - { - $body = trim((string) $this->body); - - if ($body === '') { - return '

No document body has been added yet.

'; - } - - if (method_exists(Str::class, 'markdown')) { - return (string) Str::markdown($body, [ - 'html_input' => 'strip', - 'allow_unsafe_links' => false, - ]); - } - - return '

' . nl2br(e($body), false) . '

'; - } - - /** - * Determine whether the attached file is a Markdown document. - */ - public function hasMarkdownAttachment(): bool - { - $name = strtolower((string) $this->attachment_name); - $mime = strtolower((string) $this->attachment_mime); - - return str_ends_with($name, '.md') || in_array($mime, ['text/markdown', 'text/x-markdown', 'text/plain'], true); - } - -} diff --git a/Documentation/Providers/RouteServiceProvider.php b/Documentation/Providers/RouteServiceProvider.php deleted file mode 100644 index bc8373f8..00000000 --- a/Documentation/Providers/RouteServiceProvider.php +++ /dev/null @@ -1,67 +0,0 @@ -mapApiRoutes(); - - $this->mapWebRoutes(); - } - - /** - * Define the "web" routes for the application. - * - * These routes all receive session state, CSRF protection, etc. - * - * @return void - */ - protected function mapWebRoutes() - { - Route::middleware('web') - ->group(module_path('Documentation', '/Routes/web.php')); - } - - /** - * Define the "api" routes for the application. - * - * These routes are typically stateless. - * - * @return void - */ - protected function mapApiRoutes() - { - Route::prefix('api') - ->middleware('api') - ->group(module_path('Documentation', '/Routes/api.php')); - } -} diff --git a/Documentation/Resources/views/categories/create.blade.php b/Documentation/Resources/views/categories/create.blade.php deleted file mode 100644 index c6c90cd3..00000000 --- a/Documentation/Resources/views/categories/create.blade.php +++ /dev/null @@ -1,18 +0,0 @@ -@extends('documentation::layouts.master') - -@section('title', 'Create Category') - -@section('content') - @include('documentation::partials.hero', [ - 'title' => 'Create category', - 'subtitle' => 'Add a professional grouping for documents and operational knowledge.', - ]) -
-
Category details
-
-
- @include('documentation::partials.category-form') -
-
-
-@endsection diff --git a/Documentation/Resources/views/categories/edit.blade.php b/Documentation/Resources/views/categories/edit.blade.php deleted file mode 100644 index 1abbaaff..00000000 --- a/Documentation/Resources/views/categories/edit.blade.php +++ /dev/null @@ -1,18 +0,0 @@ -@extends('documentation::layouts.master') - -@section('title', 'Edit Category') - -@section('content') - @include('documentation::partials.hero', [ - 'title' => 'Edit category', - 'subtitle' => 'Update category naming, ordering, visibility, and presentation metadata.', - ]) -
-
{{ $category->name }}
-
-
- @include('documentation::partials.category-form') -
-
-
-@endsection diff --git a/Documentation/Resources/views/categories/index.blade.php b/Documentation/Resources/views/categories/index.blade.php deleted file mode 100644 index 10a43602..00000000 --- a/Documentation/Resources/views/categories/index.blade.php +++ /dev/null @@ -1,57 +0,0 @@ -@extends('documentation::layouts.master') - -@section('title', 'Document Categories') - -@section('content') - @include('documentation::partials.hero', [ - 'title' => 'Document categories', - 'subtitle' => 'Organise documents by policy, process, onboarding, operational support, or technical area.', - ]) - - @if(session('status')) -
{{ session('status') }}
- @endif - -
-
- Categories - New category -
-
- - - - - - - - - - - - - @forelse($categories as $category) - - - - - - - - - @empty - - @endforelse - -
NameDescriptionDocumentsOrderStatus
{{ $category->name }}
{{ $category->slug }}
{{ $category->description ?: 'No description' }}{{ $category->documents_count }}{{ $category->sort_order }}{{ $category->is_active ? 'Active' : 'Inactive' }} - Edit -
- @csrf - @method('DELETE') - -
-
No categories found.
-
{{ $categories->links() }}
-
-
-@endsection diff --git a/Documentation/Resources/views/documents/create.blade.php b/Documentation/Resources/views/documents/create.blade.php deleted file mode 100644 index cc6d574f..00000000 --- a/Documentation/Resources/views/documents/create.blade.php +++ /dev/null @@ -1,18 +0,0 @@ -@extends('documentation::layouts.master') - -@section('title', 'Create Document') - -@section('content') - @include('documentation::partials.hero', [ - 'title' => 'Create document', - 'subtitle' => 'Add a controlled document, guide, policy, SOP, upload, or operational note.', - ]) -
-
Document details
-
-
- @include('documentation::partials.document-form') -
-
-
-@endsection diff --git a/Documentation/Resources/views/documents/edit.blade.php b/Documentation/Resources/views/documents/edit.blade.php deleted file mode 100644 index 28b0bd98..00000000 --- a/Documentation/Resources/views/documents/edit.blade.php +++ /dev/null @@ -1,25 +0,0 @@ -@extends('documentation::layouts.master') - -@section('title', 'Edit Document') - -@section('content') - @include('documentation::partials.hero', [ - 'title' => 'Edit document', - 'subtitle' => 'Maintain document metadata, workflow status, version and attachment details.', - ]) -
-
{{ $document->title }}
-
-
- @include('documentation::partials.document-form') -
-
-
- - @foreach($document->attachments as $attachment) -
- @csrf - @method('DELETE') -
- @endforeach -@endsection diff --git a/Documentation/Resources/views/documents/index.blade.php b/Documentation/Resources/views/documents/index.blade.php deleted file mode 100644 index a30c84a4..00000000 --- a/Documentation/Resources/views/documents/index.blade.php +++ /dev/null @@ -1,92 +0,0 @@ -@extends('documentation::layouts.master') - -@section('title', 'Documentation') - -@section('content') - @include('documentation::partials.hero', [ - 'title' => 'Documentation Centre', - 'subtitle' => 'Create, control, publish, and archive operational documents from a clean professional register.', - ]) - - @if(session('status')) -
{{ session('status') }}
- @endif - -
-
{{ $stats['total'] }}Total documents
-
{{ $stats['published'] }}Published
-
{{ $stats['drafts'] }}Drafts
-
{{ $stats['categories'] }}Categories
-
- - @if($featuredDocuments->isNotEmpty()) -
-
Featured documents
- -
- @endif - -
-
- Document register - New document -
-
-
- - - - -
- - - - - - - - - - - - - - @forelse($documents as $document) - - - - - - - - - @empty - - @endforelse - -
DocumentCategoryStatusVersionUpdated
- {{ $document->title }} -
{{ $document->reference ?: 'No reference' }} · {{ $document->summary ?: 'No summary' }}
-
{{ $document->category?->name ?: 'Uncategorised' }}{{ ucfirst($document->status) }}{{ $document->version }}{{ $document->updated_at?->format('d M Y H:i') }}Edit
No documents found.
-
{{ $documents->links() }}
-
-
-@endsection diff --git a/Documentation/Resources/views/documents/show.blade.php b/Documentation/Resources/views/documents/show.blade.php deleted file mode 100644 index 9f2dbeb0..00000000 --- a/Documentation/Resources/views/documents/show.blade.php +++ /dev/null @@ -1,53 +0,0 @@ -@extends('documentation::layouts.master') - -@section('title', $document->title) - -@section('content') - @include('documentation::partials.hero', [ - 'title' => $document->title, - 'subtitle' => $document->summary ?: 'Controlled documentation record with versioning, attachments and Markdown rendering.', - 'actions' => [ - ['label' => 'Edit document', 'url' => route('documentation.documents.edit', $document), 'class' => 'primary'], - ['label' => 'Back to documents', 'url' => route('documentation.documents.index'), 'class' => 'secondary'], - ], - ]) - -
-
-
- {{ $document->reference ?: 'Document' }} -
Version {{ $document->version }} @if($document->category) · {{ $document->category->name }} @endif
-
- {{ ucfirst($document->status) }} -
-
-
{!! $document->rendered_body !!}
-
-
- - @if($document->attachment_name || $document->attachments->isNotEmpty()) -
-
Attachments
-
-
- @if($document->attachment_name) -
- -
- @endif - @foreach($document->attachments as $attachment) -
-
- {{ $attachment->original_name }} - {{ $attachment->size_label }} -
-
- @endforeach -
-
-
- @endif -@endsection diff --git a/Documentation/Resources/views/index.blade.php b/Documentation/Resources/views/index.blade.php deleted file mode 100644 index 1c568d76..00000000 --- a/Documentation/Resources/views/index.blade.php +++ /dev/null @@ -1 +0,0 @@ -@extends('documentation::documents.index') diff --git a/Documentation/Resources/views/layouts/master.blade.php b/Documentation/Resources/views/layouts/master.blade.php deleted file mode 100644 index 8d46ba1e..00000000 --- a/Documentation/Resources/views/layouts/master.blade.php +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - {{ trim($__env->yieldContent('title', 'Documentation')) }} - {{ config('app.name', 'Laravel') }} - - - -
- @yield('content') -
- - - diff --git a/Documentation/Resources/views/partials/category-form.blade.php b/Documentation/Resources/views/partials/category-form.blade.php deleted file mode 100644 index 14657b2f..00000000 --- a/Documentation/Resources/views/partials/category-form.blade.php +++ /dev/null @@ -1,43 +0,0 @@ -@csrf -@if($category->exists) - @method('PUT') -@endif -
-
- - - @error('name')
{{ $message }}
@enderror -
-
- - - @error('slug')
{{ $message }}
@enderror -
-
- - - @error('icon')
{{ $message }}
@enderror -
-
- - - @error('colour')
{{ $message }}
@enderror -
-
- - - @error('sort_order')
{{ $message }}
@enderror -
-
- -
-
- - - @error('description')
{{ $message }}
@enderror -
-
-
- - Cancel -
diff --git a/Documentation/Resources/views/partials/document-form.blade.php b/Documentation/Resources/views/partials/document-form.blade.php deleted file mode 100644 index b49c33cb..00000000 --- a/Documentation/Resources/views/partials/document-form.blade.php +++ /dev/null @@ -1,109 +0,0 @@ -@csrf -@if($document->exists) - @method('PUT') -@endif - -
-
- - - @error('title')
{{ $message }}
@enderror -
-
- - - @error('slug')
{{ $message }}
@enderror -
-
- - - @error('documentation_category_id')
{{ $message }}
@enderror -
-
- - - @error('status')
{{ $message }}
@enderror -
-
- - - @error('version')
{{ $message }}
@enderror -
-
- - - @error('reference')
{{ $message }}
@enderror -
-
- - - @error('summary')
{{ $message }}
@enderror -
-
- -
-
- - - - - - - - - - -
- -

Markdown is supported, including fenced code blocks such as ```php, tables, lists, links, and headings.

-
- @error('body')
{{ $message }}
@enderror -
-
- - -

Primary Markdown files can be imported into the document body when the body is blank.

- @if($document->attachment_name) -

Current primary file: {{ $document->attachment_name }}

- - @endif - @error('attachment')
{{ $message }}
@enderror -
-
- - -

Attach supporting files such as PDFs, screenshots, spreadsheets, Word documents, Markdown, CSV, and images.

- @error('attachments')
{{ $message }}
@enderror - @error('attachments.*')
{{ $message }}
@enderror - - @if($document->exists && $document->attachments->isNotEmpty()) -
- @foreach($document->attachments as $attachment) -
-
- {{ $attachment->original_name }} - {{ $attachment->size_label }} -
- -
- @endforeach -
- @endif -
-
- -
-
-
- - Cancel -
diff --git a/Documentation/Resources/views/partials/hero.blade.php b/Documentation/Resources/views/partials/hero.blade.php deleted file mode 100644 index 782c9c31..00000000 --- a/Documentation/Resources/views/partials/hero.blade.php +++ /dev/null @@ -1,13 +0,0 @@ -
-
-

{{ $eyebrow ?? 'Documentation Suite' }}

-

{{ $title ?? 'Documentation' }}

-

{{ $subtitle ?? 'Manage controlled documents, policies, guides, files, and operational knowledge in one professional workspace.' }}

- -
- -
diff --git a/Documentation/Routes/api.php b/Documentation/Routes/api.php deleted file mode 100644 index f8e55aef..00000000 --- a/Documentation/Routes/api.php +++ /dev/null @@ -1,11 +0,0 @@ -name('api.documentation.')->group(function (): void { - Route::get('/status', static fn (): array => [ - 'module' => 'documentation', - 'status' => 'ok', - 'version' => '1.1.0', - ])->name('status'); -}); diff --git a/Documentation/Routes/web.php b/Documentation/Routes/web.php deleted file mode 100644 index eca35047..00000000 --- a/Documentation/Routes/web.php +++ /dev/null @@ -1,24 +0,0 @@ -name('documentation.') - ->middleware(config('documentation.middleware', ['web', 'auth'])) - ->group(function (): void { - Route::get('/', [DocumentationController::class, 'index'])->name('index'); - Route::get('/documents', [DocumentationController::class, 'index'])->name('documents.index'); - Route::get('/documents/create', [DocumentationController::class, 'create'])->name('documents.create'); - Route::post('/documents', [DocumentationController::class, 'store'])->name('documents.store'); - Route::get('/documents/{document}', [DocumentationController::class, 'show'])->name('documents.show'); - Route::get('/documents/{document}/edit', [DocumentationController::class, 'edit'])->name('documents.edit'); - Route::put('/documents/{document}', [DocumentationController::class, 'update'])->name('documents.update'); - Route::delete('/documents/{document}', [DocumentationController::class, 'destroy'])->name('documents.destroy'); - Route::get('/documents/{document}/download', [DocumentationController::class, 'download'])->name('documents.download'); - Route::get('/documents/{document}/attachments/{attachment}/download', [DocumentationController::class, 'downloadAttachment'])->name('documents.attachments.download'); - Route::delete('/documents/{document}/attachments/{attachment}', [DocumentationController::class, 'destroyAttachment'])->name('documents.attachments.destroy'); - - Route::resource('categories', DocumentationCategoryController::class)->except(['show']); - }); diff --git a/Documentation/Tests/Feature/DocumentationModuleTest.php b/Documentation/Tests/Feature/DocumentationModuleTest.php deleted file mode 100644 index 608049c8..00000000 --- a/Documentation/Tests/Feature/DocumentationModuleTest.php +++ /dev/null @@ -1,176 +0,0 @@ -create(); - - $this->actingAs($user) - ->get(route('documentation.documents.index')) - ->assertOk() - ->assertSee('Documentation Centre'); - } - - /** - * Documents can be created through the CRUD endpoint. - */ - public function test_document_can_be_created(): void - { - $user = \App\Models\User::factory()->create(); - $category = DocumentationCategory::query()->create([ - 'name' => 'Operations', - 'slug' => 'operations', - 'colour' => 'emerald', - 'is_active' => true, - ]); - - $response = $this->actingAs($user)->post(route('documentation.documents.store'), [ - 'documentation_category_id' => $category->id, - 'title' => 'Goods In Process', - 'slug' => 'goods-in-process', - 'summary' => 'Inbound warehouse process.', - 'body' => 'Step-by-step goods in process.', - 'status' => DocumentationDocument::STATUS_PUBLISHED, - 'version' => '1.0.0', - 'reference' => 'SOP-001', - 'is_featured' => '1', - ]); - - $response->assertRedirect(); - $this->assertDatabaseHas('documentation_documents', [ - 'title' => 'Goods In Process', - 'status' => DocumentationDocument::STATUS_PUBLISHED, - 'reference' => 'SOP-001', - 'is_featured' => true, - ]); - } - - - /** - * Markdown attachments are stored and imported into the document body. - */ - public function test_markdown_attachment_is_imported_and_rendered(): void - { - Storage::fake('public'); - - $user = \App\Models\User::factory()->create(); - $file = UploadedFile::fake()->createWithContent('goods-in.md', "# Goods In\n\n- Scan delivery note\n- Confirm quantities"); - - $response = $this->actingAs($user)->post(route('documentation.documents.store'), [ - 'title' => 'Goods In Markdown', - 'slug' => 'goods-in-markdown', - 'summary' => 'Markdown controlled document.', - 'body' => '', - 'status' => DocumentationDocument::STATUS_PUBLISHED, - 'version' => '1.0.0', - 'attachment' => $file, - ]); - - $response->assertRedirect(); - - $document = DocumentationDocument::query()->where('slug', 'goods-in-markdown')->firstOrFail(); - - $this->assertStringContainsString('# Goods In', (string) $document->body); - $this->assertStringContainsString('

Goods In

', $document->rendered_body); - Storage::disk('public')->assertExists($document->attachment_path); - } - - - - /** - * Multiple supporting attachments can be uploaded, downloaded and removed. - */ - public function test_document_supports_multiple_attachments(): void - { - Storage::fake('public'); - - $user = \App\Models\User::factory()->create(); - - $response = $this->actingAs($user)->post(route('documentation.documents.store'), [ - 'title' => 'Attachment Pack', - 'slug' => 'attachment-pack', - 'summary' => 'Document with supporting files.', - 'body' => 'Attach evidence and supporting material.', - 'status' => DocumentationDocument::STATUS_PUBLISHED, - 'version' => '1.0.0', - 'attachments' => [ - UploadedFile::fake()->createWithContent('setup.md', "```php\necho 'ok';\n```"), - UploadedFile::fake()->create('process.pdf', 64, 'application/pdf'), - ], - ]); - - $response->assertRedirect(); - - $document = DocumentationDocument::query()->where('slug', 'attachment-pack')->firstOrFail(); - - $this->assertCount(2, $document->attachments()->get()); - - $attachment = $document->attachments()->where('original_name', 'setup.md')->firstOrFail(); - Storage::disk('public')->assertExists($attachment->path); - - $this->actingAs($user) - ->get(route('documentation.documents.attachments.download', [$document, $attachment])) - ->assertOk(); - - $this->actingAs($user) - ->delete(route('documentation.documents.attachments.destroy', [$document, $attachment])) - ->assertRedirect(route('documentation.documents.edit', $document)); - - $this->assertDatabaseMissing('documentation_attachments', ['id' => $attachment->id]); - Storage::disk('public')->assertMissing($attachment->path); - } - - /** - * The editor renders Markdown controls for code blocks and tables. - */ - public function test_document_editor_has_markdown_code_controls(): void - { - $user = \App\Models\User::factory()->create(); - - $this->actingAs($user) - ->get(route('documentation.documents.create')) - ->assertOk() - ->assertSee('data-documentation-editor', false) - ->assertSee('Code block') - ->assertSee('Markdown is supported'); - } - - /** - * Categories can be created through the CRUD endpoint. - */ - public function test_category_can_be_created(): void - { - $user = \App\Models\User::factory()->create(); - - $this->actingAs($user)->post(route('documentation.categories.store'), [ - 'name' => 'Policies', - 'slug' => 'policies', - 'colour' => 'emerald', - 'description' => 'Controlled business policies.', - 'sort_order' => 1, - 'is_active' => '1', - ])->assertRedirect(route('documentation.categories.index')); - - $this->assertDatabaseHas('documentation_categories', [ - 'name' => 'Policies', - 'slug' => 'policies', - 'is_active' => true, - ]); - } -} diff --git a/Documentation/changelog.yaml b/Documentation/changelog.yaml deleted file mode 100644 index 83197344..00000000 --- a/Documentation/changelog.yaml +++ /dev/null @@ -1,14 +0,0 @@ -version: 1.4.0 -date: 2026-06-15 -changes: - - Added professional documentation dashboard and document CRUD. - - Added document categories with status, ordering, colour, and icon support. - - Added migrations, Eloquent models, form requests, routes, and feature coverage. - - Added self-contained Abbott-style module views with accessible responsive styling. - - Added Markdown upload processing for .md files with safe rendered HTML preview. - - Added Markdown import coverage and styling for headings, code blocks, tables, and blockquotes. - - Added multiple supporting attachments per document with upload, download, removal, storage cleanup, and tests. - - Added a free built-in Markdown editor toolbar with controls for headings, bold, italic, quotes, lists, links, inline code, fenced code blocks, and tables. - - Added WMS Support sidebar integration for Documentation, New Document, and Categories links using the existing sidebar colour scheme and active-state conventions. - - Added a root documentation.index route while preserving documentation.documents.index for CRUD navigation. - - Fixed module composer metadata and synced module.json version. diff --git a/Documentation/composer.json b/Documentation/composer.json deleted file mode 100644 index c5500bce..00000000 --- a/Documentation/composer.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "mpba/documentation", - "description": "Professional documentation and document management module.", - "type": "laravel-module", - "authors": [ - { - "name": "Mark Gregory", - "email": "mark.gregory@mpba.co.uk" - } - ], - "extra": { - "laravel": { - "providers": [], - "aliases": {} - } - }, - "autoload": { - "psr-4": { - "Modules\\Documentation\\": "" - } - }, - "autoload-dev": { - "psr-4": { - "Modules\\Documentation\\Tests\\": "Tests/" - } - } -} diff --git a/Documentation/module.json b/Documentation/module.json deleted file mode 100644 index 0564e5c4..00000000 --- a/Documentation/module.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "Documentation", - "alias": "documentation", - "version": "1.4.0", - "description": "Professional document management and knowledge-base module with categories, CRUD, publishing, Markdown editing/import support, code blocks, attachments, and WMS Support navigation integration.", - "keywords": [ - "documentation", - "documents", - "knowledge-base", - "crud" - ], - "priority": 0, - "providers": [ - "Modules\\Documentation\\Providers\\DocumentationServiceProvider" - ], - "aliases": {}, - "files": [], - "requires": [] -} diff --git a/Modules/Documentation/Config/.gitkeep b/Modules/Documentation/Config/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Modules/Documentation/Config/config.php b/Modules/Documentation/Config/config.php deleted file mode 100644 index 4cc6008f..00000000 --- a/Modules/Documentation/Config/config.php +++ /dev/null @@ -1,25 +0,0 @@ - 'Documentation', - 'route_prefix' => 'documentation', - 'middleware' => ['web', 'auth'], - 'storage_disk' => env('DOCUMENTATION_DISK', 'public'), - 'upload_path' => env('DOCUMENTATION_UPLOAD_PATH', 'documentation'), - 'allowed_upload_mimes' => [ - 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'md', 'csv', 'png', 'jpg', 'jpeg', 'webp', - ], - 'max_upload_kb' => 20480, - 'import_markdown_attachments' => true, - 'max_markdown_import_bytes' => 1048576, - 'editor' => [ - 'markdown_toolbar' => true, - 'supports_code_blocks' => true, - 'supports_tables' => true, - ], - 'statuses' => [ - 'draft' => 'Draft', - 'published' => 'Published', - 'archived' => 'Archived', - ], -]; diff --git a/Modules/Documentation/Console/.gitkeep b/Modules/Documentation/Console/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Modules/Documentation/Database/Migrations/.gitkeep b/Modules/Documentation/Database/Migrations/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Modules/Documentation/Database/Migrations/2026_06_12_000000_create_documentation_categories_table.php b/Modules/Documentation/Database/Migrations/2026_06_12_000000_create_documentation_categories_table.php deleted file mode 100644 index 4d932cff..00000000 --- a/Modules/Documentation/Database/Migrations/2026_06_12_000000_create_documentation_categories_table.php +++ /dev/null @@ -1,35 +0,0 @@ -id(); - $table->string('name')->comment('Human-readable category name.'); - $table->string('slug')->unique()->comment('URL-safe category identifier.'); - $table->string('icon')->nullable()->comment('Optional UI icon class or key.'); - $table->string('colour', 32)->default('emerald')->comment('Display colour token.'); - $table->text('description')->nullable()->comment('Category description shown in the documentation UI.'); - $table->unsignedInteger('sort_order')->default(0)->index()->comment('Manual display order.'); - $table->boolean('is_active')->default(true)->index()->comment('Whether the category is visible and usable.'); - $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete()->comment('User that created the category.'); - $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete()->comment('Last user that updated the category.'); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('documentation_categories'); - } -}; diff --git a/Modules/Documentation/Database/Migrations/2026_06_12_000010_create_documentation_documents_table.php b/Modules/Documentation/Database/Migrations/2026_06_12_000010_create_documentation_documents_table.php deleted file mode 100644 index 5a05c111..00000000 --- a/Modules/Documentation/Database/Migrations/2026_06_12_000010_create_documentation_documents_table.php +++ /dev/null @@ -1,47 +0,0 @@ -id(); - $table->foreignId('documentation_category_id')->nullable()->constrained('documentation_categories')->nullOnDelete()->comment('Optional owning documentation category.'); - $table->string('title')->comment('Document title.'); - $table->string('slug')->unique()->comment('URL-safe document identifier.'); - $table->string('summary', 500)->nullable()->comment('Short document summary.'); - $table->longText('body')->nullable()->comment('Full document body, markdown or HTML-safe text depending on editor implementation.'); - $table->string('status', 32)->default('draft')->index()->comment('Workflow status: draft, published, or archived.'); - $table->boolean('is_featured')->default(false)->index()->comment('Whether this document is highlighted on the documentation dashboard.'); - $table->string('version', 50)->default('1.0.0')->comment('Document version label.'); - $table->string('reference', 100)->nullable()->index()->comment('Optional business reference or document code.'); - $table->string('attachment_disk')->nullable()->comment('Storage disk used for the attached file.'); - $table->string('attachment_path')->nullable()->comment('Storage path for the attached file.'); - $table->string('attachment_name')->nullable()->comment('Original uploaded file name.'); - $table->string('attachment_mime')->nullable()->comment('Uploaded file MIME type.'); - $table->unsignedBigInteger('attachment_size')->nullable()->comment('Uploaded file size in bytes.'); - $table->timestamp('published_at')->nullable()->index()->comment('Date/time the document was published.'); - $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete()->comment('User that created the document.'); - $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete()->comment('Last user that updated the document.'); - $table->timestamps(); - $table->softDeletes(); - - $table->index(['status', 'is_featured']); - $table->index(['documentation_category_id', 'status']); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('documentation_documents'); - } -}; diff --git a/Modules/Documentation/Database/Migrations/2026_06_12_000020_create_documentation_attachments_table.php b/Modules/Documentation/Database/Migrations/2026_06_12_000020_create_documentation_attachments_table.php deleted file mode 100644 index c0764708..00000000 --- a/Modules/Documentation/Database/Migrations/2026_06_12_000020_create_documentation_attachments_table.php +++ /dev/null @@ -1,35 +0,0 @@ -id(); - $table->foreignId('documentation_document_id')->constrained('documentation_documents')->cascadeOnDelete()->comment('Owning documentation document.'); - $table->string('disk')->comment('Storage disk used for the uploaded attachment.'); - $table->string('path')->comment('Storage path for the uploaded attachment.'); - $table->string('original_name')->comment('Original uploaded file name.'); - $table->string('mime_type')->nullable()->comment('Uploaded file MIME type.'); - $table->unsignedBigInteger('size')->nullable()->comment('Uploaded file size in bytes.'); - $table->foreignId('uploaded_by')->nullable()->constrained('users')->nullOnDelete()->comment('User that uploaded the attachment.'); - $table->timestamps(); - - $table->index(['documentation_document_id', 'created_at']); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('documentation_attachments'); - } -}; diff --git a/Modules/Documentation/Database/Seeders/.gitkeep b/Modules/Documentation/Database/Seeders/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Modules/Documentation/Database/Seeders/DocumentationDatabaseSeeder.php b/Modules/Documentation/Database/Seeders/DocumentationDatabaseSeeder.php deleted file mode 100644 index a1b97e89..00000000 --- a/Modules/Documentation/Database/Seeders/DocumentationDatabaseSeeder.php +++ /dev/null @@ -1,21 +0,0 @@ -call("OthersTableSeeder"); - } -} diff --git a/Modules/Documentation/Database/factories/.gitkeep b/Modules/Documentation/Database/factories/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Modules/Documentation/Helpers/.gitkeep b/Modules/Documentation/Helpers/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Modules/Documentation/Helpers/Helper.php b/Modules/Documentation/Helpers/Helper.php deleted file mode 100644 index b3c1d761..00000000 --- a/Modules/Documentation/Helpers/Helper.php +++ /dev/null @@ -1,12 +0,0 @@ -registerTranslations(); - $this->registerConfig(); - $this->registerViews(); - $this->registerHelpers(); - $this->registerCommands(); - $this->loadMigrationsFrom(module_path(ModuleInfo::name(), 'Database/Migrations')); - } - - /** - * Register the service provider. - * - * @return void - */ - public function register(): void - { - $this->app->register(RouteServiceProvider::class); - } - - /** - * Register the helpers. - * - * @return void - */ - protected function registerHelpers(): void - { - foreach (glob(__DIR__.'/../Helpers/*.php') as $filename) { - require_once $filename; - } - } - - /** - * Dynamically Register Commands. - * - * @return void - */ - public function registerCommands(): void - { - if (!$this->app->runningInConsole()) { - return; - } - $commandPath = __DIR__ . '/../Console'; - $commands = collect(File::allFiles($commandPath)) - ->map(function ($file) { - $class = 'Modules\\' . $this->moduleName . '\\Console\\' . $file->getFilenameWithoutExtension(); - return $class; - }) - ->filter(function ($class) { - return class_exists($class); - }) - ->values() - ->all(); - $this->commands($commands); - } - - /** - * Register config. - * - * @return void - */ - protected function registerConfig(): void - { - $this->publishes([ - module_path(ModuleInfo::name(), 'Config/config.php') => config_path( ModuleInfo::nameLower(). '.php'), - ], 'config'); - $this->mergeConfigFrom( - module_path(ModuleInfo::name(), 'Config/config.php'), ModuleInfo::nameLower() - ); - } - - /** - * Register views. - * - * @return void - */ - public function registerViews(): void - { - $viewPath = resource_path('views/modules/' . ModuleInfo::nameLower()); - $sourcePath = module_path(ModuleInfo::name(), 'Resources/views'); - $this->publishes([ - $sourcePath => $viewPath - ], ['views', ModuleInfo::nameLower() . '-module-views']); - $this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), ModuleInfo::nameLower()); - } - - /** - * Register translations. - * - * @return void - */ - public function registerTranslations(): void - { - $langPath = resource_path('lang/modules/' . ModuleInfo::nameLower()); - - if (is_dir($langPath)) { - $this->loadTranslationsFrom($langPath, ModuleInfo::nameLower()); - } else { - $this->loadTranslationsFrom(module_path(ModuleInfo::name(), 'Resources/lang'), ModuleInfo::nameLower()); - } - } - - /** - * Regsiter View Paths - * @return array - */ - private function getPublishableViewPaths(): array - { - $paths = []; - foreach (Config::get('view.paths') as $path) { - if (is_dir($path . '/modules/' . ModuleInfo::nameLower())) { - $paths[] = $path . '/modules/' . ModuleInfo::nameLower(); - } - } - return $paths; - } -} diff --git a/Modules/Documentation/Resources/assets/.gitkeep b/Modules/Documentation/Resources/assets/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Modules/Documentation/Resources/assets/js/app.js b/Modules/Documentation/Resources/assets/js/app.js deleted file mode 100644 index e69de29b..00000000 diff --git a/Modules/Documentation/Resources/assets/sass/app.scss b/Modules/Documentation/Resources/assets/sass/app.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/Modules/Documentation/Resources/lang/.gitkeep b/Modules/Documentation/Resources/lang/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Modules/Documentation/Resources/views/.gitkeep b/Modules/Documentation/Resources/views/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Modules/Documentation/Resources/views/categories/create.blade.php b/Modules/Documentation/Resources/views/categories/create.blade.php index c6c90cd3..b44fbe50 100644 --- a/Modules/Documentation/Resources/views/categories/create.blade.php +++ b/Modules/Documentation/Resources/views/categories/create.blade.php @@ -3,6 +3,7 @@ @section('title', 'Create Category') @section('content') +
@include('documentation::partials.hero', [ 'title' => 'Create category', 'subtitle' => 'Add a professional grouping for documents and operational knowledge.', @@ -15,4 +16,5 @@
+ @endsection diff --git a/Modules/Documentation/Resources/views/categories/edit.blade.php b/Modules/Documentation/Resources/views/categories/edit.blade.php index 1abbaaff..8c7825b7 100644 --- a/Modules/Documentation/Resources/views/categories/edit.blade.php +++ b/Modules/Documentation/Resources/views/categories/edit.blade.php @@ -3,6 +3,7 @@ @section('title', 'Edit Category') @section('content') +
@include('documentation::partials.hero', [ 'title' => 'Edit category', 'subtitle' => 'Update category naming, ordering, visibility, and presentation metadata.', @@ -15,4 +16,5 @@
+ @endsection diff --git a/Modules/Documentation/Resources/views/categories/index.blade.php b/Modules/Documentation/Resources/views/categories/index.blade.php index 10a43602..f84cf2fb 100644 --- a/Modules/Documentation/Resources/views/categories/index.blade.php +++ b/Modules/Documentation/Resources/views/categories/index.blade.php @@ -3,6 +3,7 @@ @section('title', 'Document Categories') @section('content') +
@include('documentation::partials.hero', [ 'title' => 'Document categories', 'subtitle' => 'Organise documents by policy, process, onboarding, operational support, or technical area.', @@ -54,4 +55,5 @@
{{ $categories->links() }}
+ @endsection diff --git a/Modules/Documentation/Resources/views/documents/create.blade.php b/Modules/Documentation/Resources/views/documents/create.blade.php index cc6d574f..ec88891f 100644 --- a/Modules/Documentation/Resources/views/documents/create.blade.php +++ b/Modules/Documentation/Resources/views/documents/create.blade.php @@ -3,6 +3,7 @@ @section('title', 'Create Document') @section('content') +
@include('documentation::partials.hero', [ 'title' => 'Create document', 'subtitle' => 'Add a controlled document, guide, policy, SOP, upload, or operational note.', @@ -15,4 +16,5 @@
+ @endsection diff --git a/Modules/Documentation/Resources/views/documents/edit.blade.php b/Modules/Documentation/Resources/views/documents/edit.blade.php index 28b0bd98..edc8b528 100644 --- a/Modules/Documentation/Resources/views/documents/edit.blade.php +++ b/Modules/Documentation/Resources/views/documents/edit.blade.php @@ -3,6 +3,7 @@ @section('title', 'Edit Document') @section('content') +
@include('documentation::partials.hero', [ 'title' => 'Edit document', 'subtitle' => 'Maintain document metadata, workflow status, version and attachment details.', @@ -22,4 +23,5 @@ @method('DELETE') @endforeach +
@endsection diff --git a/Modules/Documentation/Resources/views/documents/index.blade.php b/Modules/Documentation/Resources/views/documents/index.blade.php index a30c84a4..a1766dfa 100644 --- a/Modules/Documentation/Resources/views/documents/index.blade.php +++ b/Modules/Documentation/Resources/views/documents/index.blade.php @@ -3,6 +3,7 @@ @section('title', 'Documentation') @section('content') +
@include('documentation::partials.hero', [ 'title' => 'Documentation Centre', 'subtitle' => 'Create, control, publish, and archive operational documents from a clean professional register.', @@ -89,4 +90,5 @@
{{ $documents->links() }}
+ @endsection diff --git a/Modules/Documentation/Resources/views/documents/show.blade.php b/Modules/Documentation/Resources/views/documents/show.blade.php index 9f2dbeb0..0419c493 100644 --- a/Modules/Documentation/Resources/views/documents/show.blade.php +++ b/Modules/Documentation/Resources/views/documents/show.blade.php @@ -3,6 +3,7 @@ @section('title', $document->title) @section('content') +
@include('documentation::partials.hero', [ 'title' => $document->title, 'subtitle' => $document->summary ?: 'Controlled documentation record with versioning, attachments and Markdown rendering.', @@ -50,4 +51,5 @@
@endif + @endsection diff --git a/Modules/Documentation/Resources/views/layouts/master.blade.php b/Modules/Documentation/Resources/views/layouts/master.blade.php index 8d46ba1e..269b689a 100644 --- a/Modules/Documentation/Resources/views/layouts/master.blade.php +++ b/Modules/Documentation/Resources/views/layouts/master.blade.php @@ -6,104 +6,536 @@ {{ trim($__env->yieldContent('title', 'Documentation')) }} - {{ config('app.name', 'Laravel') }} diff --git a/Modules/Documentation/Resources/views/partials/hero.blade.php b/Modules/Documentation/Resources/views/partials/hero.blade.php index 782c9c31..47f584bf 100644 --- a/Modules/Documentation/Resources/views/partials/hero.blade.php +++ b/Modules/Documentation/Resources/views/partials/hero.blade.php @@ -1,13 +1,17 @@ -
-
-

{{ $eyebrow ?? 'Documentation Suite' }}

-

{{ $title ?? 'Documentation' }}

-

{{ $subtitle ?? 'Manage controlled documents, policies, guides, files, and operational knowledge in one professional workspace.' }}

-
- Create document - Manage categories - Document register +
+
+
+
+

{{ $eyebrow ?? 'Documentation workflow' }}

+

{{ $title ?? 'Documentation' }}

+

{{ $subtitle ?? 'Manage controlled documents, policies, guides, files, and operational knowledge in one professional workspace.' }}

+
+ +
-
diff --git a/Modules/Documentation/Routes/.gitkeep b/Modules/Documentation/Routes/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Modules/Documentation/Routes/api.php b/Modules/Documentation/Routes/api.php deleted file mode 100644 index f8e55aef..00000000 --- a/Modules/Documentation/Routes/api.php +++ /dev/null @@ -1,11 +0,0 @@ -name('api.documentation.')->group(function (): void { - Route::get('/status', static fn (): array => [ - 'module' => 'documentation', - 'status' => 'ok', - 'version' => '1.1.0', - ])->name('status'); -}); diff --git a/Modules/Documentation/Routes/web.php b/Modules/Documentation/Routes/web.php deleted file mode 100644 index 246f75cd..00000000 --- a/Modules/Documentation/Routes/web.php +++ /dev/null @@ -1,23 +0,0 @@ -name('documentation.') - ->middleware(config('documentation.middleware', ['web', 'auth'])) - ->group(function (): void { - Route::get('/', [DocumentationController::class, 'index'])->name('documents.index'); - Route::get('/documents/create', [DocumentationController::class, 'create'])->name('documents.create'); - Route::post('/documents', [DocumentationController::class, 'store'])->name('documents.store'); - Route::get('/documents/{document}', [DocumentationController::class, 'show'])->name('documents.show'); - Route::get('/documents/{document}/edit', [DocumentationController::class, 'edit'])->name('documents.edit'); - Route::put('/documents/{document}', [DocumentationController::class, 'update'])->name('documents.update'); - Route::delete('/documents/{document}', [DocumentationController::class, 'destroy'])->name('documents.destroy'); - Route::get('/documents/{document}/download', [DocumentationController::class, 'download'])->name('documents.download'); - Route::get('/documents/{document}/attachments/{attachment}/download', [DocumentationController::class, 'downloadAttachment'])->name('documents.attachments.download'); - Route::delete('/documents/{document}/attachments/{attachment}', [DocumentationController::class, 'destroyAttachment'])->name('documents.attachments.destroy'); - - Route::resource('categories', DocumentationCategoryController::class)->except(['show']); - }); diff --git a/Modules/Documentation/Support/.gitkeep b/Modules/Documentation/Support/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Modules/Documentation/Support/ModuleInfo.php b/Modules/Documentation/Support/ModuleInfo.php deleted file mode 100644 index be7455e3..00000000 --- a/Modules/Documentation/Support/ModuleInfo.php +++ /dev/null @@ -1,26 +0,0 @@ - @endif - @if($documentationEnabled) - - - {{ __('Documentation') }} - - @endif - - @if(Route::has('documentation.documents.create')) - - - {{ __('New Document') }} - - @endif - - @if(Route::has('documentation.categories.index')) - - - {{ __('Categories') }} - - @endif - @if($helpdeskGitRoute) @@ -93,6 +84,27 @@ {{ __('Settings') }} @endif + + @if($documentationDashboardRoute) + + + {{ __('Documentation') }} + + @endif + + @if($documentationCreateRoute) + + + {{ __('New Document') }} + + @endif + + @if($documentationCategoriesRoute) + + + {{ __('Categories') }} + + @endif
@endif -{{-- Support Portal + Documentation end --}} +{{-- Support Portal end --}}