diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a006b9..3afc6da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,9 +20,21 @@ jobs: extensions: mbstring, sqlite3, pdo_sqlite coverage: none + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + - name: Install Composer dependencies run: composer install --no-interaction --prefer-dist + - name: Install NPM dependencies + run: npm ci + + - name: Build assets + run: npm run build + - name: Copy environment file run: cp .env.example .env diff --git a/app/Livewire/Concerns/HasVersionHistory.php b/app/Livewire/Concerns/HasVersionHistory.php new file mode 100644 index 0000000..e814105 --- /dev/null +++ b/app/Livewire/Concerns/HasVersionHistory.php @@ -0,0 +1,99 @@ +showVersionHistory = true; + $this->previewVersionId = null; + } + + public function closeVersionHistory(): void + { + $this->showVersionHistory = false; + $this->previewVersionId = null; + } + + public function selectVersion(string $versionId): void + { + $this->previewVersionId = $versionId; + } + + public function restoreVersion(string $versionId): void + { + $oldVersion = DocumentVersion::findOrFail($versionId); + $document = $this->document; + + if (! $document) { + return; + } + + // Verify version belongs to this document + if ($oldVersion->document_id !== $document->id) { + return; + } + + // Verify user has permission to update this project + $this->authorize('update', $document->project); + + // Wrap in transaction for atomicity + DB::transaction(function () use ($oldVersion, $document) { + // Create new version from old content + $newVersion = DocumentVersion::create([ + 'document_id' => $document->id, + 'created_by' => auth()->id(), + 'content_md' => $oldVersion->content_md, + 'summary' => 'Restored from '.$oldVersion->created_at->format('M j, Y g:i A'), + ]); + + // Update document to point to new version + $document->update(['current_version_id' => $newVersion->id]); + }); + + // Refresh UI state + $this->loadContent(); + unset($this->document); + unset($this->versions); + + $this->closeVersionHistory(); + + // Notify other components + $this->dispatch('docUpdated', type: $document->type->value); + $this->dispatch('version-restored', message: 'Version restored successfully'); + } + + #[Computed] + public function versions(): Collection + { + if (! $this->document) { + return collect(); + } + + return $this->document + ->versions() + ->with(['createdBy', 'planRun']) + ->orderByDesc('created_at') + ->get(); + } + + #[Computed] + public function selectedVersionForPreview(): ?DocumentVersion + { + if (! $this->previewVersionId) { + return null; + } + + return $this->versions->firstWhere('id', $this->previewVersionId); + } +} diff --git a/app/Livewire/Projects/Tabs/KanbanBoard.php b/app/Livewire/Projects/Tabs/KanbanBoard.php index 189d842..67552ba 100644 --- a/app/Livewire/Projects/Tabs/KanbanBoard.php +++ b/app/Livewire/Projects/Tabs/KanbanBoard.php @@ -7,7 +7,6 @@ use App\Models\Project; use App\Models\Task; use App\Models\TaskSet; -use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Filament\Actions\Concerns\InteractsWithActions; use Filament\Actions\Contracts\HasActions; use Filament\Actions\CreateAction; @@ -20,6 +19,7 @@ use Filament\Forms\Contracts\HasForms; use Filament\Infolists\Components\TextEntry; use Filament\Schemas\Schema; +use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Livewire\Attributes\Computed; use Livewire\Attributes\On; use Livewire\Component; diff --git a/app/Livewire/Projects/Tabs/Prd.php b/app/Livewire/Projects/Tabs/Prd.php index 3021aa8..13fd16a 100644 --- a/app/Livewire/Projects/Tabs/Prd.php +++ b/app/Livewire/Projects/Tabs/Prd.php @@ -3,6 +3,7 @@ namespace App\Livewire\Projects\Tabs; use App\Enums\DocumentType; +use App\Livewire\Concerns\HasVersionHistory; use App\Models\Document; use App\Models\DocumentVersion; use App\Models\Project; @@ -14,6 +15,7 @@ class Prd extends Component { use AuthorizesRequests; + use HasVersionHistory; public string $projectId; diff --git a/app/Livewire/Projects/Tabs/Tech.php b/app/Livewire/Projects/Tabs/Tech.php index 207859e..add3232 100644 --- a/app/Livewire/Projects/Tabs/Tech.php +++ b/app/Livewire/Projects/Tabs/Tech.php @@ -5,11 +5,12 @@ use App\Actions\GenerateTasksFromTechSpec; use App\Enums\DocumentType; use App\Enums\PlanRunStepStatus; +use App\Livewire\Concerns\HasVersionHistory; use App\Models\Document; use App\Models\DocumentVersion; use App\Models\Project; -use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use App\Models\TaskSet; +use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Livewire\Attributes\Computed; use Livewire\Attributes\On; use Livewire\Component; @@ -17,6 +18,7 @@ class Tech extends Component { use AuthorizesRequests; + use HasVersionHistory; public string $projectId; diff --git a/resources/views/components/version-history-slide-over.blade.php b/resources/views/components/version-history-slide-over.blade.php new file mode 100644 index 0000000..3577e71 --- /dev/null +++ b/resources/views/components/version-history-slide-over.blade.php @@ -0,0 +1,215 @@ +@props([ + 'versions', + 'selectedVersion' => null, + 'previewVersionId' => null, + 'currentVersionId' => null, +]) + +
+ {{-- Backdrop --}} +
+ + {{-- Slide-over panel --}} +
+
+
+ {{-- Header --}} +
+
+
+ + + +

Version History

+
+ +
+
+ + {{-- Content: Two-column layout --}} +
+ {{-- Left: Version list --}} +
+ @if($versions->isEmpty()) +
+ + + +

No versions yet

+
+ @else +
+ @foreach($versions as $version) + + @endforeach +
+ @endif +
+ + {{-- Right: Preview & Restore --}} +
+ @if($selectedVersion) + {{-- Preview Header --}} +
+
+

+ Version from {{ $selectedVersion->created_at->format('M j, Y') }} at {{ $selectedVersion->created_at->format('g:i A') }} +

+

+ @if($selectedVersion->created_by && $selectedVersion->createdBy) + By {{ $selectedVersion->createdBy->name }} + @elseif($selectedVersion->plan_run_id) + AI Generated + @endif +

+
+ + @if($selectedVersion->id !== $currentVersionId) + + @else + + Current Version + + @endif +
+ + {{-- Preview Content --}} +
+
{{ $selectedVersion->content_md }}
+
+ @else + {{-- Empty state --}} +
+
+ + + + +

Select a version to preview

+
+
+ @endif +
+
+
+
+
+
+ +{{-- Toast notification --}} +
+ + + +

+
diff --git a/resources/views/livewire/projects/tabs/prd.blade.php b/resources/views/livewire/projects/tabs/prd.blade.php index ecae9d7..66e1c7c 100644 --- a/resources/views/livewire/projects/tabs/prd.blade.php +++ b/resources/views/livewire/projects/tabs/prd.blade.php @@ -8,6 +8,20 @@ @if($isDirty) Unsaved changes @endif + + {{-- Version History Button --}} + @if($this->document) + + @endif + + @endif +