added epic and stories for potential clients
This commit is contained in:
parent
31a4a47849
commit
5803410584
|
|
@ -0,0 +1,132 @@
|
|||
# Epic 15: Potential Clients Management
|
||||
|
||||
## Epic Goal
|
||||
|
||||
Provide the admin with a simple lead tracking system to manage prospective clients (individuals, companies, agencies) who don't yet have accounts, enabling better organization of business development targets.
|
||||
|
||||
## Epic Description
|
||||
|
||||
### Existing System Context
|
||||
|
||||
- **Current functionality:** User management system supports admin, individual clients, and company clients with full account capabilities
|
||||
- **Technology stack:** Laravel 12, Livewire 3/Volt, Flux UI, existing admin dashboard patterns
|
||||
- **Integration points:** Admin dashboard navigation, existing list/filter patterns from client management
|
||||
- **Current constraint:** No system for tracking prospects without creating full user accounts
|
||||
|
||||
### Enhancement Details
|
||||
|
||||
**What's being added:**
|
||||
- New `PotentialClient` model and database table (completely separate from `users`)
|
||||
- New enum `PotentialClientType` with values: individual, company, agency
|
||||
- Admin-only CRUD interface at `/admin/potential-clients`
|
||||
- Filterable list view by client type
|
||||
- Contact information storage (all optional): phone, email, address, social media, website
|
||||
- Notes field for admin remarks
|
||||
|
||||
**How it integrates:**
|
||||
- New menu item in admin sidebar navigation
|
||||
- Follows existing admin interface patterns (Volt components, Flux UI)
|
||||
- Standalone feature - no impact on existing user/client functionality
|
||||
- Uses existing admin middleware and layout
|
||||
|
||||
**Success criteria:**
|
||||
- Admin can create, view, edit, and delete potential clients
|
||||
- Admin can filter list by type (individual, company, agency)
|
||||
- All contact fields are optional (flexible data entry)
|
||||
- Interface matches existing admin dashboard aesthetics
|
||||
- Bilingual support (Arabic/English)
|
||||
|
||||
---
|
||||
|
||||
## Stories
|
||||
|
||||
### Story 15.1: Database Schema & Model Setup
|
||||
Create the `potential_clients` database table, `PotentialClient` model, `PotentialClientType` enum, and factory for testing.
|
||||
|
||||
### Story 15.2: Potential Clients List with Type Filter
|
||||
Create the admin list view at `/admin/potential-clients` with type filtering and pagination.
|
||||
|
||||
### Story 15.3: CRUD Operations for Potential Clients
|
||||
Implement create, view, edit, and delete functionality for potential clients.
|
||||
|
||||
---
|
||||
|
||||
## Compatibility Requirements
|
||||
|
||||
- [x] Existing user/client system remains unchanged
|
||||
- [x] Existing APIs remain unchanged
|
||||
- [x] No changes to existing database tables
|
||||
- [x] UI follows existing admin dashboard patterns (Flux UI, Volt)
|
||||
- [x] No impact on client-facing functionality
|
||||
|
||||
## Technical Considerations
|
||||
|
||||
### Database Schema
|
||||
|
||||
```
|
||||
potential_clients table:
|
||||
- id: bigint (primary key)
|
||||
- type: varchar (enum: individual, company, agency)
|
||||
- name: varchar(255) NULLABLE
|
||||
- phone: varchar(50) NULLABLE
|
||||
- email: varchar(255) NULLABLE
|
||||
- address: text NULLABLE
|
||||
- social_media: varchar(255) NULLABLE
|
||||
- website: varchar(255) NULLABLE
|
||||
- notes: text NULLABLE
|
||||
- created_at: timestamp
|
||||
- updated_at: timestamp
|
||||
```
|
||||
|
||||
### New Enum
|
||||
|
||||
```php
|
||||
// App\Enums\PotentialClientType
|
||||
enum PotentialClientType: string
|
||||
{
|
||||
case Individual = 'individual';
|
||||
case Company = 'company';
|
||||
case Agency = 'agency';
|
||||
}
|
||||
```
|
||||
|
||||
### Routes
|
||||
|
||||
```php
|
||||
// Admin routes (admin middleware)
|
||||
Route::prefix('admin/potential-clients')->group(function () {
|
||||
Route::get('/', PotentialClientsList::class)->name('admin.potential-clients.index');
|
||||
Route::get('/create', PotentialClientCreate::class)->name('admin.potential-clients.create');
|
||||
Route::get('/{potentialClient}', PotentialClientShow::class)->name('admin.potential-clients.show');
|
||||
Route::get('/{potentialClient}/edit', PotentialClientEdit::class)->name('admin.potential-clients.edit');
|
||||
});
|
||||
```
|
||||
|
||||
### Translation Keys
|
||||
|
||||
```php
|
||||
// potential-clients translations needed
|
||||
'potential_clients' => 'Potential Clients' / 'العملاء المحتملون'
|
||||
'type' => 'Type' / 'النوع'
|
||||
'individual' => 'Individual' / 'فرد'
|
||||
'company' => 'Company' / 'شركة'
|
||||
'agency' => 'Agency' / 'وكالة'
|
||||
// ... additional field labels
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Risk Mitigation
|
||||
|
||||
- **Primary Risk:** Feature scope creep (adding status tracking, conversion flow, etc.)
|
||||
- **Mitigation:** Strict adherence to simple contact list requirements; defer enhancements to future epics
|
||||
- **Rollback Plan:** Drop `potential_clients` table and remove routes/components
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- [ ] All stories completed with acceptance criteria met
|
||||
- [ ] Tests cover CRUD operations and filtering
|
||||
- [ ] Admin can manage potential clients through new interface
|
||||
- [ ] No regression in existing features
|
||||
- [ ] Bilingual support (Arabic/English) for all UI elements
|
||||
- [ ] Admin navigation updated with new menu item
|
||||
|
|
@ -30,8 +30,10 @@ This document provides an index of all epics for the Libra Law Firm platform dev
|
|||
| 11 | [Guest Booking](./epic-11-guest-booking.md) | 4 | High | Epic 3 |
|
||||
| 12 | [Branding Refresh - Logo & Colors](./epic-12-branding-refresh.md) | 6 | High | Epic 9, 10 |
|
||||
| 13 | [Auth Page Design Enhancement](./epic-13-auth-page-design.md) | 5 | Medium | Epic 12 |
|
||||
| 14 | [Home Page Redesign](./epic-14-home-page-redesign.md) | 6 | Medium | Epic 12 |
|
||||
| 15 | [Potential Clients Management](./epic-15-potential-clients.md) | 3 | Medium | Epic 6 |
|
||||
|
||||
**Total Stories:** 83
|
||||
**Total Stories:** 92
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -71,6 +73,7 @@ Epic 1 (Core Foundation)
|
|||
│ │ └── Epic 11 (Guest Booking)
|
||||
│ └── Epic 4 (Timeline)
|
||||
│ └── Epic 6 (Admin Dashboard)
|
||||
│ │ └── Epic 15 (Potential Clients)
|
||||
│ └── Epic 7 (Client Dashboard)
|
||||
│ └── Epic 8 (Email)
|
||||
├── Epic 5 (Posts)
|
||||
|
|
@ -78,6 +81,7 @@ Epic 1 (Core Foundation)
|
|||
└── Epic 10 (Brand Color Refresh)
|
||||
└── Epic 12 (Branding Refresh)
|
||||
└── Epic 13 (Auth Page Design)
|
||||
└── Epic 14 (Home Page Redesign)
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -0,0 +1,266 @@
|
|||
# Story 15.1: Database Schema & Model Setup
|
||||
|
||||
## Story
|
||||
|
||||
**As an** admin
|
||||
**I want to** have a database structure for potential clients
|
||||
**So that** I can store and manage prospective client information
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
### AC1: Database Migration
|
||||
|
||||
**Given** the system needs to store potential clients
|
||||
**When** the migration runs
|
||||
**Then** a `potential_clients` table is created with:
|
||||
- `id` - bigint, primary key, auto-increment
|
||||
- `type` - varchar, stores potential client type (individual, company, agency)
|
||||
- `name` - varchar(255), nullable
|
||||
- `phone` - varchar(50), nullable
|
||||
- `email` - varchar(255), nullable
|
||||
- `address` - text, nullable
|
||||
- `social_media` - varchar(255), nullable
|
||||
- `website` - varchar(255), nullable
|
||||
- `notes` - text, nullable
|
||||
- `created_at` - timestamp
|
||||
- `updated_at` - timestamp
|
||||
|
||||
### AC2: PotentialClientType Enum
|
||||
|
||||
**Given** potential clients have different types
|
||||
**When** the enum is created
|
||||
**Then** `App\Enums\PotentialClientType` contains:
|
||||
- `Individual` = 'individual'
|
||||
- `Company` = 'company'
|
||||
- `Agency` = 'agency'
|
||||
|
||||
And includes a `label()` method returning translated labels.
|
||||
|
||||
### AC3: PotentialClient Model
|
||||
|
||||
**Given** the migration exists
|
||||
**When** the model is created
|
||||
**Then** `App\Models\PotentialClient` includes:
|
||||
- Proper `$fillable` array for all fields
|
||||
- Cast `type` to `PotentialClientType` enum
|
||||
- No relationships required (standalone model)
|
||||
|
||||
### AC4: Model Factory
|
||||
|
||||
**Given** tests need potential client data
|
||||
**When** the factory is created
|
||||
**Then** `Database\Factories\PotentialClientFactory` generates:
|
||||
- Random type from enum values
|
||||
- Realistic fake data for all fields
|
||||
- State methods: `individual()`, `company()`, `agency()`
|
||||
|
||||
### AC5: Translation Keys
|
||||
|
||||
**Given** the system is bilingual
|
||||
**When** translations are added
|
||||
**Then** create keys in `lang/en/potential-clients.php` and `lang/ar/potential-clients.php`:
|
||||
- Model name (singular/plural)
|
||||
- Enum type labels
|
||||
- All field labels
|
||||
|
||||
## Technical Notes
|
||||
|
||||
### Files to Create
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `database/migrations/xxxx_create_potential_clients_table.php` | Database schema |
|
||||
| `app/Enums/PotentialClientType.php` | Type enum |
|
||||
| `app/Models/PotentialClient.php` | Eloquent model |
|
||||
| `database/factories/PotentialClientFactory.php` | Test factory |
|
||||
| `lang/en/potential-clients.php` | English translations |
|
||||
| `lang/ar/potential-clients.php` | Arabic translations |
|
||||
|
||||
### Migration Schema
|
||||
|
||||
```php
|
||||
Schema::create('potential_clients', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('type');
|
||||
$table->string('name')->nullable();
|
||||
$table->string('phone', 50)->nullable();
|
||||
$table->string('email')->nullable();
|
||||
$table->text('address')->nullable();
|
||||
$table->string('social_media')->nullable();
|
||||
$table->string('website')->nullable();
|
||||
$table->text('notes')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
```
|
||||
|
||||
### Enum Implementation
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum PotentialClientType: string
|
||||
{
|
||||
case Individual = 'individual';
|
||||
case Company = 'company';
|
||||
case Agency = 'agency';
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return match($this) {
|
||||
self::Individual => __('potential-clients.types.individual'),
|
||||
self::Company => __('potential-clients.types.company'),
|
||||
self::Agency => __('potential-clients.types.agency'),
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Model Implementation
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\PotentialClientType;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PotentialClient extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'type',
|
||||
'name',
|
||||
'phone',
|
||||
'email',
|
||||
'address',
|
||||
'social_media',
|
||||
'website',
|
||||
'notes',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'type' => PotentialClientType::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Factory Implementation
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Enums\PotentialClientType;
|
||||
use App\Models\PotentialClient;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class PotentialClientFactory extends Factory
|
||||
{
|
||||
protected $model = PotentialClient::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'type' => fake()->randomElement(PotentialClientType::cases()),
|
||||
'name' => fake()->name(),
|
||||
'phone' => fake()->phoneNumber(),
|
||||
'email' => fake()->safeEmail(),
|
||||
'address' => fake()->address(),
|
||||
'social_media' => fake()->url(),
|
||||
'website' => fake()->url(),
|
||||
'notes' => fake()->optional()->sentence(),
|
||||
];
|
||||
}
|
||||
|
||||
public function individual(): static
|
||||
{
|
||||
return $this->state(['type' => PotentialClientType::Individual]);
|
||||
}
|
||||
|
||||
public function company(): static
|
||||
{
|
||||
return $this->state(['type' => PotentialClientType::Company]);
|
||||
}
|
||||
|
||||
public function agency(): static
|
||||
{
|
||||
return $this->state(['type' => PotentialClientType::Agency]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Translation Files
|
||||
|
||||
```php
|
||||
// lang/en/potential-clients.php
|
||||
return [
|
||||
'title' => 'Potential Clients',
|
||||
'singular' => 'Potential Client',
|
||||
'types' => [
|
||||
'individual' => 'Individual',
|
||||
'company' => 'Company',
|
||||
'agency' => 'Agency',
|
||||
],
|
||||
'fields' => [
|
||||
'type' => 'Type',
|
||||
'name' => 'Name',
|
||||
'phone' => 'Phone',
|
||||
'email' => 'Email',
|
||||
'address' => 'Address',
|
||||
'social_media' => 'Social Media',
|
||||
'website' => 'Website',
|
||||
'notes' => 'Notes',
|
||||
],
|
||||
];
|
||||
|
||||
// lang/ar/potential-clients.php
|
||||
return [
|
||||
'title' => 'العملاء المحتملون',
|
||||
'singular' => 'عميل محتمل',
|
||||
'types' => [
|
||||
'individual' => 'فرد',
|
||||
'company' => 'شركة',
|
||||
'agency' => 'وكالة',
|
||||
],
|
||||
'fields' => [
|
||||
'type' => 'النوع',
|
||||
'name' => 'الاسم',
|
||||
'phone' => 'الهاتف',
|
||||
'email' => 'البريد الإلكتروني',
|
||||
'address' => 'العنوان',
|
||||
'social_media' => 'وسائل التواصل',
|
||||
'website' => 'الموقع الإلكتروني',
|
||||
'notes' => 'ملاحظات',
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
## Dev Checklist
|
||||
|
||||
- [ ] Create migration file with proper schema
|
||||
- [ ] Run migration successfully
|
||||
- [ ] Create `PotentialClientType` enum with `label()` method
|
||||
- [ ] Create `PotentialClient` model with fillable and casts
|
||||
- [ ] Create factory with all state methods
|
||||
- [ ] Create English translations
|
||||
- [ ] Create Arabic translations
|
||||
- [ ] Write model unit tests
|
||||
- [ ] Verify factory generates valid data
|
||||
|
||||
## Estimation
|
||||
|
||||
**Complexity:** Low
|
||||
**Risk:** Low - Standard model/migration setup
|
||||
|
||||
## Dependencies
|
||||
|
||||
- None (foundation for other stories in this epic)
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
# Story 15.2: Potential Clients List with Type Filter
|
||||
|
||||
## Story
|
||||
|
||||
**As an** admin
|
||||
**I want to** view a list of potential clients with filtering capabilities
|
||||
**So that** I can easily browse and find specific prospective clients
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
### AC1: Admin Navigation
|
||||
|
||||
**Given** the admin is logged in
|
||||
**When** viewing the admin sidebar
|
||||
**Then** a "Potential Clients" menu item is visible with an appropriate icon
|
||||
**And** clicking it navigates to `/admin/potential-clients`
|
||||
|
||||
### AC2: List Page Header
|
||||
|
||||
**Given** the admin navigates to `/admin/potential-clients`
|
||||
**When** the page loads
|
||||
**Then** display:
|
||||
- Page title: "Potential Clients" / "العملاء المحتملون"
|
||||
- Subtitle: "Manage prospective clients" / "إدارة العملاء المحتملين"
|
||||
- "Add Potential Client" button linking to create page
|
||||
|
||||
### AC3: Type Filter
|
||||
|
||||
**Given** the potential clients list
|
||||
**When** the filter is displayed
|
||||
**Then** a type dropdown includes:
|
||||
- "All Types" (default) / "جميع الأنواع"
|
||||
- "Individual" / "فرد"
|
||||
- "Company" / "شركة"
|
||||
- "Agency" / "وكالة"
|
||||
|
||||
**When** a type is selected
|
||||
**Then** the list filters to show only potential clients of that type
|
||||
**And** pagination resets to page 1
|
||||
|
||||
### AC4: Search Functionality
|
||||
|
||||
**Given** the potential clients list
|
||||
**When** the admin types in the search box
|
||||
**Then** filter potential clients by name, email, or phone
|
||||
**And** search is debounced (300ms delay)
|
||||
**And** pagination resets to page 1
|
||||
|
||||
### AC5: List Table Display
|
||||
|
||||
**Given** potential clients exist
|
||||
**When** viewing the list
|
||||
**Then** display a table with columns:
|
||||
- Name (with avatar placeholder if no name)
|
||||
- Type (with badge color per type)
|
||||
- Email
|
||||
- Phone
|
||||
- Created date
|
||||
- Actions (view, edit, delete icons)
|
||||
|
||||
### AC6: Type Badges
|
||||
|
||||
**Given** potential clients in the list
|
||||
**When** displaying the type
|
||||
**Then** show colored badges:
|
||||
- Individual: Blue badge
|
||||
- Company: Purple badge
|
||||
- Agency: Amber badge
|
||||
|
||||
### AC7: Empty State
|
||||
|
||||
**Given** no potential clients exist
|
||||
**When** viewing the list
|
||||
**Then** display an empty state with:
|
||||
- Icon (user-group or similar)
|
||||
- Message: "No potential clients found" / "لم يتم العثور على عملاء محتملون"
|
||||
- "Add Potential Client" button
|
||||
|
||||
**Given** filters are active but no results match
|
||||
**When** viewing the list
|
||||
**Then** display:
|
||||
- Message: "No potential clients match your filters" / "لا يوجد عملاء محتملون يطابقون الفلاتر"
|
||||
- "Clear filters" button
|
||||
|
||||
### AC8: Pagination
|
||||
|
||||
**Given** more than 10 potential clients exist
|
||||
**When** viewing the list
|
||||
**Then** display pagination controls
|
||||
**And** allow changing items per page (10, 25, 50)
|
||||
|
||||
### AC9: Clear Filters
|
||||
|
||||
**Given** search or type filter is active
|
||||
**When** admin clicks "Clear filters"
|
||||
**Then** reset all filters to defaults
|
||||
**And** show full unfiltered list
|
||||
|
||||
### AC10: RTL Support
|
||||
|
||||
**Given** Arabic language is selected
|
||||
**When** viewing the potential clients list
|
||||
**Then** all text and layout align correctly right-to-left
|
||||
|
||||
## Technical Notes
|
||||
|
||||
### Files to Create
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `resources/views/livewire/admin/potential-clients/index.blade.php` | List Volt component |
|
||||
| `routes/web.php` | Add route for potential clients |
|
||||
|
||||
### Files to Modify
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `resources/views/components/layouts/admin/sidebar.blade.php` | Add navigation item |
|
||||
| `lang/en/potential-clients.php` | Add list-related translations |
|
||||
| `lang/ar/potential-clients.php` | Add list-related translations |
|
||||
|
||||
### Route Definition
|
||||
|
||||
```php
|
||||
// In routes/web.php, within admin middleware group
|
||||
Route::get('/admin/potential-clients', \App\Livewire\Admin\PotentialClients\Index::class)
|
||||
->name('admin.potential-clients.index');
|
||||
```
|
||||
|
||||
Or using Volt/Folio pattern based on existing structure.
|
||||
|
||||
### Component Structure
|
||||
|
||||
Follow the existing pattern from `admin/clients/individual/index.blade.php`:
|
||||
- Use `WithPagination` trait
|
||||
- Properties: `$search`, `$typeFilter`, `$perPage`
|
||||
- Methods: `updatedSearch()`, `updatedTypeFilter()`, `updatedPerPage()`, `clearFilters()`
|
||||
- `with()` method returning paginated query with filters
|
||||
|
||||
### Badge Colors by Type
|
||||
|
||||
```blade
|
||||
@switch($potentialClient->type)
|
||||
@case(PotentialClientType::Individual)
|
||||
<flux:badge color="blue" size="sm">{{ $potentialClient->type->label() }}</flux:badge>
|
||||
@break
|
||||
@case(PotentialClientType::Company)
|
||||
<flux:badge color="purple" size="sm">{{ $potentialClient->type->label() }}</flux:badge>
|
||||
@break
|
||||
@case(PotentialClientType::Agency)
|
||||
<flux:badge color="amber" size="sm">{{ $potentialClient->type->label() }}</flux:badge>
|
||||
@break
|
||||
@endswitch
|
||||
```
|
||||
|
||||
### Additional Translations
|
||||
|
||||
```php
|
||||
// Add to lang/en/potential-clients.php
|
||||
'subtitle' => 'Manage prospective clients',
|
||||
'add_potential_client' => 'Add Potential Client',
|
||||
'all_types' => 'All Types',
|
||||
'search_placeholder' => 'Search by name, email, or phone...',
|
||||
'no_potential_clients_found' => 'No potential clients found',
|
||||
'no_potential_clients_match' => 'No potential clients match your filters',
|
||||
'clear_filters' => 'Clear filters',
|
||||
'per_page' => 'per page',
|
||||
'actions' => 'Actions',
|
||||
'view' => 'View',
|
||||
'edit' => 'Edit',
|
||||
'delete' => 'Delete',
|
||||
'created_at' => 'Created',
|
||||
|
||||
// Add to lang/ar/potential-clients.php
|
||||
'subtitle' => 'إدارة العملاء المحتملين',
|
||||
'add_potential_client' => 'إضافة عميل محتمل',
|
||||
'all_types' => 'جميع الأنواع',
|
||||
'search_placeholder' => 'البحث بالاسم أو البريد أو الهاتف...',
|
||||
'no_potential_clients_found' => 'لم يتم العثور على عملاء محتملون',
|
||||
'no_potential_clients_match' => 'لا يوجد عملاء محتملون يطابقون الفلاتر',
|
||||
'clear_filters' => 'مسح الفلاتر',
|
||||
'per_page' => 'لكل صفحة',
|
||||
'actions' => 'الإجراءات',
|
||||
'view' => 'عرض',
|
||||
'edit' => 'تعديل',
|
||||
'delete' => 'حذف',
|
||||
'created_at' => 'تاريخ الإنشاء',
|
||||
```
|
||||
|
||||
## Dev Checklist
|
||||
|
||||
- [ ] Add route for potential clients index
|
||||
- [ ] Create Volt component `admin/potential-clients/index.blade.php`
|
||||
- [ ] Implement search functionality with debounce
|
||||
- [ ] Implement type filter dropdown
|
||||
- [ ] Implement pagination with per-page selector
|
||||
- [ ] Add navigation item to admin sidebar
|
||||
- [ ] Style type badges with correct colors
|
||||
- [ ] Handle empty state (no data)
|
||||
- [ ] Handle empty state (no filter matches)
|
||||
- [ ] Add English translations
|
||||
- [ ] Add Arabic translations
|
||||
- [ ] Test RTL layout
|
||||
- [ ] Write feature tests for list and filtering
|
||||
|
||||
## Estimation
|
||||
|
||||
**Complexity:** Medium
|
||||
**Risk:** Low - Follows established patterns
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Story 15.1 (Database & Model) must be completed first
|
||||
|
|
@ -0,0 +1,324 @@
|
|||
# Story 15.3: CRUD Operations for Potential Clients
|
||||
|
||||
## Story
|
||||
|
||||
**As an** admin
|
||||
**I want to** create, view, edit, and delete potential clients
|
||||
**So that** I can fully manage my prospective client records
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
### AC1: Create Form Access
|
||||
|
||||
**Given** the admin is on the potential clients list
|
||||
**When** clicking "Add Potential Client"
|
||||
**Then** navigate to `/admin/potential-clients/create`
|
||||
**And** display the create form
|
||||
|
||||
### AC2: Create Form Fields
|
||||
|
||||
**Given** the admin is on the create form
|
||||
**When** the form is displayed
|
||||
**Then** show the following fields:
|
||||
- Type (required) - dropdown with Individual, Company, Agency
|
||||
- Name (optional) - text input
|
||||
- Phone (optional) - text input
|
||||
- Email (optional) - email input
|
||||
- Address (optional) - textarea
|
||||
- Social Media (optional) - text input (URL or handle)
|
||||
- Website (optional) - text input (URL)
|
||||
- Notes (optional) - textarea
|
||||
|
||||
### AC3: Create Submission
|
||||
|
||||
**Given** the admin fills the create form
|
||||
**When** submitting with valid data
|
||||
**Then** create a new potential client record
|
||||
**And** show success message: "Potential client created successfully" / "تم إنشاء العميل المحتمل بنجاح"
|
||||
**And** redirect to potential clients list
|
||||
|
||||
### AC4: Create Validation
|
||||
|
||||
**Given** the admin submits the create form
|
||||
**When** type is not selected
|
||||
**Then** show validation error: "Please select a type" / "يرجى اختيار النوع"
|
||||
|
||||
**When** email is provided but invalid format
|
||||
**Then** show validation error on email field
|
||||
|
||||
**When** website is provided but invalid URL format
|
||||
**Then** show validation error on website field
|
||||
|
||||
### AC5: View Page
|
||||
|
||||
**Given** a potential client exists
|
||||
**When** the admin clicks the view icon from the list
|
||||
**Then** navigate to `/admin/potential-clients/{id}`
|
||||
**And** display all potential client information in a clean layout
|
||||
**And** show "Edit" and "Delete" action buttons
|
||||
**And** show "Back to Potential Clients" navigation
|
||||
|
||||
### AC6: View Page Display
|
||||
|
||||
**Given** the admin is viewing a potential client
|
||||
**When** the page loads
|
||||
**Then** display:
|
||||
- Type with colored badge
|
||||
- All contact information (showing "Not provided" / "غير متوفر" for empty fields)
|
||||
- Notes section (if provided)
|
||||
- Created date
|
||||
- Last updated date
|
||||
|
||||
### AC7: Edit Form Access
|
||||
|
||||
**Given** the admin is viewing a potential client
|
||||
**When** clicking "Edit"
|
||||
**Then** navigate to `/admin/potential-clients/{id}/edit`
|
||||
**And** pre-populate form with existing data
|
||||
|
||||
**Given** the admin is on the list page
|
||||
**When** clicking the edit icon
|
||||
**Then** navigate directly to the edit form
|
||||
|
||||
### AC8: Edit Form Submission
|
||||
|
||||
**Given** the admin modifies the edit form
|
||||
**When** submitting with valid data
|
||||
**Then** update the potential client record
|
||||
**And** show success message: "Potential client updated successfully" / "تم تحديث العميل المحتمل بنجاح"
|
||||
**And** redirect to the view page
|
||||
|
||||
### AC9: Delete Confirmation
|
||||
|
||||
**Given** the admin is viewing a potential client
|
||||
**When** clicking "Delete"
|
||||
**Then** show a confirmation modal with:
|
||||
- Warning message: "Are you sure you want to delete this potential client?" / "هل أنت متأكد من حذف هذا العميل المحتمل؟"
|
||||
- Potential client name displayed
|
||||
- "Cancel" and "Delete" buttons
|
||||
|
||||
**Given** the admin is on the list page
|
||||
**When** clicking the delete icon
|
||||
**Then** show the same confirmation modal
|
||||
|
||||
### AC10: Delete Execution
|
||||
|
||||
**Given** the delete confirmation modal is shown
|
||||
**When** the admin confirms deletion
|
||||
**Then** delete the potential client record
|
||||
**And** close the modal
|
||||
**And** show success message: "Potential client deleted successfully" / "تم حذف العميل المحتمل بنجاح"
|
||||
**And** redirect to list (if on view page) or refresh list (if on list page)
|
||||
|
||||
### AC11: RTL Support
|
||||
|
||||
**Given** Arabic language is selected
|
||||
**When** viewing create/edit/view pages
|
||||
**Then** all forms and layouts align correctly right-to-left
|
||||
|
||||
### AC12: Cancel Actions
|
||||
|
||||
**Given** the admin is on create or edit form
|
||||
**When** clicking "Cancel"
|
||||
**Then** navigate back to the previous page (list or view)
|
||||
**And** discard unsaved changes
|
||||
|
||||
## Technical Notes
|
||||
|
||||
### Files to Create
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `resources/views/livewire/admin/potential-clients/create.blade.php` | Create form component |
|
||||
| `resources/views/livewire/admin/potential-clients/show.blade.php` | View page component |
|
||||
| `resources/views/livewire/admin/potential-clients/edit.blade.php` | Edit form component |
|
||||
|
||||
### Files to Modify
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `routes/web.php` | Add create, show, edit routes |
|
||||
| `resources/views/livewire/admin/potential-clients/index.blade.php` | Add delete modal |
|
||||
| `lang/en/potential-clients.php` | Add CRUD translations |
|
||||
| `lang/ar/potential-clients.php` | Add CRUD translations |
|
||||
|
||||
### Routes
|
||||
|
||||
```php
|
||||
// Add to admin routes group
|
||||
Route::get('/admin/potential-clients/create', /* component */)->name('admin.potential-clients.create');
|
||||
Route::get('/admin/potential-clients/{potentialClient}', /* component */)->name('admin.potential-clients.show');
|
||||
Route::get('/admin/potential-clients/{potentialClient}/edit', /* component */)->name('admin.potential-clients.edit');
|
||||
```
|
||||
|
||||
### Create Component Structure
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use App\Enums\PotentialClientType;
|
||||
use App\Models\PotentialClient;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new class extends Component {
|
||||
public string $type = '';
|
||||
public string $name = '';
|
||||
public string $phone = '';
|
||||
public string $email = '';
|
||||
public string $address = '';
|
||||
public string $social_media = '';
|
||||
public string $website = '';
|
||||
public string $notes = '';
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'type' => ['required', 'in:individual,company,agency'],
|
||||
'name' => ['nullable', 'string', 'max:255'],
|
||||
'phone' => ['nullable', 'string', 'max:50'],
|
||||
'email' => ['nullable', 'email', 'max:255'],
|
||||
'address' => ['nullable', 'string', 'max:1000'],
|
||||
'social_media' => ['nullable', 'string', 'max:255'],
|
||||
'website' => ['nullable', 'url', 'max:255'],
|
||||
'notes' => ['nullable', 'string', 'max:5000'],
|
||||
];
|
||||
}
|
||||
|
||||
public function create(): void
|
||||
{
|
||||
$validated = $this->validate();
|
||||
|
||||
PotentialClient::create($validated);
|
||||
|
||||
session()->flash('success', __('potential-clients.created_success'));
|
||||
$this->redirect(route('admin.potential-clients.index'), navigate: true);
|
||||
}
|
||||
|
||||
public function with(): array
|
||||
{
|
||||
return [
|
||||
'types' => PotentialClientType::cases(),
|
||||
];
|
||||
}
|
||||
}; ?>
|
||||
```
|
||||
|
||||
### Show Component Structure
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use App\Models\PotentialClient;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new class extends Component {
|
||||
public PotentialClient $potentialClient;
|
||||
|
||||
public bool $showDeleteModal = false;
|
||||
|
||||
public function confirmDelete(): void
|
||||
{
|
||||
$this->showDeleteModal = true;
|
||||
}
|
||||
|
||||
public function delete(): void
|
||||
{
|
||||
$this->potentialClient->delete();
|
||||
|
||||
session()->flash('success', __('potential-clients.deleted_success'));
|
||||
$this->redirect(route('admin.potential-clients.index'), navigate: true);
|
||||
}
|
||||
}; ?>
|
||||
```
|
||||
|
||||
### Additional Translations
|
||||
|
||||
```php
|
||||
// Add to lang/en/potential-clients.php
|
||||
'create_potential_client' => 'Create Potential Client',
|
||||
'edit_potential_client' => 'Edit Potential Client',
|
||||
'potential_client_details' => 'Potential Client Details',
|
||||
'back_to_list' => 'Back to Potential Clients',
|
||||
'not_provided' => 'Not provided',
|
||||
'created_success' => 'Potential client created successfully',
|
||||
'updated_success' => 'Potential client updated successfully',
|
||||
'deleted_success' => 'Potential client deleted successfully',
|
||||
'delete_confirm_title' => 'Delete Potential Client',
|
||||
'delete_confirm_message' => 'Are you sure you want to delete this potential client?',
|
||||
'cancel' => 'Cancel',
|
||||
'save' => 'Save',
|
||||
'create' => 'Create',
|
||||
'contact_information' => 'Contact Information',
|
||||
'additional_information' => 'Additional Information',
|
||||
'type_required' => 'Please select a type',
|
||||
|
||||
// Add to lang/ar/potential-clients.php
|
||||
'create_potential_client' => 'إنشاء عميل محتمل',
|
||||
'edit_potential_client' => 'تعديل العميل المحتمل',
|
||||
'potential_client_details' => 'تفاصيل العميل المحتمل',
|
||||
'back_to_list' => 'العودة للقائمة',
|
||||
'not_provided' => 'غير متوفر',
|
||||
'created_success' => 'تم إنشاء العميل المحتمل بنجاح',
|
||||
'updated_success' => 'تم تحديث العميل المحتمل بنجاح',
|
||||
'deleted_success' => 'تم حذف العميل المحتمل بنجاح',
|
||||
'delete_confirm_title' => 'حذف العميل المحتمل',
|
||||
'delete_confirm_message' => 'هل أنت متأكد من حذف هذا العميل المحتمل؟',
|
||||
'cancel' => 'إلغاء',
|
||||
'save' => 'حفظ',
|
||||
'create' => 'إنشاء',
|
||||
'contact_information' => 'معلومات الاتصال',
|
||||
'additional_information' => 'معلومات إضافية',
|
||||
'type_required' => 'يرجى اختيار النوع',
|
||||
```
|
||||
|
||||
### Delete Modal (using Flux)
|
||||
|
||||
```blade
|
||||
<flux:modal name="delete-potential-client" class="min-w-[22rem]">
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<flux:heading size="lg">{{ __('potential-clients.delete_confirm_title') }}</flux:heading>
|
||||
<flux:text class="mt-2">{{ __('potential-clients.delete_confirm_message') }}</flux:text>
|
||||
<flux:text class="mt-2 font-medium">{{ $potentialClient->name ?? __('potential-clients.not_provided') }}</flux:text>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<flux:spacer />
|
||||
<flux:button variant="ghost" x-on:click="$flux.modal('delete-potential-client').close()">
|
||||
{{ __('potential-clients.cancel') }}
|
||||
</flux:button>
|
||||
<flux:button variant="danger" wire:click="delete">
|
||||
{{ __('potential-clients.delete') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
</flux:modal>
|
||||
```
|
||||
|
||||
## Dev Checklist
|
||||
|
||||
- [ ] Add routes for create, show, edit pages
|
||||
- [ ] Create `create.blade.php` component with form
|
||||
- [ ] Create `show.blade.php` component with details display
|
||||
- [ ] Create `edit.blade.php` component with pre-populated form
|
||||
- [ ] Add delete confirmation modal to show page
|
||||
- [ ] Add delete functionality to list page (inline delete)
|
||||
- [ ] Implement form validation rules
|
||||
- [ ] Add success flash messages
|
||||
- [ ] Add English translations for CRUD operations
|
||||
- [ ] Add Arabic translations for CRUD operations
|
||||
- [ ] Test create flow
|
||||
- [ ] Test edit flow
|
||||
- [ ] Test delete flow
|
||||
- [ ] Test validation errors
|
||||
- [ ] Test RTL layout
|
||||
- [ ] Write feature tests for all CRUD operations
|
||||
|
||||
## Estimation
|
||||
|
||||
**Complexity:** Medium
|
||||
**Risk:** Low - Follows established patterns
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Story 15.1 (Database & Model) must be completed
|
||||
- Story 15.2 (List & Filter) must be completed
|
||||
Loading…
Reference in New Issue