comlete story 2.2 with qa test & updated architect files
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
# Libra - Coding Standards
|
||||
|
||||
> Extracted from `docs/architecture.md` Section 20
|
||||
|
||||
## Critical Rules
|
||||
|
||||
| Rule | Description |
|
||||
|------|-------------|
|
||||
| **Volt Pattern** | Use class-based Volt components (per CLAUDE.md) |
|
||||
| **Flux UI** | Use Flux components when available |
|
||||
| **Form Validation** | Always use Form Request or Livewire form objects |
|
||||
| **Eloquent** | Prefer `Model::query()` over `DB::` facade |
|
||||
| **Actions** | Use single-purpose Action classes for business logic |
|
||||
| **Testing** | Every feature must have corresponding Pest tests |
|
||||
| **Pint** | Run `vendor/bin/pint --dirty` before committing |
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
| Element | Convention | Example |
|
||||
|---------|------------|---------|
|
||||
| Models | Singular PascalCase | `Consultation` |
|
||||
| Tables | Plural snake_case | `consultations` |
|
||||
| Columns | snake_case | `booking_date` |
|
||||
| Controllers | PascalCase + Controller | `ConsultationController` |
|
||||
| Actions | Verb + Noun + Action | `CreateConsultationAction` |
|
||||
| Jobs | Verb + Noun | `SendBookingNotification` |
|
||||
| Events | Past tense | `ConsultationApproved` |
|
||||
| Listeners | Verb phrase | `SendApprovalNotification` |
|
||||
| Volt Components | kebab-case path | `admin/consultations/index` |
|
||||
| Enums | PascalCase | `ConsultationStatus` |
|
||||
| Enum Cases | PascalCase | `NoShow` |
|
||||
| Traits | Adjective or -able | `HasConsultations`, `Bookable` |
|
||||
| Interfaces | Adjective or -able | `Exportable` |
|
||||
|
||||
## Volt Component Template
|
||||
|
||||
```php
|
||||
<?php
|
||||
// resources/views/livewire/admin/consultations/index.blade.php
|
||||
|
||||
use App\Models\Consultation;
|
||||
use App\Enums\ConsultationStatus;
|
||||
use Livewire\Volt\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
new class extends Component {
|
||||
use WithPagination;
|
||||
|
||||
public string $search = '';
|
||||
public string $status = '';
|
||||
|
||||
public function updatedSearch(): void
|
||||
{
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function updatedStatus(): void
|
||||
{
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function with(): array
|
||||
{
|
||||
return [
|
||||
'consultations' => Consultation::query()
|
||||
->with('user:id,full_name,email')
|
||||
->when($this->search, fn ($q) => $q->whereHas('user', fn ($q) =>
|
||||
$q->where('full_name', 'like', "%{$this->search}%")
|
||||
))
|
||||
->when($this->status, fn ($q) => $q->where('status', $this->status))
|
||||
->orderBy('booking_date', 'desc')
|
||||
->paginate(15),
|
||||
'statuses' => ConsultationStatus::cases(),
|
||||
];
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div>
|
||||
<x-slot name="header">
|
||||
<flux:heading size="xl">{{ __('models.consultations') }}</flux:heading>
|
||||
</x-slot>
|
||||
|
||||
<div class="flex gap-4 mb-6">
|
||||
<flux:input
|
||||
wire:model.live.debounce.300ms="search"
|
||||
placeholder="{{ __('messages.search') }}"
|
||||
icon="magnifying-glass"
|
||||
/>
|
||||
|
||||
<flux:select wire:model.live="status">
|
||||
<option value="">{{ __('messages.all_statuses') }}</option>
|
||||
@foreach($statuses as $s)
|
||||
<option value="{{ $s->value }}">{{ $s->label() }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
@forelse($consultations as $consultation)
|
||||
<x-ui.card wire:key="consultation-{{ $consultation->id }}">
|
||||
{{-- Card content --}}
|
||||
</x-ui.card>
|
||||
@empty
|
||||
<x-ui.empty-state :message="__('messages.no_consultations')" />
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
{{ $consultations->links() }}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Action Class Template
|
||||
|
||||
```php
|
||||
<?php
|
||||
// app/Actions/Consultation/CreateConsultationAction.php
|
||||
|
||||
namespace App\Actions\Consultation;
|
||||
|
||||
use App\Models\Consultation;
|
||||
use App\Models\User;
|
||||
use App\Enums\ConsultationStatus;
|
||||
use App\Enums\PaymentStatus;
|
||||
use App\Jobs\SendBookingNotification;
|
||||
use App\Exceptions\BookingLimitExceededException;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class CreateConsultationAction
|
||||
{
|
||||
public function execute(User $user, array $data): Consultation
|
||||
{
|
||||
// Validate booking limit
|
||||
if ($user->hasBookingOnDate($data['booking_date'])) {
|
||||
throw new BookingLimitExceededException(
|
||||
__('messages.booking_limit_exceeded')
|
||||
);
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($user, $data) {
|
||||
$consultation = Consultation::create([
|
||||
'user_id' => $user->id,
|
||||
'booking_date' => $data['booking_date'],
|
||||
'booking_time' => $data['booking_time'],
|
||||
'problem_summary' => $data['problem_summary'],
|
||||
'status' => ConsultationStatus::Pending,
|
||||
'payment_status' => PaymentStatus::NotApplicable,
|
||||
]);
|
||||
|
||||
SendBookingNotification::dispatch($consultation);
|
||||
|
||||
return $consultation;
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Standards
|
||||
|
||||
- Use **Pest 4** with `Volt::test()` for Livewire components
|
||||
- Every feature must have corresponding tests
|
||||
- Use factories with custom states when creating test models
|
||||
- Follow existing test structure in `tests/Feature/` and `tests/Unit/`
|
||||
|
||||
### Test Example
|
||||
|
||||
```php
|
||||
use Livewire\Volt\Volt;
|
||||
|
||||
test('consultation list can be filtered by status', function () {
|
||||
$user = User::factory()->admin()->create();
|
||||
|
||||
Volt::test('admin.consultations.index')
|
||||
->actingAs($user)
|
||||
->set('status', ConsultationStatus::Pending->value)
|
||||
->assertHasNoErrors();
|
||||
});
|
||||
```
|
||||
|
||||
## Bilingual Support
|
||||
|
||||
- All user-facing strings must use Laravel's `__()` helper
|
||||
- Translations stored in `resources/lang/{ar,en}/`
|
||||
- Use JSON translations for simple strings, PHP arrays for structured data
|
||||
- RTL layout handled via Tailwind CSS classes
|
||||
Reference in New Issue
Block a user