diff --git a/SHIPLOGIC_TESTING.md b/SHIPLOGIC_TESTING.md
new file mode 100644
index 0000000..b98e6ce
--- /dev/null
+++ b/SHIPLOGIC_TESTING.md
@@ -0,0 +1,223 @@
+# Shiplogic Integration Testing Guide
+
+This guide explains how to test the Shiplogic API integration with mocked responses.
+
+## Files Created
+
+### 1. **routes/shiplogic-mock.php** - Mock API Endpoints
+Routes that simulate the Shiplogic API responses for local testing without hitting the live API.
+
+**Endpoints:**
+- `POST /api/v1/mock/rates` - Returns available shipping rates/service levels
+- `POST /api/v1/mock/shipments` - Returns created shipment confirmation
+- `GET /api/v1/mock/shipments/label` - Returns waybill/label PDF
+- `GET /api/v1/mock/shipments/label/stickers` - Returns sticker labels PDF
+
+**To Enable:**
+Add this line to `routes/web.php`:
+```php
+include base_path('routes/shiplogic-mock.php');
+```
+
+Then mock routes are available at `http://localhost:8000/api/v1/mock/*`
+
+### 2. **app/Testing/ShiplogicMockClient.php** - Test Helper
+PHP class for setting up HTTP mocking in tests. Uses Laravel's `Http::fake()` to intercept HTTP requests.
+
+**Usage in Tests:**
+```php
+public function test_something()
+{
+ ShiplogicMockClient::setup();
+
+ // Now all requests to shiplogic.* URLs will return mocked responses
+ // Run your shipment creation logic here
+}
+```
+
+### 3. **tests/Feature/ShipmentCreationTest.php** - Example Tests
+Two example test cases demonstrating:
+- End-to-end shipment creation workflow
+- ECO service level selection verification
+
+## Test Flow
+
+### Local Testing with Mock Routes
+
+1. **Start Laravel server:**
+ ```bash
+ php artisan serve
+ ```
+
+2. **Update CourierService.php** to point to mock endpoints temporarily:
+ ```php
+ // In CourierService.php constructor or config
+ // Change: $this->baseUrl = config('services.shiplogic.api_base_url');
+ // To test locally: $this->baseUrl = 'http://localhost:8000/api/v1/mock';
+ ```
+
+3. **Trigger shipment creation** via Filament UI or directly:
+ ```php
+ // Create an order
+ $order = Order::factory()->create([...]);
+
+ // Trigger ReadyToShipIntent event
+ event(new App\Events\ReadyToShipIntent($order));
+ ```
+
+4. **Check logs** for detailed request/response logging:
+ ```bash
+ tail -f storage/logs/laravel.log
+ ```
+
+### Unit/Feature Testing with HTTP Mocking
+
+1. **Run the test:**
+ ```bash
+ php artisan test tests/Feature/ShipmentCreationTest.php
+ ```
+
+2. **Test will:**
+ - Mock all HTTP requests to shiplogic API
+ - Create test order with required fields
+ - Trigger shipment creation
+ - Assert order has shipment metadata
+ - Assert PDFs were stored locally
+
+## Logging Details
+
+The enhanced CourierService now logs at each stage:
+
+### GET Rates Request
+```
+INFO: Fetching shipping rates from Shiplogic
+ - order_uuid: 019b8335-4b47-7087-8b93-73f6aa39ee7a
+ - api_url: https://api.shiplogic.com/rates
+ - collection_address: {...}
+ - delivery_address: {...}
+ - parcel_dimensions: {...}
+```
+
+### GET Rates Response
+```
+INFO: Rates API response received
+ - status: 200
+ - successful: true
+
+OR
+
+ERROR: Rates API error response
+ - status: 400 (or other error code)
+ - error_message: Invalid address format
+ - full_response: {...}
+```
+
+### CREATE Shipment Request
+```
+INFO: Creating Shiplogic shipment
+ - order_uuid: 019b8335-4b47-7087-8b93-73f6aa39ee7a
+ - order_number: ORDER-001
+ - customer: John Doe
+ - delivery_address: Apt 5B, 123 Main Street
+ - service_level: FEDEX_INTERNATIONAL_ECONOMY
+ - api_url: https://api.shiplogic.com/shipments
+ - payload: {...}
+```
+
+### CREATE Shipment Response
+```
+INFO: Shipment created in API
+ - shipment_id: 550e8400-e29b-41d4-a716-446655440002
+ - tracking_reference: SHP123456789
+```
+
+## Debugging "Unknown Error"
+
+If you see `ERROR: Failed to get shipping rates {"error":"Failed to fetch rates: Unknown error"}`:
+
+1. **Enable detailed logging** - Now included in updated CourierService
+2. **Check the full API response** - Logs will now show the actual error response
+3. **Common issues:**
+ - Invalid API key format
+ - Missing required address fields
+ - Invalid parcel dimensions
+ - API endpoint URL incorrect
+ - Network/SSL certificate issues
+
+## Running Tests
+
+```bash
+# Run all shipment creation tests
+php artisan test tests/Feature/ShipmentCreationTest.php
+
+# Run specific test
+php artisan test tests/Feature/ShipmentCreationTest.php::test_shipment_creation_with_mock_api
+
+# Run with verbose output
+php artisan test tests/Feature/ShipmentCreationTest.php -v
+
+# Run and dump test database
+php artisan test tests/Feature/ShipmentCreationTest.php --debug
+```
+
+## Mock Response Structure
+
+All mock responses follow the actual Shiplogic API structure:
+
+### Rates Response
+```json
+{
+ "id": "550e8400-e29b-41d4-a716-446655440001",
+ "company_shipment_rates": [
+ {
+ "id": "9f67ff00-7a82-4d81-9481-4c5d8c8f1a01",
+ "company_code": "FEDEX",
+ "company_name": "FedEx",
+ "service_level": {
+ "id": "123456790",
+ "code": "FEDEX_INTERNATIONAL_ECONOMY",
+ "name": "International Economy",
+ "description": "Economy service (ECO)"
+ },
+ "rate": 45.75,
+ "currency": "GBP",
+ "transit_days": "5-7",
+ "delivery_guarantee_date": "2026-01-09"
+ }
+ ]
+}
+```
+
+### Shipments Response
+```json
+{
+ "id": "550e8400-e29b-41d4-a716-446655440002",
+ "short_tracking_reference": "SHP123456789",
+ "tracking_reference": "SHP-123456789-ABC",
+ "customer_reference": "ORDER-12345",
+ "company_code": "FEDEX",
+ "service_level": {
+ "code": "FEDEX_INTERNATIONAL_ECONOMY",
+ "name": "International Economy"
+ },
+ "collection_min_date": "2026-01-04",
+ "delivery_min_date": "2026-01-09",
+ "status": "created",
+ "created_at": "2026-01-03T12:42:56.000000Z"
+}
+```
+
+## Next Steps
+
+1. ✅ Add detailed logging to CourierService
+2. ✅ Create mock API routes and test helper
+3. **TODO:** Test with actual order to capture real error
+4. **TODO:** Fix identified Shiplogic API integration issue
+5. **TODO:** Verify PDFs are being fetched and attached correctly
+
+## Configuration Reference
+
+Key configuration files for Shiplogic:
+- `config/services.php` - API credentials and collection address
+- `config/courier.php` - Courier service settings
+- `.env` - Environment variables (SHIPLOGIC_API_URL, SHIPLOGIC_API_KEY, collection address details)
diff --git a/app/Events/InspectionFailed.php b/app/Events/InspectionFailed.php
new file mode 100644
index 0000000..bbdb93d
--- /dev/null
+++ b/app/Events/InspectionFailed.php
@@ -0,0 +1,17 @@
+update([
'packing_width' => $validated['width'],
'packing_length' => $validated['length'],
+ 'packing_height' => $validated['height'],
'packing_weight' => $validated['weight'],
'packing_completed_at' => now(),
'packed_by' => auth()->id(),
@@ -175,7 +176,7 @@ class OpsController extends Controller
]);
// Emit event for listeners to alert ops
- // TODO: Create InspectionFailed event
+ \App\Events\InspectionFailed::dispatch($order, $validated['issue_description']);
return response()->json([
'success' => true,
@@ -238,4 +239,118 @@ class OpsController extends Controller
"QR-{$order->order_number}.pdf"
);
}
-}
+
+ /**
+ * Re-download shipment PDFs from Shiplogic API
+ *
+ * POST /ops/orders/{id}/redownload-pdfs
+ */
+ public function redownloadShipmentPdfs(Order $order)
+ {
+ // Authorize
+ if (! auth()->user()->can('access-ops')) {
+ abort(403, 'Unauthorized to access ops interface');
+ }
+
+ // Only allow for orders with shipments
+ if (! $order->courier_shipment_id) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'No shipment exists for this order',
+ ], 422);
+ }
+
+ try {
+ $courierService = new \App\Services\CourierService();
+ $result = $courierService->redownloadShipmentPdfs($order);
+
+ Log::info('Shipment PDFs re-downloaded via ops', [
+ 'order_uuid' => $order->uuid,
+ 'order_number' => $order->order_number,
+ 'user_id' => auth()->id(),
+ 'user_name' => auth()->user()->name,
+ 'success' => $result['success'],
+ ]);
+
+ if ($result['success']) {
+ return response()->json([
+ 'success' => true,
+ 'message' => $result['message'],
+ 'sticker_path' => $result['sticker_path'],
+ 'waybill_path' => $result['waybill_path'],
+ ]);
+ } else {
+ return response()->json([
+ 'success' => false,
+ 'message' => $result['message'],
+ ], 500);
+ }
+ } catch (\Exception $e) {
+ Log::error('Failed to re-download shipment PDFs via ops', [
+ 'order_uuid' => $order->uuid,
+ 'error' => $e->getMessage(),
+ ]);
+
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Failed to re-download PDFs: ' . $e->getMessage(),
+ ], 500);
+ }
+ }
+
+ /**
+ * Mark order as ready for collection and move Trello card
+ *
+ * POST /ops/orders/{id}/ready-for-collection
+ */
+ public function markReadyForCollection(Order $order)
+ {
+ // Authorize
+ if (! auth()->user()->can('access-ops')) {
+ abort(403, 'Unauthorized to access ops interface');
+ }
+
+ // Only allow for orders with shipments in ready_to_ship status
+ if (! $order->courier_shipment_id || $order->status !== 'ready_to_ship') {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Order must have a shipment and be in Ready to Ship status',
+ ], 422);
+ }
+
+ try {
+ // Update order status
+ $order->update([
+ 'status' => 'awaiting_collection',
+ 'courier_status' => 'awaiting_collection',
+ ]);
+
+ // Move Trello card to Awaiting Collection
+ if ($order->trello_card_id) {
+ $trelloService = new \App\Services\TrelloService();
+ $trelloService->moveCard($order->trello_card_id, 'Awaiting Collection');
+ }
+
+ Log::info('Order marked ready for collection via ops', [
+ 'order_uuid' => $order->uuid,
+ 'order_number' => $order->order_number,
+ 'user_id' => auth()->id(),
+ 'user_name' => auth()->user()->name,
+ ]);
+
+ return response()->json([
+ 'success' => true,
+ 'message' => 'Order moved to Awaiting Collection',
+ ]);
+ } catch (\Exception $e) {
+ Log::error('Failed to mark order ready for collection', [
+ 'order_uuid' => $order->uuid,
+ 'error' => $e->getMessage(),
+ ]);
+
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Failed to mark ready for collection: ' . $e->getMessage(),
+ ], 500);
+ }
+ }
diff --git a/app/Http/Controllers/OrderController.php b/app/Http/Controllers/OrderController.php
index 14f90f3..c296a40 100644
--- a/app/Http/Controllers/OrderController.php
+++ b/app/Http/Controllers/OrderController.php
@@ -117,12 +117,14 @@ class OrderController extends Controller
'customer_email' => 'required|email',
'customer_phone' => 'required|string|max:20',
'shipping_street_address' => 'required|string|max:255',
+ 'shipping_unit_number' => 'nullable|string|max:255',
'shipping_local_area' => 'required|string|max:255',
'shipping_city' => 'required|string|max:255',
'shipping_zone' => 'required|string|max:255',
'shipping_postcode' => 'required|string|max:20',
- 'shipping_country' => 'required|string|max:2',
+ 'shipping_country' => 'required|string|max:255',
'shipping_type' => 'required|in:residential,business',
+ 'business_name' => 'nullable|string|max:255',
'notes' => 'nullable|string|max:500'
]);
@@ -226,12 +228,14 @@ class OrderController extends Controller
'customer_email' => $request->input('customer_email'),
'customer_phone' => $request->input('customer_phone'),
'shipping_street_address' => $request->input('shipping_street_address'),
+ 'shipping_unit_number' => $request->input('shipping_unit_number'),
'shipping_local_area' => $request->input('shipping_local_area'),
'shipping_city' => $request->input('shipping_city'),
'shipping_zone' => $request->input('shipping_zone'),
'shipping_postcode' => $request->input('shipping_postcode'),
'shipping_country' => $request->input('shipping_country'),
'shipping_type' => $request->input('shipping_type'),
+ 'business_name' => $request->input('business_name'),
];
if ($request->filled('notes')) {
diff --git a/app/Http/Controllers/PackingController.php b/app/Http/Controllers/PackingController.php
index 95096aa..7a99d7d 100644
--- a/app/Http/Controllers/PackingController.php
+++ b/app/Http/Controllers/PackingController.php
@@ -47,6 +47,7 @@ class PackingController extends Controller
$order->update([
'packing_width' => $validated['width'],
'packing_length' => $validated['length'],
+ 'packing_height' => $validated['height'],
'packing_weight' => $validated['weight'],
'packing_completed_at' => now(),
'packed_by' => Auth::id(),
diff --git a/app/Http/Controllers/TrelloWebhookController.php b/app/Http/Controllers/TrelloWebhookController.php
index 1550171..519854e 100644
--- a/app/Http/Controllers/TrelloWebhookController.php
+++ b/app/Http/Controllers/TrelloWebhookController.php
@@ -3,6 +3,11 @@
namespace App\Http\Controllers;
use App\Events\ReadyToShipIntent;
+use App\Events\OrderMovedToPrep;
+use App\Events\OrderMovedToAwaitingApproval;
+use App\Events\OrderMovedToReadyForPrint;
+use App\Events\OrderMovedToPrinting;
+use App\Events\OrderMovedToInspection;
use App\Models\Order;
use App\Models\CustomOrder;
use Illuminate\Http\Request;
@@ -106,9 +111,26 @@ class TrelloWebhookController extends Controller
// Handle list-specific actions
match ($listName) {
- 'Prep' => $order->update(['status' => 'prep']),
- 'Printing' => $order->update(['status' => 'printing']),
- 'Inspection' => $order->update(['status' => 'inspection']),
+ 'Prep/Design' => (function () use ($order) {
+ $order->update(['status' => 'prep']);
+ OrderMovedToPrep::dispatch($order);
+ })(),
+ 'Awaiting Customer Approval' => (function () use ($order) {
+ $order->update(['status' => 'awaiting_approval']);
+ OrderMovedToAwaitingApproval::dispatch($order);
+ })(),
+ 'Ready for Print' => (function () use ($order) {
+ $order->update(['status' => 'ready_for_print']);
+ OrderMovedToReadyForPrint::dispatch($order);
+ })(),
+ 'Printing' => (function () use ($order) {
+ $order->update(['status' => 'printing']);
+ OrderMovedToPrinting::dispatch($order);
+ })(),
+ 'Inspection' => (function () use ($order) {
+ $order->update(['status' => 'inspection']);
+ OrderMovedToInspection::dispatch($order);
+ })(),
'Packing' => $order->update(['status' => 'packing']),
'Ready to Ship' => (function () use ($order, $cardId) {
$order->update(['status' => 'ready_to_ship']);
diff --git a/app/Listeners/CreateShipmentOnReadyToShip.php b/app/Listeners/CreateShipmentOnReadyToShip.php
index 41cbdd2..fefa751 100644
--- a/app/Listeners/CreateShipmentOnReadyToShip.php
+++ b/app/Listeners/CreateShipmentOnReadyToShip.php
@@ -4,17 +4,21 @@ namespace App\Listeners;
use App\Events\ReadyToShipIntent;
use App\Services\CourierService;
+use App\Services\TrelloService;
use App\Events\ShipmentCreated;
use App\Events\ShipmentCreationFailed;
use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Storage;
class CreateShipmentOnReadyToShip
{
/**
* Create the event listener.
*/
- public function __construct(protected CourierService $courierService)
- {
+ public function __construct(
+ protected CourierService $courierService,
+ protected TrelloService $trelloService,
+ ) {
}
/**
@@ -31,6 +35,11 @@ class CreateShipmentOnReadyToShip
try {
$result = $this->courierService->createShipmentForOrder($order);
+ // Attach shipment PDFs to Trello card
+ if ($order->trello_card_id) {
+ $this->attachShipmentDocumentsToTrello($order, $result);
+ }
+
// Emit event to trigger Slack/Trello updates
ShipmentCreated::dispatch(
$order,
@@ -49,4 +58,48 @@ class CreateShipmentOnReadyToShip
ShipmentCreationFailed::dispatch($order, $e->getMessage());
}
}
+
+ /**
+ * Attach shipment documents (label and sticker) to the Trello card
+ */
+ private function attachShipmentDocumentsToTrello($order, array $result): void
+ {
+ if (! $result['sticker_path'] && ! $result['waybill_path']) {
+ Log::warning('No shipment documents to attach', ['order_uuid' => $order->uuid]);
+ return;
+ }
+
+ try {
+ // Attach sticker PDF
+ if ($result['sticker_path'] && Storage::disk('public')->exists($result['sticker_path'])) {
+ $stickerUrl = asset('storage/' . $result['sticker_path']);
+ $this->trelloService->attachFile($order->trello_card_id, 'Shipment Sticker.pdf', $stickerUrl);
+ Log::info('Sticker attached to Trello card', [
+ 'order_uuid' => $order->uuid,
+ 'card_id' => $order->trello_card_id,
+ ]);
+ }
+
+ // Attach waybill/label PDF
+ if ($result['waybill_path'] && Storage::disk('public')->exists($result['waybill_path'])) {
+ $waybillUrl = asset('storage/' . $result['waybill_path']);
+ $this->trelloService->attachFile($order->trello_card_id, 'Shipment Label.pdf', $waybillUrl);
+ Log::info('Waybill attached to Trello card', [
+ 'order_uuid' => $order->uuid,
+ 'card_id' => $order->trello_card_id,
+ ]);
+ }
+
+ Log::info('Shipment documents attached to Trello card', [
+ 'order_uuid' => $order->uuid,
+ 'card_id' => $order->trello_card_id,
+ ]);
+ } catch (\Exception $e) {
+ Log::error('Failed to attach shipment documents to Trello', [
+ 'order_uuid' => $order->uuid,
+ 'error' => $e->getMessage(),
+ ]);
+ }
+ }
}
+
diff --git a/app/Listeners/MoveCardToPackingOnInspectionPassed.php b/app/Listeners/MoveCardToPackingOnInspectionPassed.php
index 8008844..f9bae7f 100644
--- a/app/Listeners/MoveCardToPackingOnInspectionPassed.php
+++ b/app/Listeners/MoveCardToPackingOnInspectionPassed.php
@@ -27,7 +27,7 @@ class MoveCardToPackingOnInspectionPassed implements ShouldQueue
try {
// Move card to Packing list
- $success = $this->trello->moveCard($order->trello_card_id, 'packing');
+ $success = $this->trello->moveCard($order->trello_card_id, 'Packing');
if ($success) {
Log::info('Trello card moved to Packing', [
diff --git a/app/Listeners/NotifySlackOnCardMovedToInspection.php b/app/Listeners/NotifySlackOnCardMovedToInspection.php
new file mode 100644
index 0000000..2941ab7
--- /dev/null
+++ b/app/Listeners/NotifySlackOnCardMovedToInspection.php
@@ -0,0 +1,31 @@
+order;
+
+ $message = "🔍 Order Moved to Inspection\n" .
+ "Order: #{$order->order_number}\n" .
+ "Status: Ready for Quality Check";
+
+ // Notify Slack #production
+ $this->slack->production($message);
+
+ Log::info('Card moved to inspection notification sent to production', [
+ 'order_uuid' => $order->uuid,
+ 'order_number' => $order->order_number,
+ ]);
+ }
+}
diff --git a/app/Listeners/NotifySlackOnInspectionFailed.php b/app/Listeners/NotifySlackOnInspectionFailed.php
new file mode 100644
index 0000000..ff453c9
--- /dev/null
+++ b/app/Listeners/NotifySlackOnInspectionFailed.php
@@ -0,0 +1,33 @@
+order;
+
+ $message = "⚠️ Order Inspection Failed\n" .
+ "Order: #{$order->order_number}\n" .
+ "Reason: {$event->reason}\n" .
+ "Status: Awaiting Review";
+
+ // Notify Slack #ops-alerts
+ $this->slack->opsAlerts($message);
+
+ Log::info('Inspection failed notification sent to ops-alerts', [
+ 'order_uuid' => $order->uuid,
+ 'order_number' => $order->order_number,
+ 'reason' => $event->reason,
+ ]);
+ }
+}
diff --git a/app/Listeners/NotifySlackOnInspectionPassed.php b/app/Listeners/NotifySlackOnInspectionPassed.php
new file mode 100644
index 0000000..da5d1df
--- /dev/null
+++ b/app/Listeners/NotifySlackOnInspectionPassed.php
@@ -0,0 +1,31 @@
+order;
+
+ $message = "✅ Order Inspection Passed\n" .
+ "Order: #{$order->order_number}\n" .
+ "Status: Ready for Packing";
+
+ // Notify Slack #production
+ $this->slack->production($message);
+
+ Log::info('Inspection passed notification sent to production', [
+ 'order_uuid' => $order->uuid,
+ 'order_number' => $order->order_number,
+ ]);
+ }
+}
diff --git a/app/Listeners/NotifySlackOnOrderMovedToAwaitingApproval.php b/app/Listeners/NotifySlackOnOrderMovedToAwaitingApproval.php
new file mode 100644
index 0000000..a4cd0c4
--- /dev/null
+++ b/app/Listeners/NotifySlackOnOrderMovedToAwaitingApproval.php
@@ -0,0 +1,77 @@
+order;
+ $webhook = config('services.slack.design_hook_url');
+
+ Log::info('Preparing Slack notification for order awaiting approval', [
+ 'webhook_configured' => !empty($webhook),
+ 'order_number' => $order->order_number,
+ ]);
+
+ if (! $webhook) {
+ Log::warning('Slack design channel webhook not configured');
+ return;
+ }
+
+ $payload = [
+ 'text' => 'Order Awaiting Customer Approval',
+ 'blocks' => [
+ [
+ 'type' => 'header',
+ 'text' => [
+ 'type' => 'plain_text',
+ 'text' => '⏳ Order Awaiting Customer Approval',
+ ],
+ ],
+ [
+ 'type' => 'section',
+ 'fields' => [
+ [
+ 'type' => 'mrkdwn',
+ 'text' => "*Order Number:*\n#{$order->order_number}",
+ ],
+ [
+ 'type' => 'mrkdwn',
+ 'text' => "*Status:*\nAwaiting Approval",
+ ],
+ ],
+ ],
+ [
+ 'type' => 'context',
+ 'elements' => [
+ [
+ 'type' => 'mrkdwn',
+ 'text' => "Updated at " . now()->format('Y-m-d H:i:s'),
+ ],
+ ],
+ ],
+ ],
+ ];
+
+ $response = Http::post($webhook, $payload);
+
+ Log::info('Slack notification sent for order awaiting approval', [
+ 'order_uuid' => $order->uuid,
+ 'order_number' => $order->order_number,
+ 'channel' => 'design',
+ 'status_code' => $response->status(),
+ ]);
+ } catch (\Throwable $e) {
+ Log::error('Failed to send Slack notification for order awaiting approval', [
+ 'error' => $e->getMessage(),
+ 'trace' => $e->getTraceAsString(),
+ ]);
+ }
+ }
+}
diff --git a/app/Listeners/NotifySlackOnOrderMovedToPrep.php b/app/Listeners/NotifySlackOnOrderMovedToPrep.php
new file mode 100644
index 0000000..4404608
--- /dev/null
+++ b/app/Listeners/NotifySlackOnOrderMovedToPrep.php
@@ -0,0 +1,77 @@
+order;
+ $webhook = config('services.slack.design_hook_url');
+
+ Log::info('Preparing Slack notification for order moved to prep', [
+ 'webhook_configured' => !empty($webhook),
+ 'order_number' => $order->order_number,
+ ]);
+
+ if (! $webhook) {
+ Log::warning('Slack design channel webhook not configured');
+ return;
+ }
+
+ $payload = [
+ 'text' => 'Order Moved to Prep/Design',
+ 'blocks' => [
+ [
+ 'type' => 'header',
+ 'text' => [
+ 'type' => 'plain_text',
+ 'text' => '📋 Order Moved to Prep/Design',
+ ],
+ ],
+ [
+ 'type' => 'section',
+ 'fields' => [
+ [
+ 'type' => 'mrkdwn',
+ 'text' => "*Order Number:*\n#{$order->order_number}",
+ ],
+ [
+ 'type' => 'mrkdwn',
+ 'text' => "*Status:*\n{$order->status}",
+ ],
+ ],
+ ],
+ [
+ 'type' => 'context',
+ 'elements' => [
+ [
+ 'type' => 'mrkdwn',
+ 'text' => "Updated at " . now()->format('Y-m-d H:i:s'),
+ ],
+ ],
+ ],
+ ],
+ ];
+
+ $response = Http::post($webhook, $payload);
+
+ Log::info('Slack notification sent for order moved to prep', [
+ 'order_uuid' => $order->uuid,
+ 'order_number' => $order->order_number,
+ 'channel' => 'design',
+ 'status_code' => $response->status(),
+ ]);
+ } catch (\Throwable $e) {
+ Log::error('Failed to send Slack notification for order moved to prep', [
+ 'error' => $e->getMessage(),
+ 'trace' => $e->getTraceAsString(),
+ ]);
+ }
+ }
+}
diff --git a/app/Listeners/NotifySlackOnOrderMovedToPrinting.php b/app/Listeners/NotifySlackOnOrderMovedToPrinting.php
new file mode 100644
index 0000000..78714ec
--- /dev/null
+++ b/app/Listeners/NotifySlackOnOrderMovedToPrinting.php
@@ -0,0 +1,77 @@
+order;
+ $webhook = config('services.slack.production_hook_url');
+
+ Log::info('Preparing Slack notification for order printing started', [
+ 'webhook_configured' => !empty($webhook),
+ 'order_number' => $order->order_number,
+ ]);
+
+ if (! $webhook) {
+ Log::warning('Slack production channel webhook not configured');
+ return;
+ }
+
+ $payload = [
+ 'text' => 'Order Printing Started',
+ 'blocks' => [
+ [
+ 'type' => 'header',
+ 'text' => [
+ 'type' => 'plain_text',
+ 'text' => '🖨️ Order Printing Started',
+ ],
+ ],
+ [
+ 'type' => 'section',
+ 'fields' => [
+ [
+ 'type' => 'mrkdwn',
+ 'text' => "*Order Number:*\n#{$order->order_number}",
+ ],
+ [
+ 'type' => 'mrkdwn',
+ 'text' => "*Status:*\nPrinting in Progress",
+ ],
+ ],
+ ],
+ [
+ 'type' => 'context',
+ 'elements' => [
+ [
+ 'type' => 'mrkdwn',
+ 'text' => "Updated at " . now()->format('Y-m-d H:i:s'),
+ ],
+ ],
+ ],
+ ],
+ ];
+
+ $response = Http::post($webhook, $payload);
+
+ Log::info('Slack notification sent for order printing started', [
+ 'order_uuid' => $order->uuid,
+ 'order_number' => $order->order_number,
+ 'channel' => 'production',
+ 'status_code' => $response->status(),
+ ]);
+ } catch (\Throwable $e) {
+ Log::error('Failed to send Slack notification for order printing started', [
+ 'error' => $e->getMessage(),
+ 'trace' => $e->getTraceAsString(),
+ ]);
+ }
+ }
+}
diff --git a/app/Listeners/NotifySlackOnOrderMovedToReadyForPrint.php b/app/Listeners/NotifySlackOnOrderMovedToReadyForPrint.php
new file mode 100644
index 0000000..da3c451
--- /dev/null
+++ b/app/Listeners/NotifySlackOnOrderMovedToReadyForPrint.php
@@ -0,0 +1,77 @@
+order;
+ $webhook = config('services.slack.production_hook_url');
+
+ Log::info('Preparing Slack notification for order ready for print', [
+ 'webhook_configured' => !empty($webhook),
+ 'order_number' => $order->order_number,
+ ]);
+
+ if (! $webhook) {
+ Log::warning('Slack production channel webhook not configured');
+ return;
+ }
+
+ $payload = [
+ 'text' => 'Order Ready for Print',
+ 'blocks' => [
+ [
+ 'type' => 'header',
+ 'text' => [
+ 'type' => 'plain_text',
+ 'text' => '✅ Order Ready for Print',
+ ],
+ ],
+ [
+ 'type' => 'section',
+ 'fields' => [
+ [
+ 'type' => 'mrkdwn',
+ 'text' => "*Order Number:*\n#{$order->order_number}",
+ ],
+ [
+ 'type' => 'mrkdwn',
+ 'text' => "*Status:*\nReady for Print",
+ ],
+ ],
+ ],
+ [
+ 'type' => 'context',
+ 'elements' => [
+ [
+ 'type' => 'mrkdwn',
+ 'text' => "Updated at " . now()->format('Y-m-d H:i:s'),
+ ],
+ ],
+ ],
+ ],
+ ];
+
+ $response = Http::post($webhook, $payload);
+
+ Log::info('Slack notification sent for order ready for print', [
+ 'order_uuid' => $order->uuid,
+ 'order_number' => $order->order_number,
+ 'channel' => 'production',
+ 'status_code' => $response->status(),
+ ]);
+ } catch (\Throwable $e) {
+ Log::error('Failed to send Slack notification for order ready for print', [
+ 'error' => $e->getMessage(),
+ 'trace' => $e->getTraceAsString(),
+ ]);
+ }
+ }
+}
diff --git a/app/Listeners/NotifySlackOnOrderPacked.php b/app/Listeners/NotifySlackOnOrderPacked.php
index 7e46ae5..feb48e5 100644
--- a/app/Listeners/NotifySlackOnOrderPacked.php
+++ b/app/Listeners/NotifySlackOnOrderPacked.php
@@ -27,14 +27,25 @@ class NotifySlackOnOrderPacked
Log::info('Order packed notification sent', ['order_uuid' => $order->uuid]);
- // Move Trello card to Packing list
+ // Move Trello card to Ready to Ship and populate shipping dimensions as custom fields
if ($order->trello_card_id) {
- $this->trello->moveCard($order->trello_card_id, 'Packing');
+ // Move card to Ready to Ship list
+ $this->trello->moveCard($order->trello_card_id, 'Ready to Ship');
+
+ // Populate custom fields with packing dimensions
+ $this->trello->setNumberField($order->trello_card_id, 'ship_w', $event->width);
+ $this->trello->setNumberField($order->trello_card_id, 'ship_h', $event->height);
+ $this->trello->setNumberField($order->trello_card_id, 'ship_l', $event->length);
+ $this->trello->setNumberField($order->trello_card_id, 'weight', $event->weight);
// Try to check off "Packed" item in checklist
$this->trello->checkItem($order->trello_card_id, 'Packing', 'Packed');
- Log::info('Trello card moved to Packing', ['order_uuid' => $order->uuid]);
+ Log::info('Trello card moved to Ready to Ship and dimensions added', [
+ 'order_uuid' => $order->uuid,
+ 'dimensions' => "{$event->width}x{$event->length}x{$event->height}cm",
+ 'weight' => "{$event->weight}kg",
+ ]);
}
}
}
diff --git a/app/Listeners/NotifySlackOnShipmentCreated.php b/app/Listeners/NotifySlackOnShipmentCreated.php
index 897418b..86b0cc5 100644
--- a/app/Listeners/NotifySlackOnShipmentCreated.php
+++ b/app/Listeners/NotifySlackOnShipmentCreated.php
@@ -28,22 +28,7 @@ class NotifySlackOnShipmentCreated
Log::info('Shipment created notification sent', ['order_uuid' => $order->uuid]);
- // Attach shipping documents to Trello card
- if ($order->trello_card_id) {
- if ($event->stickerPath && Storage::disk('public')->exists($event->stickerPath)) {
- $stickerUrl = Storage::disk('public')->url($event->stickerPath);
- $this->trello->attachFile($order->trello_card_id, 'Sticker.pdf', $stickerUrl);
- }
-
- if ($event->waybillPath && Storage::disk('public')->exists($event->waybillPath)) {
- $waybillUrl = Storage::disk('public')->url($event->waybillPath);
- $this->trello->attachFile($order->trello_card_id, 'Waybill.pdf', $waybillUrl);
- }
-
- // Move card to Awaiting Collection
- $this->trello->moveCard($order->trello_card_id, 'Awaiting Collection');
-
- Log::info('Trello card updated with shipment details', ['order_uuid' => $order->uuid]);
- }
+ // Note: PDFs are already attached to Trello card by CreateShipmentOnReadyToShip listener
+ // This listener only handles Slack notification
}
}
diff --git a/app/Models/CustomOrder.php b/app/Models/CustomOrder.php
index ee2fc90..b357545 100644
--- a/app/Models/CustomOrder.php
+++ b/app/Models/CustomOrder.php
@@ -36,6 +36,7 @@ class CustomOrder extends Model
'proof_approved_at',
'packing_width',
'packing_length',
+ 'packing_height',
'packing_weight',
'packing_completed_at',
'packed_by',
diff --git a/app/Models/Order.php b/app/Models/Order.php
index a70f23b..76e072c 100644
--- a/app/Models/Order.php
+++ b/app/Models/Order.php
@@ -27,12 +27,14 @@ class Order extends Model
'customer_phone',
'shipping_address',
'shipping_street_address',
+ 'shipping_unit_number',
'shipping_local_area',
'shipping_city',
'shipping_zone',
'shipping_country',
'shipping_postcode',
'shipping_type',
+ 'business_name',
'notes',
'yoco_checkout_id',
'yoco_redirect_url',
@@ -40,12 +42,19 @@ class Order extends Model
'yoco_payment_id',
'packing_width',
'packing_length',
+ 'packing_height',
'packing_weight',
'packing_completed_at',
'packed_by',
+ 'courier_shipment_id',
'courier_waybill_id',
'courier_tracking_number',
'courier_status',
+ 'courier_rate',
+ 'courier_service_level_code',
+ 'courier_service_level_id',
+ 'courier_collection_min_date',
+ 'courier_delivery_min_date',
'delivered_at',
'delivery_failure_reason',
'trello_card_id',
@@ -59,6 +68,8 @@ class Order extends Model
'packing_completed_at' => 'datetime',
'delivered_at' => 'datetime',
'qr_generated_at' => 'datetime',
+ 'courier_collection_min_date' => 'datetime',
+ 'courier_delivery_min_date' => 'datetime',
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php
index 321c75c..a2b4179 100644
--- a/app/Providers/EventServiceProvider.php
+++ b/app/Providers/EventServiceProvider.php
@@ -5,7 +5,13 @@ namespace App\Providers;
use App\Events\BalancePaid;
use App\Events\DepositPaid;
use App\Events\InspectionPassed;
+use App\Events\InspectionFailed;
use App\Events\OrderCreated;
+use App\Events\OrderMovedToPrep;
+use App\Events\OrderMovedToAwaitingApproval;
+use App\Events\OrderMovedToReadyForPrint;
+use App\Events\OrderMovedToPrinting;
+use App\Events\OrderMovedToInspection;
use App\Events\OrderPacked;
use App\Events\ParcelCollected;
use App\Events\ParcelDelivered;
@@ -21,12 +27,19 @@ use App\Listeners\CreateShipmentOnReadyToShip;
use App\Listeners\GenerateQrCodeOnOrderCreated;
use App\Listeners\MoveCardToPackingOnInspectionPassed;
use App\Listeners\NotifySlackOnOrderCreated;
+use App\Listeners\NotifySlackOnOrderMovedToPrep;
+use App\Listeners\NotifySlackOnOrderMovedToAwaitingApproval;
+use App\Listeners\NotifySlackOnOrderMovedToReadyForPrint;
+use App\Listeners\NotifySlackOnOrderMovedToPrinting;
use App\Listeners\NotifySlackOnOrderPacked;
use App\Listeners\NotifySlackOnParcelCollected;
use App\Listeners\NotifySlackOnParcelDelivered;
use App\Listeners\NotifySlackOnParcelFailedDelivery;
use App\Listeners\NotifySlackOnShipmentCreated;
use App\Listeners\NotifySlackOnShipmentCreationFailed;
+use App\Listeners\NotifySlackOnInspectionPassed;
+use App\Listeners\NotifySlackOnInspectionFailed;
+use App\Listeners\NotifySlackOnCardMovedToInspection;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
@@ -45,6 +58,20 @@ class EventServiceProvider extends ServiceProvider
GenerateQrCodeOnOrderCreated::class,
],
+ // Trello card move events
+ OrderMovedToPrep::class => [
+ NotifySlackOnOrderMovedToPrep::class,
+ ],
+ OrderMovedToAwaitingApproval::class => [
+ NotifySlackOnOrderMovedToAwaitingApproval::class,
+ ],
+ OrderMovedToReadyForPrint::class => [
+ NotifySlackOnOrderMovedToReadyForPrint::class,
+ ],
+ OrderMovedToPrinting::class => [
+ NotifySlackOnOrderMovedToPrinting::class,
+ ],
+
// Packing events
OrderPacked::class => [
NotifySlackOnOrderPacked::class,
@@ -53,6 +80,13 @@ class EventServiceProvider extends ServiceProvider
// Inspection events
InspectionPassed::class => [
MoveCardToPackingOnInspectionPassed::class,
+ NotifySlackOnInspectionPassed::class,
+ ],
+ InspectionFailed::class => [
+ NotifySlackOnInspectionFailed::class,
+ ],
+ OrderMovedToInspection::class => [
+ NotifySlackOnCardMovedToInspection::class,
],
// Ready to Ship intent (from Trello webhook)
@@ -60,8 +94,6 @@ class EventServiceProvider extends ServiceProvider
CreateShipmentOnReadyToShip::class,
],
- //
-
// Shipment events
ShipmentCreated::class => [
NotifySlackOnShipmentCreated::class,
diff --git a/app/Services/CourierService.php b/app/Services/CourierService.php
index 0825dc0..e7c30de 100644
--- a/app/Services/CourierService.php
+++ b/app/Services/CourierService.php
@@ -6,6 +6,7 @@ use App\Models\Order;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
+use Carbon\Carbon;
class CourierService
{
@@ -15,11 +16,11 @@ class CourierService
public function __construct()
{
$this->apiKey = config('courier.api_key');
- $this->baseUrl = config('courier.api_base_url', 'https://api.shiplogic.com/api');
+ $this->baseUrl = config('courier.api_base_url', 'https://api.shiplogic.com');
}
/**
- * Create shipment with full validation and database updates
+ * Create shipment with full validation, rates fetching, and document retrieval
*
* @param Order $order
* @return array{waybill_id: string, tracking_number: string, sticker_path: ?string, waybill_path: ?string}
@@ -33,52 +34,103 @@ class CourierService
if (! $this->validatePacking($order)) {
throw new \Exception('Order not yet packed with valid dimensions');
}
+ Log::info('Packing validation passed', ['order_uuid' => $order->uuid]);
// Guard 2: Validate payment/approval
if (! $this->validatePayment($order)) {
- throw new \Exception($order->is_custom_order ?
- 'Custom order requires proof approved and balance paid' :
- 'Standard order must be fully paid'
+ throw new \Exception(
+ $order->is_custom_order ?
+ 'Custom order requires proof approved and balance paid' :
+ 'Standard order must be fully paid'
);
}
-
+ Log::info('Payment validation passed', ['order_uuid' => $order->uuid]);
// Guard 3: Validate order status
if ($order->status !== 'ready_to_ship') {
throw new \Exception('Order must be in Ready to Ship state');
}
-
+ Log::info('Order status validation passed', ['order_uuid' => $order->uuid]);
// Guard 4: Prevent duplicates
if ($order->courier_waybill_id) {
throw new \Exception('Shipment already exists for this order');
}
-
+ Log::info('Duplicate shipment validation passed', ['order_uuid' => $order->uuid]);
try {
- // Call API to create shipment
+ // Step 1: Get rates and select ECO (cheapest) service level
+ Log::info('Fetching shipping rates', ['order_uuid' => $order->uuid]);
+ $rates = $this->getRates($order);
+ if (empty($rates)) {
+ throw new \Exception('No shipping rates available for this route');
+ }
+ Log::info('Shipping rates retrieved', [
+ 'order_uuid' => $order->uuid,
+ 'rate_count' => count($rates),
+ ]);
+ // Select ECO rate (should be cheapest)
+ $selectedRate = $this->selectEcoRate($rates);
+ if (!$selectedRate) {
+ throw new \Exception('ECO service level not available for this route');
+ }
+ Log::info('Selected ECO service level', [
+ 'order_uuid' => $order->uuid,
+ 'service_level' => $selectedRate['service_level']['code'],
+ 'rate' => $selectedRate['rate'],
+ ]);
+ // Step 2: Build delivery address from order fields
+ $deliveryAddress = $this->buildDeliveryAddress($order);
+ Log::info('Built delivery address', [
+ 'order_uuid' => $order->uuid,
+ 'delivery_address' => $deliveryAddress,
+ ]);
+ // Step 3: Determine collection and delivery minimum dates
+ [$collectionMinDate, $deliveryMinDate] = $this->getMinimumDates();
+ Log::info('Determined minimum dates', [
+ 'order_uuid' => $order->uuid,
+ 'collection_min_date' => $collectionMinDate,
+ 'delivery_min_date' => $deliveryMinDate,
+ ]);
+ // Step 4: Call API to create shipment with all proper data
$shipmentData = $this->callCreateShipmentApi(
- $order->id,
- $order->packing_width,
- $order->packing_length,
- $order->packing_weight,
+ $order,
+ $deliveryAddress,
+ $selectedRate,
+ $collectionMinDate,
+ $deliveryMinDate,
);
-
- // Save shipment details to database
+ Log::info('Shipment created via API', [
+ 'order_uuid' => $order->uuid,
+ 'shipment_id' => $shipmentData['shipment_id'],
+ 'waybill_id' => $shipmentData['waybill_id'],
+ 'tracking_number' => $shipmentData['tracking_number'],
+ ]);
+ // Step 5: Save shipment details to database
$order->update([
+ 'courier_shipment_id' => $shipmentData['shipment_id'],
'courier_waybill_id' => $shipmentData['waybill_id'],
'courier_tracking_number' => $shipmentData['tracking_number'],
+ 'courier_rate' => $selectedRate['rate'],
+ 'courier_service_level_code' => $selectedRate['service_level']['code'],
+ 'courier_service_level_id' => $selectedRate['service_level']['id'],
+ 'courier_collection_min_date' => $collectionMinDate,
+ 'courier_delivery_min_date' => $deliveryMinDate,
'courier_status' => 'awaiting_collection',
'status' => 'awaiting_collection',
]);
Log::info('Shipment created successfully', [
'order_uuid' => $order->uuid,
+ 'shipment_id' => $shipmentData['shipment_id'],
'waybill_id' => $shipmentData['waybill_id'],
+ 'service_level' => $selectedRate['service_level']['code'],
+ 'rate' => $selectedRate['rate'],
]);
- // Fetch shipping documents
- $stickerPath = $this->fetchSticker($shipmentData['shipment_id'], $order->id);
- $waybillPath = $this->fetchWaybill($shipmentData['shipment_id'], $order->id);
+ // Step 6: Fetch shipping documents
+ $stickerPath = $this->fetchAndStoreSticker($shipmentData['shipment_id'], $order->uuid);
+ $waybillPath = $this->fetchAndStoreWaybill($shipmentData['shipment_id'], $order->uuid);
return [
+ 'shipment_id' => $shipmentData['shipment_id'],
'waybill_id' => $shipmentData['waybill_id'],
'tracking_number' => $shipmentData['tracking_number'],
'sticker_path' => $stickerPath,
@@ -95,104 +147,96 @@ class CourierService
}
/**
- * Create a shipment with Shiplogic API
- *
- * @param string $orderId
- * @param float $width Width in cm
- * @param float $length Length in cm
- * @param float $weight Weight in kg
- * @return array{shipment_id: string, waybill_id: string, tracking_number: string}
- * @throws \Exception
+ * Get available shipping rates for an order
*/
- private function callCreateShipmentApi(string $orderId, float $width, float $length, float $weight): array
+ private function getRates(Order $order): array
{
if (! $this->isConfigured()) {
throw new \Exception('Courier API not configured');
}
- if ($width <= 0 || $length <= 0 || $weight <= 0) {
- throw new \Exception('Invalid dimensions or weight: all must be greater than 0');
- }
-
try {
- // Fetch order to get customer and shipping details
- $order = Order::findOrFail($orderId);
-
- // Validate required shipping info
- if (! $order->customer_name || ! $order->shipping_street_address) {
- throw new \Exception('Order missing required customer name or shipping address');
- }
-
- if (! $order->customer_email && ! $order->customer_phone) {
- throw new \Exception('Order must have at least email or phone number');
- }
-
- // Build shipment payload for Shiplogic
+ $deliveryAddress = $this->buildDeliveryAddress($order);
+ Log::info('Built delivery address for rates', [
+ 'order_uuid' => $order->uuid,
+ 'delivery_address' => $deliveryAddress,
+ ]);
+ $collectionAddress = config('services.shiplogic.collection_address');
+ Log::info('Using collection address for rates', [
+ 'order_uuid' => $order->uuid,
+ 'collection_address' => $collectionAddress,
+ ]);
$payload = [
- 'collection_address' => [
- 'street' => 'Two Tales Designs', // TODO: Get from AppSetting
- 'city' => 'Cape Town',
- 'postcode' => '8000',
- 'country' => 'ZA',
- ],
- 'collection_contact' => [
- 'email' => config('mail.from.address'),
- 'mobile_number' => '+27000000000', // TODO: Get from AppSetting
- ],
- 'delivery_address' => [
- 'type' => $order->shipping_type ?? 'residential',
- 'street_address' => $order->shipping_street_address,
- 'local_area' => $order->shipping_local_area,
- 'city' => $order->shipping_city,
- 'zone' => $order->shipping_zone,
- 'code' => $order->shipping_postcode,
- 'country' => $order->shipping_country ?? 'ZA',
- ],
- 'delivery_contact' => [
- 'name' => $order->customer_name,
- 'email' => $order->customer_email,
- 'mobile_number' => $order->customer_phone,
- ],
+ 'collection_address' => $collectionAddress,
+ 'delivery_address' => $deliveryAddress,
'parcels' => [
[
- 'weight' => $weight,
- 'height' => 10, // TODO: Update when height is captured separately
- 'width' => $width,
- 'length' => $length,
+ 'submitted_length_cm' => (float) $order->packing_length,
+ 'submitted_width_cm' => (float) $order->packing_width,
+ 'submitted_height_cm' => (float) ($order->packing_height ?? 10),
+ 'submitted_weight_kg' => (float) $order->packing_weight,
],
],
- 'service_level_id' => $this->getServiceLevelId(), // Standard delivery
- 'customer_reference' => $order->order_number,
- 'mute_notifications' => false,
];
+ Log::info('Prepared rates request payload', [
+ 'order_uuid' => $order->uuid,
+ 'payload' => $payload,
+ ]);
- Log::info('Creating Shiplogic shipment', [
- 'order_id' => $orderId,
- 'order_number' => $order->order_number,
- 'customer' => $order->customer_name,
- 'delivery_address' => $street,
+ $ratesUrl = "{$this->baseUrl}/rates";
+ Log::info('Fetching shipping rates from Shiplogic', [
+ 'order_uuid' => $order->uuid,
+ 'base_url' => $this->baseUrl,
+ 'full_url' => $ratesUrl,
+ 'api_key_set' => ! empty($this->apiKey),
+ 'api_key_length' => strlen($this->apiKey ?? ''),
+ 'collection_address' => $collectionAddress,
+ 'delivery_address' => $deliveryAddress,
+ 'parcel_dimensions' => [
+ 'length' => $order->packing_length,
+ 'width' => $order->packing_width,
+ 'height' => $order->packing_height ?? 10,
+ 'weight' => $order->packing_weight,
+ ],
]);
$response = Http::withHeaders([
'Authorization' => "Bearer {$this->apiKey}",
- ])->post("{$this->baseUrl}/shipments", $payload);
+ ])->post($ratesUrl, $payload);
+
+ Log::info('Rates API response received', [
+ 'order_uuid' => $order->uuid,
+ 'status' => $response->status(),
+ 'successful' => $response->successful(),
+ ]);
if (! $response->successful()) {
- $errorMessage = $response->json('error.message', $response->json('message', 'Unknown error'));
- throw new \Exception("Courier API error: {$errorMessage}");
+ $errorData = $response->json();
+ $errorMessage = $errorData['error']['message'] ?? $errorData['message'] ?? 'Unknown error';
+
+ Log::error('Rates API error response', [
+ 'order_uuid' => $order->uuid,
+ 'status' => $response->status(),
+ 'error_message' => $errorMessage,
+ 'full_response' => $response->json(),
+ ]);
+
+ throw new \Exception("Failed to fetch rates: {$errorMessage}");
}
$data = $response->json();
- return [
- 'shipment_id' => $data['id'] ?? null,
- 'waybill_id' => $data['waybill_number'] ?? null,
- 'tracking_number' => $data['tracking_number'] ?? null,
- ];
+ Log::info('Rates fetched successfully', [
+ 'order_uuid' => $order->uuid,
+ 'rate_count' => count($data['rates'] ?? []),
+ ]);
+
+ return $data['rates'] ?? [];
} catch (\Exception $e) {
- Log::error('Failed to call courier API', [
- 'order_id' => $orderId,
+ Log::error('Failed to get shipping rates', [
+ 'order_uuid' => $order->uuid,
'error' => $e->getMessage(),
+ 'trace' => $e->getTraceAsString(),
]);
throw $e;
@@ -200,12 +244,375 @@ class CourierService
}
/**
- * Get service level ID for standard delivery
- * TODO: Move to AppSetting and make configurable
+ * Select the ECO (Economy) service level - should be the cheapest
*/
- private function getServiceLevelId(): int
+ private function selectEcoRate(array $rates): ?array
{
- return 1; // Standard service level
+ foreach ($rates as $rate) {
+ if (isset($rate['service_level']) && $rate['service_level']['code'] === 'ECO') {
+ return $rate;
+ }
+ }
+
+ // If ECO not found, return the cheapest rate available
+ if (empty($rates)) {
+ return null;
+ }
+
+ usort($rates, function ($a, $b) {
+ return ($a['rate'] ?? PHP_INT_MAX) <=> ($b['rate'] ?? PHP_INT_MAX);
+ });
+
+ return $rates[0] ?? null;
+ }
+
+ /**
+ * Build complete delivery address from order shipping fields
+ */
+ private function buildDeliveryAddress(Order $order): array
+ {
+ // Combine unit number and street address
+ $streetAddress = $order->shipping_street_address;
+ if ($order->shipping_unit_number) {
+ $streetAddress = "{$order->shipping_unit_number}, {$streetAddress}";
+ }
+
+ return [
+ 'type' => $order->shipping_type ?? 'residential',
+ 'company' => $order->business_name ?? '',
+ 'street_address' => $streetAddress,
+ 'local_area' => $order->shipping_local_area ?? '',
+ 'city' => $order->shipping_city ?? '',
+ 'zone' => $order->shipping_zone ?? '',
+ 'code' => $order->shipping_postcode ?? '',
+ 'country' => $order->shipping_country ?? 'ZA',
+ ];
+ }
+
+ /**
+ * Determine collection and delivery minimum dates
+ * If before noon: today
+ * If after noon: tomorrow
+ */
+ private function getMinimumDates(): array
+ {
+ $now = Carbon::now();
+ $noon = Carbon::now()->setHour(12)->setMinute(0)->setSecond(0);
+
+ if ($now->isBefore($noon)) {
+ $date = $now->startOfDay();
+ } else {
+ $date = $now->addDay()->startOfDay();
+ }
+
+ return [$date, $date];
+ }
+
+ /**
+ * Create a shipment with Shiplogic API
+ */
+ private function callCreateShipmentApi(
+ Order $order,
+ array $deliveryAddress,
+ array $selectedRate,
+ \DateTime $collectionMinDate,
+ \DateTime $deliveryMinDate,
+ ): array {
+ if (! $this->isConfigured()) {
+ throw new \Exception('Courier API not configured');
+ }
+ Log::info('Preparing to create shipment via API', [
+ 'order_uuid' => $order->uuid,
+ 'order_number' => $order->order_number,
+ ]);
+ // Validate required shipping info
+ if (! $order->customer_name || ! $order->shipping_street_address) {
+ throw new \Exception('Order missing required customer name or shipping address');
+ }
+ Log::info('Validated required shipping info', [
+ 'order_uuid' => $order->uuid,
+ 'customer_name' => $order->customer_name,
+ 'shipping_street_address' => $order->shipping_street_address,
+ ]);
+ if (! $order->customer_email && ! $order->customer_phone) {
+ throw new \Exception('Order must have at least email or phone number');
+ }
+ Log::info('Validated contact information', [
+ 'order_uuid' => $order->uuid,
+ 'customer_email' => $order->customer_email,
+ 'customer_phone' => $order->customer_phone,
+ ]);
+ try {
+ $collectionAddress = config('services.shiplogic.collection_address');
+ $collectionContact = config('services.shiplogic.collection_contact');
+
+ $payload = [
+ 'collection_address' => $collectionAddress,
+ 'collection_contact' => $collectionContact,
+ 'delivery_address' => $deliveryAddress,
+ 'delivery_contact' => [
+ 'name' => $order->customer_name,
+ 'email' => $order->customer_email ?? '',
+ 'mobile_number' => $order->customer_phone ?? '',
+ ],
+ 'parcels' => [
+ [
+ 'parcel_description' => $order->order_number,
+ 'submitted_length_cm' => (float) $order->packing_length,
+ 'submitted_width_cm' => (float) $order->packing_width,
+ 'submitted_height_cm' => (float) ($order->packing_height ?? 10),
+ 'submitted_weight_kg' => (float) $order->packing_weight,
+ ],
+ ],
+ 'service_level_code' => $selectedRate['service_level']['code'],
+ 'collection_min_date' => $collectionMinDate->format(DATE_ATOM),
+ 'delivery_min_date' => $deliveryMinDate->format(DATE_ATOM),
+ 'customer_reference' => $order->order_number,
+ 'mute_notifications' => false,
+ ];
+
+ $shipmentsUrl = "{$this->baseUrl}/shipments";
+ Log::info('Creating Shiplogic shipment', [
+ 'order_uuid' => $order->uuid,
+ 'order_number' => $order->order_number,
+ 'customer' => $order->customer_name,
+ 'delivery_address' => $deliveryAddress['street_address'],
+ 'service_level' => $selectedRate['service_level']['code'],
+ 'base_url' => $this->baseUrl,
+ 'full_url' => $shipmentsUrl,
+ 'api_key_set' => ! empty($this->apiKey),
+ 'api_key_length' => strlen($this->apiKey ?? ''),
+ 'payload' => $payload,
+ ]);
+
+ $response = Http::withHeaders([
+ 'Authorization' => "Bearer {$this->apiKey}",
+ ])->post($shipmentsUrl, $payload);
+
+ Log::info('Shipment API response received', [
+ 'order_uuid' => $order->uuid,
+ 'status' => $response->status(),
+ 'successful' => $response->successful(),
+ ]);
+
+ if (! $response->successful()) {
+ $errorData = $response->json();
+ $errorMessage = $errorData['error']['message'] ?? $errorData['message'] ?? 'Unknown error';
+
+ Log::error('Shipment API error response', [
+ 'order_uuid' => $order->uuid,
+ 'status' => $response->status(),
+ 'error_message' => $errorMessage,
+ 'full_response' => $response->json(),
+ ]);
+
+ throw new \Exception("Courier API error: {$errorMessage}");
+ }
+
+ $data = $response->json();
+
+ Log::info('Shipment created in API', [
+ 'order_uuid' => $order->uuid,
+ 'shipment_id' => $data['id'] ?? null,
+ 'tracking_reference' => $data['short_tracking_reference'] ?? null,
+ ]);
+
+ return [
+ 'shipment_id' => $data['id'] ?? null,
+ 'waybill_id' => $data['short_tracking_reference'] ?? $data['id'],
+ 'tracking_number' => $data['short_tracking_reference'] ?? null,
+ ];
+ } catch (\Exception $e) {
+ Log::error('Failed to call courier shipment API', [
+ 'order_uuid' => $order->uuid,
+ 'error' => $e->getMessage(),
+ 'trace' => $e->getTraceAsString(),
+ ]);
+
+ throw $e;
+ }
+ }
+
+ /**
+ * Fetch and store shipment label (waybill) PDF from Shiplogic
+ */
+ private function fetchAndStoreWaybill(string $shipmentId, string $orderUuid): ?string
+ {
+ if (! $this->isConfigured()) {
+ return null;
+ }
+
+ try {
+ $url = "{$this->baseUrl}/shipments/label?id={$shipmentId}";
+ Log::info('Fetching waybill PDF', [
+ 'shipment_id' => $shipmentId,
+ 'order_uuid' => $orderUuid,
+ 'url' => $url,
+ ]);
+
+ $response = Http::withHeaders([
+ 'Authorization' => "Bearer {$this->apiKey}",
+ ])->get($url);
+
+ Log::info('Waybill PDF response received', [
+ 'shipment_id' => $shipmentId,
+ 'status' => $response->status(),
+ 'successful' => $response->successful(),
+ 'content_type' => $response->header('Content-Type'),
+ ]);
+
+ if ($response->successful()) {
+ $data = $response->json();
+
+ // API returns a JSON with S3 URL, need to download the actual PDF
+ if (isset($data['url'])) {
+ Log::info('Got S3 URL for waybill PDF', [
+ 'shipment_id' => $shipmentId,
+ 's3_url' => $data['url'],
+ 'filename' => $data['filename'] ?? 'unknown',
+ 'file_size' => $data['file_size'] ?? 'unknown',
+ ]);
+
+ // Download the actual PDF from S3
+ $pdfResponse = Http::get($data['url']);
+
+ if ($pdfResponse->successful()) {
+ $directory = "shipments/{$orderUuid}";
+ $path = "{$directory}/Shipment Label.pdf";
+
+ // Store the binary PDF content
+ $content = $pdfResponse->body();
+ Storage::disk('public')->put($path, $content);
+
+ Log::info('Waybill PDF stored successfully', [
+ 'shipment_id' => $shipmentId,
+ 'path' => $path,
+ 'file_size' => strlen($content),
+ 'exists' => Storage::disk('public')->exists($path),
+ ]);
+ return $path;
+ } else {
+ Log::error('Failed to download waybill PDF from S3', [
+ 'shipment_id' => $shipmentId,
+ 's3_url' => $data['url'],
+ 'status' => $pdfResponse->status(),
+ ]);
+ return null;
+ }
+ } else {
+ Log::error('No S3 URL in waybill response', [
+ 'shipment_id' => $shipmentId,
+ 'response' => $data,
+ ]);
+ return null;
+ }
+ }
+
+ Log::warning('Failed to fetch waybill from courier', [
+ 'shipment_id' => $shipmentId,
+ 'status' => $response->status(),
+ 'response' => $response->json(),
+ ]);
+ return null;
+ } catch (\Exception $e) {
+ Log::error('Exception fetching waybill', [
+ 'shipment_id' => $shipmentId,
+ 'error' => $e->getMessage(),
+ 'trace' => $e->getTraceAsString(),
+ ]);
+ return null;
+ }
+ }
+
+ /**
+ * Fetch and store shipment sticker label PDF from Shiplogic
+ */
+ private function fetchAndStoreSticker(string $shipmentId, string $orderUuid): ?string
+ {
+ if (! $this->isConfigured()) {
+ return null;
+ }
+
+ try {
+ $url = "{$this->baseUrl}/shipments/label/stickers?id={$shipmentId}";
+ Log::info('Fetching sticker PDF', [
+ 'shipment_id' => $shipmentId,
+ 'order_uuid' => $orderUuid,
+ 'url' => $url,
+ ]);
+
+ $response = Http::withHeaders([
+ 'Authorization' => "Bearer {$this->apiKey}",
+ ])->get($url);
+
+ Log::info('Sticker PDF response received', [
+ 'shipment_id' => $shipmentId,
+ 'status' => $response->status(),
+ 'successful' => $response->successful(),
+ 'content_type' => $response->header('Content-Type'),
+ ]);
+
+ if ($response->successful()) {
+ $data = $response->json();
+
+ // API returns a JSON with S3 URL, need to download the actual PDF
+ if (isset($data['url'])) {
+ Log::info('Got S3 URL for sticker PDF', [
+ 'shipment_id' => $shipmentId,
+ 's3_url' => $data['url'],
+ 'filename' => $data['filename'] ?? 'unknown',
+ 'file_size' => $data['file_size'] ?? 'unknown',
+ ]);
+
+ // Download the actual PDF from S3
+ $pdfResponse = Http::get($data['url']);
+
+ if ($pdfResponse->successful()) {
+ $directory = "shipments/{$orderUuid}";
+ $path = "{$directory}/Shipment Sticker.pdf";
+
+ // Store the binary PDF content
+ $content = $pdfResponse->body();
+ Storage::disk('public')->put($path, $content);
+
+ Log::info('Sticker PDF stored successfully', [
+ 'shipment_id' => $shipmentId,
+ 'path' => $path,
+ 'file_size' => strlen($content),
+ 'exists' => Storage::disk('public')->exists($path),
+ ]);
+ return $path;
+ } else {
+ Log::error('Failed to download sticker PDF from S3', [
+ 'shipment_id' => $shipmentId,
+ 's3_url' => $data['url'],
+ 'status' => $pdfResponse->status(),
+ ]);
+ return null;
+ }
+ } else {
+ Log::error('No S3 URL in sticker response', [
+ 'shipment_id' => $shipmentId,
+ 'response' => $data,
+ ]);
+ return null;
+ }
+ }
+
+ Log::warning('Failed to fetch sticker from courier', [
+ 'shipment_id' => $shipmentId,
+ 'status' => $response->status(),
+ 'response' => $response->json(),
+ ]);
+ return null;
+ } catch (\Exception $e) {
+ Log::error('Exception fetching sticker', [
+ 'shipment_id' => $shipmentId,
+ 'error' => $e->getMessage(),
+ 'trace' => $e->getTraceAsString(),
+ ]);
+ return null;
+ }
}
/**
@@ -256,68 +663,6 @@ class CourierService
return $valid;
}
- /**
- * Fetch shipping label/sticker PDF from Shiplogic
- */
- public function fetchSticker(string $shipmentId, string $orderId): ?string
- {
- if (! $this->isConfigured()) {
- return null;
- }
-
- try {
- $response = Http::withHeaders([
- 'Authorization' => "Bearer {$this->apiKey}",
- ])->get("{$this->baseUrl}/shipments/{$shipmentId}/sticker");
-
- if ($response->successful()) {
- $path = "shipments/{$orderId}/sticker.pdf";
- Storage::disk('public')->put($path, $response->body());
-
- return $path;
- }
-
- Log::warning('Failed to fetch sticker from courier', ['shipment_id' => $shipmentId]);
-
- return null;
- } catch (\Exception $e) {
- Log::error('Exception fetching sticker', ['error' => $e->getMessage()]);
-
- return null;
- }
- }
-
- /**
- * Fetch waybill PDF from Shiplogic
- */
- public function fetchWaybill(string $shipmentId, string $orderId): ?string
- {
- if (! $this->isConfigured()) {
- return null;
- }
-
- try {
- $response = Http::withHeaders([
- 'Authorization' => "Bearer {$this->apiKey}",
- ])->get("{$this->baseUrl}/shipments/{$shipmentId}/label");
-
- if ($response->successful()) {
- $path = "shipments/{$orderId}/waybill.pdf";
- Storage::disk('public')->put($path, $response->body());
-
- return $path;
- }
-
- Log::warning('Failed to fetch waybill from courier', ['shipment_id' => $shipmentId]);
-
- return null;
- } catch (\Exception $e) {
- Log::error('Exception fetching waybill', ['error' => $e->getMessage()]);
-
- return null;
- }
- }
-
/**
* Check if courier is configured
*/
@@ -325,4 +670,58 @@ class CourierService
{
return ! empty($this->apiKey);
}
+
+ /**
+ * Public method to re-download shipment PDFs for an existing shipment
+ *
+ * @param Order $order
+ * @return array{success: bool, sticker_path: ?string, waybill_path: ?string, message: string}
+ */
+ public function redownloadShipmentPdfs(Order $order): array
+ {
+ if (! $order->courier_shipment_id) {
+ return [
+ 'success' => false,
+ 'message' => 'No shipment exists for this order',
+ 'sticker_path' => null,
+ 'waybill_path' => null,
+ ];
+ }
+
+ try {
+ Log::info('Re-downloading shipment PDFs', [
+ 'order_uuid' => $order->uuid,
+ 'shipment_id' => $order->courier_shipment_id,
+ ]);
+
+ $stickerPath = $this->fetchAndStoreSticker($order->courier_shipment_id, $order->uuid);
+ $waybillPath = $this->fetchAndStoreWaybill($order->courier_shipment_id, $order->uuid);
+
+ Log::info('Shipment PDFs re-downloaded successfully', [
+ 'order_uuid' => $order->uuid,
+ 'sticker_path' => $stickerPath,
+ 'waybill_path' => $waybillPath,
+ ]);
+
+ return [
+ 'success' => true,
+ 'message' => 'PDFs re-downloaded successfully',
+ 'sticker_path' => $stickerPath,
+ 'waybill_path' => $waybillPath,
+ ];
+ } catch (\Exception $e) {
+ Log::error('Failed to re-download shipment PDFs', [
+ 'order_uuid' => $order->uuid,
+ 'error' => $e->getMessage(),
+ 'trace' => $e->getTraceAsString(),
+ ]);
+
+ return [
+ 'success' => false,
+ 'message' => 'Failed to re-download PDFs: ' . $e->getMessage(),
+ 'sticker_path' => null,
+ 'waybill_path' => null,
+ ];
+ }
+ }
}
diff --git a/app/Services/TrelloService.php b/app/Services/TrelloService.php
index bf1a72e..b07878c 100644
--- a/app/Services/TrelloService.php
+++ b/app/Services/TrelloService.php
@@ -112,6 +112,20 @@ class TrelloService
return $response->successful();
}
+ /**
+ * Add a comment to a card
+ */
+ public function addComment(string $cardId, string $text): bool
+ {
+ $response = Http::post("{$this->baseUrl}/cards/{$cardId}/actions/comments", [
+ 'text' => $text,
+ 'key' => $this->apiKey,
+ 'token' => $this->apiToken,
+ ]);
+
+ return $response->successful();
+ }
+
/**
* Mark checklist item complete
*/
diff --git a/app/Testing/ShiplogicMockClient.php b/app/Testing/ShiplogicMockClient.php
new file mode 100644
index 0000000..f8682df
--- /dev/null
+++ b/app/Testing/ShiplogicMockClient.php
@@ -0,0 +1,107 @@
+ Http::response(self::ratesResponse(), 200),
+ 'shiplogic.*/shipments' => Http::response(self::shipmentsResponse(), 201),
+ 'shiplogic.*/shipments/label' => Http::response(self::pdfResponse(), 200, [
+ 'Content-Type' => 'application/pdf',
+ ]),
+ 'shiplogic.*/shipments/label/stickers' => Http::response(self::pdfResponse(), 200, [
+ 'Content-Type' => 'application/pdf',
+ ]),
+ ]);
+ }
+
+ /**
+ * Mock response for /rates endpoint
+ */
+ public static function ratesResponse(): array
+ {
+ return [
+ 'id' => '550e8400-e29b-41d4-a716-446655440001',
+ 'company_shipment_rates' => [
+ [
+ 'id' => '9f67ff00-7a82-4d81-9481-4c5d8c8f1a00',
+ 'company_code' => 'FEDEX',
+ 'company_name' => 'FedEx',
+ 'service_level' => [
+ 'id' => '123456789',
+ 'code' => 'FEDEX_INTERNATIONAL_PRIORITY_EXPRESS',
+ 'name' => 'International Priority Express',
+ 'description' => 'Fastest service',
+ ],
+ 'rate' => 125.50,
+ 'currency' => 'GBP',
+ 'transit_days' => '1-2',
+ 'delivery_guarantee_date' => '2026-01-05',
+ ],
+ [
+ 'id' => '9f67ff00-7a82-4d81-9481-4c5d8c8f1a01',
+ 'company_code' => 'FEDEX',
+ 'company_name' => 'FedEx',
+ 'service_level' => [
+ 'id' => '123456790',
+ 'code' => 'FEDEX_INTERNATIONAL_ECONOMY',
+ 'name' => 'International Economy',
+ 'description' => 'Economy service (ECO)',
+ ],
+ 'rate' => 45.75,
+ 'currency' => 'GBP',
+ 'transit_days' => '5-7',
+ 'delivery_guarantee_date' => '2026-01-09',
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * Mock response for /shipments endpoint
+ */
+ public static function shipmentsResponse(): array
+ {
+ return [
+ 'id' => '550e8400-e29b-41d4-a716-446655440002',
+ 'short_tracking_reference' => 'SHP123456789',
+ 'tracking_reference' => 'SHP-123456789-ABC',
+ 'customer_reference' => 'ORDER-12345',
+ 'company_code' => 'FEDEX',
+ 'service_level' => [
+ 'code' => 'FEDEX_INTERNATIONAL_ECONOMY',
+ 'name' => 'International Economy',
+ ],
+ 'collection_min_date' => '2026-01-04',
+ 'delivery_min_date' => '2026-01-09',
+ 'status' => 'created',
+ 'created_at' => now()->toIso8601String(),
+ ];
+ }
+
+ /**
+ * Mock PDF response
+ */
+ public static function pdfResponse(): string
+ {
+ // Minimal valid PDF
+ return "%PDF-1.4\n1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n3 0 obj\n<< /Type /Page /Parent 2 0 R /Resources << /Font << /F1 4 0 R >> >> /MediaBox [0 0 612 792] /Contents 5 0 R >>\nendobj\n4 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n5 0 obj\n<< /Length 44 >>\nstream\nBT\n/F1 12 Tf\n100 700 Td\n(Mock PDF) Tj\nET\nendstream\nendobj\nxref\n0 6\n0000000000 65535 f\n0000000009 00000 n\n0000000058 00000 n\n0000000115 00000 n\n0000000273 00000 n\n0000000352 00000 n\ntrailer\n<< /Size 6 /Root 1 0 R >>\nstartxref\n446\n%%EOF";
+ }
+}
diff --git a/config/courier.php b/config/courier.php
index 474641d..e40e927 100644
--- a/config/courier.php
+++ b/config/courier.php
@@ -2,6 +2,10 @@
return [
'api_key' => env('COURIER_API_KEY'),
- 'api_base_url' => env('COURIER_API_BASE_URL', 'https://api.shiplogic.com/api'),
+ 'api_base_url' => env('COURIER_API_BASE_URL', 'https://api.shiplogic.com'),
'webhook_secret' => env('COURIER_WEBHOOK_SECRET'),
+
+ // Use shiplogic config from services.php
+ 'shiplogic_collection_address' => config('services.shiplogic.collection_address'),
+ 'shiplogic_collection_contact' => config('services.shiplogic.collection_contact'),
];
diff --git a/config/services.php b/config/services.php
index b8ed1ef..2376738 100644
--- a/config/services.php
+++ b/config/services.php
@@ -44,6 +44,11 @@ return [
'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
],
+ 'design_hook_url' => env('SLACK_DESIGN_HOOK_URL'),
+ 'production_hook_url' => env('SLACK_PRODUCTION_HOOK_URL'),
+ 'orders_hook_url' => env('SLACK_ORDERS_HOOK_URL'),
+ 'shipping_hook_url' => env('SLACK_SHIPPING_HOOK_URL'),
+ 'opsalert_hook_url' => env('SLACK_OPSALERT_HOOK_URL'),
],
'yoco' => [
@@ -63,4 +68,24 @@ return [
'api_key' => env('GOOGLE_PLACES_API_KEY'),
],
+ 'shiplogic' => [
+ 'api_key' => env('COURIER_API_KEY'),
+ 'api_base_url' => env('COURIER_API_BASE_URL', 'https://api.shiplogic.com'),
+ 'collection_address' => [
+ 'type' => 'business',
+ 'company' => env('SHIPLOGIC_COLLECTION_COMPANY', 'Two Tales Designs'),
+ 'street_address' => env('SHIPLOGIC_COLLECTION_STREET', '194 Bancor Avenue'),
+ 'local_area' => env('SHIPLOGIC_COLLECTION_AREA', 'Menlyn'),
+ 'city' => env('SHIPLOGIC_COLLECTION_CITY', 'Cape Town'),
+ 'zone' => env('SHIPLOGIC_COLLECTION_ZONE', 'Western Cape'),
+ 'code' => env('SHIPLOGIC_COLLECTION_CODE', '8000'),
+ 'country' => env('SHIPLOGIC_COLLECTION_COUNTRY', 'ZA'),
+ ],
+ 'collection_contact' => [
+ 'name' => env('SHIPLOGIC_COLLECTION_CONTACT_NAME', 'Two Tales Designs'),
+ 'email' => env('SHIPLOGIC_COLLECTION_EMAIL', env('MAIL_FROM_ADDRESS')),
+ 'mobile_number' => env('SHIPLOGIC_COLLECTION_PHONE', '+27000000000'),
+ ],
+ ],
+
];
diff --git a/database/migrations/2025_12_30_add_is_sample_to_order_items_table.php b/database/migrations/2025_12_30_add_is_sample_to_order_items_table.php
index 32cc961..9419394 100644
--- a/database/migrations/2025_12_30_add_is_sample_to_order_items_table.php
+++ b/database/migrations/2025_12_30_add_is_sample_to_order_items_table.php
@@ -9,7 +9,9 @@ return new class extends Migration
public function up(): void
{
Schema::table('order_items', function (Blueprint $table) {
- $table->boolean('is_sample')->default(false)->after('type');
+ if (!Schema::hasColumn('order_items', 'is_sample')) {
+ $table->boolean('is_sample')->default(false)->after('type');
+ }
});
}
diff --git a/database/migrations/2025_12_30_add_shipping_fee_to_orders_table.php b/database/migrations/2025_12_30_add_shipping_fee_to_orders_table.php
index fec0252..db24ebd 100644
--- a/database/migrations/2025_12_30_add_shipping_fee_to_orders_table.php
+++ b/database/migrations/2025_12_30_add_shipping_fee_to_orders_table.php
@@ -9,7 +9,9 @@ return new class extends Migration
public function up(): void
{
Schema::table('orders', function (Blueprint $table) {
- $table->decimal('shipping_fee', 10, 2)->default(0)->nullable()->after('total');
+ if (!Schema::hasColumn('orders', 'shipping_fee')) {
+ $table->decimal('shipping_fee', 10, 2)->default(0)->nullable()->after('total');
+ }
});
}
diff --git a/database/migrations/2025_12_31_add_invoice_path_to_orders_table.php b/database/migrations/2025_12_31_add_invoice_path_to_orders_table.php
index d342ca4..6773194 100644
--- a/database/migrations/2025_12_31_add_invoice_path_to_orders_table.php
+++ b/database/migrations/2025_12_31_add_invoice_path_to_orders_table.php
@@ -12,7 +12,9 @@ return new class extends Migration
public function up(): void
{
Schema::table('orders', function (Blueprint $table) {
- $table->string('invoice_path')->nullable()->after('shipping_fee');
+ if (!Schema::hasColumn('orders', 'invoice_path')) {
+ $table->string('invoice_path')->nullable()->after('shipping_fee');
+ }
});
}
diff --git a/database/migrations/2026_01_03_add_address_fields_to_orders_table.php b/database/migrations/2026_01_03_add_address_fields_to_orders_table.php
new file mode 100644
index 0000000..7535119
--- /dev/null
+++ b/database/migrations/2026_01_03_add_address_fields_to_orders_table.php
@@ -0,0 +1,68 @@
+string('shipping_street_address')->nullable()->after('shipping_address');
+ }
+
+ if (!Schema::hasColumn('orders', 'shipping_unit_number')) {
+ $table->string('shipping_unit_number')->nullable()->after('shipping_street_address');
+ }
+
+ if (!Schema::hasColumn('orders', 'shipping_local_area')) {
+ $table->string('shipping_local_area')->nullable()->after('shipping_unit_number');
+ }
+
+ if (!Schema::hasColumn('orders', 'shipping_city')) {
+ $table->string('shipping_city')->nullable()->after('shipping_local_area');
+ }
+
+ if (!Schema::hasColumn('orders', 'shipping_zone')) {
+ $table->string('shipping_zone')->nullable()->after('shipping_city');
+ }
+
+ if (!Schema::hasColumn('orders', 'shipping_postcode')) {
+ $table->string('shipping_postcode')->nullable()->after('shipping_zone');
+ }
+
+ if (!Schema::hasColumn('orders', 'shipping_country')) {
+ $table->string('shipping_country')->default('ZA')->after('shipping_postcode');
+ }
+
+ if (!Schema::hasColumn('orders', 'shipping_type')) {
+ $table->enum('shipping_type', ['residential', 'business'])->default('residential')->after('shipping_country');
+ }
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('orders', function (Blueprint $table) {
+ $table->dropColumnIfExists([
+ 'shipping_street_address',
+ 'shipping_unit_number',
+ 'shipping_local_area',
+ 'shipping_city',
+ 'shipping_zone',
+ 'shipping_postcode',
+ 'shipping_country',
+ 'shipping_type'
+ ]);
+ });
+ }
+};
diff --git a/database/migrations/2026_01_03_add_business_name_to_orders_table.php b/database/migrations/2026_01_03_add_business_name_to_orders_table.php
new file mode 100644
index 0000000..a876e05
--- /dev/null
+++ b/database/migrations/2026_01_03_add_business_name_to_orders_table.php
@@ -0,0 +1,30 @@
+string('business_name')->nullable()->after('shipping_type');
+ }
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('orders', function (Blueprint $table) {
+ $table->dropColumnIfExists('business_name');
+ });
+ }
+};
diff --git a/database/migrations/2026_01_03_add_packing_height_to_orders_table.php b/database/migrations/2026_01_03_add_packing_height_to_orders_table.php
new file mode 100644
index 0000000..be03647
--- /dev/null
+++ b/database/migrations/2026_01_03_add_packing_height_to_orders_table.php
@@ -0,0 +1,40 @@
+decimal('packing_height', 8, 2)->nullable()->comment('Height in cm')->after('packing_length');
+ }
+ });
+
+ Schema::table('custom_orders', function (Blueprint $table) {
+ if (!Schema::hasColumn('custom_orders', 'packing_height')) {
+ $table->decimal('packing_height', 8, 2)->nullable()->comment('Height in cm')->after('packing_length');
+ }
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('orders', function (Blueprint $table) {
+ $table->dropColumnIfExists('packing_height');
+ });
+
+ Schema::table('custom_orders', function (Blueprint $table) {
+ $table->dropColumnIfExists('packing_height');
+ });
+ }
+};
diff --git a/database/migrations/2026_01_03_add_shipment_metadata_to_orders.php b/database/migrations/2026_01_03_add_shipment_metadata_to_orders.php
new file mode 100644
index 0000000..478385d
--- /dev/null
+++ b/database/migrations/2026_01_03_add_shipment_metadata_to_orders.php
@@ -0,0 +1,45 @@
+decimal('courier_rate', 10, 2)->nullable()->after('courier_status');
+ $table->string('courier_service_level_code')->nullable()->after('courier_rate');
+ $table->integer('courier_service_level_id')->nullable()->after('courier_service_level_code');
+
+ // Collection and delivery date information
+ $table->datetime('courier_collection_min_date')->nullable()->after('courier_service_level_id');
+ $table->datetime('courier_delivery_min_date')->nullable()->after('courier_collection_min_date');
+
+ // Shipment ID from Shiplogic API
+ $table->string('courier_shipment_id')->nullable()->after('courier_delivery_min_date');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('orders', function (Blueprint $table) {
+ $table->dropColumn([
+ 'courier_rate',
+ 'courier_service_level_code',
+ 'courier_service_level_id',
+ 'courier_collection_min_date',
+ 'courier_delivery_min_date',
+ 'courier_shipment_id',
+ ]);
+ });
+ }
+};
diff --git a/resources/views/checkout.blade.php b/resources/views/checkout.blade.php
index 15e441f..1c69414 100644
--- a/resources/views/checkout.blade.php
+++ b/resources/views/checkout.blade.php
@@ -44,7 +44,8 @@
.form-group input,
.form-group textarea,
- .form-group select {
+ .form-group select,
+ .address-search-group input {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
@@ -60,7 +61,8 @@
.form-group input:focus,
.form-group textarea:focus,
- .form-group select:focus {
+ .form-group select:focus,
+ .address-search-group input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(45, 80, 71, 0.1);
@@ -237,49 +239,67 @@
Delivery Information
- Full Name
-
+ Full Name*
+
- Email Address
-
+ Email Address*
+
- Phone Number
+ Phone Number*
+
+ Address Type *
+
+ Residential
+ Business
+
+
+
+
+ Business Name *
+
+
+
- Delivery Address
+
+ Delivery Address (Type to search)
✕
-
+
-
-
- Address Type
-
- Residential
- Business
-
-
@@ -369,95 +381,181 @@
-
+@php
+ $apiKey = config('services.google_places.api_key');
+@endphp
+
+@if(!$apiKey)
+
+ ⚠️ Configuration Issue: Google Places API key is not configured. Please add GOOGLE_PLACES_API_KEY to your .env file.
+
+@endif
- if (!addressSearchInput) return;
+@if($apiKey)
+
+
+
- // Prevent form submission on Enter when autocomplete is open
- addressSearchInput.addEventListener('keydown', function(e) {
- if (e.key === 'Enter' && document.querySelector('.pac-container:not([style*="display: none"])')) {
- e.preventDefault();
- }
- });
-
- // When place is selected
- autocomplete.addListener('place_changed', function() {
- const place = autocomplete.getPlace();
-
- if (!place.geometry) {
- console.error('Place has no geometry');
- return;
- }
-
- // Parse address components
- const addressComponents = {};
- place.address_components.forEach(component => {
- addressComponents[component.types[0]] = component.long_name;
+
+@endif
- // Show fields if form has old values (validation error)
- const hasOldValues =
- document.getElementById('shipping_street_address').value ||
- document.getElementById('shipping_city').value;
-
- if (hasOldValues) {
- addressFieldsGroup.style.display = 'block';
- }
-});
-
@endsection
diff --git a/resources/views/ops/actions/read-only.blade.php b/resources/views/ops/actions/read-only.blade.php
index a523370..fe2d5fc 100644
--- a/resources/views/ops/actions/read-only.blade.php
+++ b/resources/views/ops/actions/read-only.blade.php
@@ -2,47 +2,180 @@
-
📋 Order Status: Read-Only
-
- This order is in the {{ str_replace('_', ' ', ucfirst($order->status)) }} phase.
- No actions are available at this stage.
-
+
📋 Order Status: {{ str_replace('_', ' ', ucfirst($order->status)) }}
@if($order->status === 'ready_to_ship')
-
-
- Next Step: Move Trello card to "Ready to Ship" to trigger automatic shipment creation.
+
+
+ ✓ Shipment created and ready to print labels/stickers
+
+
+
Next Steps:
+
+ Print the shipment sticker label
+ Print the shipment waybill/label
+ Apply labels to parcel
+ Move parcel to collection bay
+ Click "Ready for Collection" button below
+
+
+
+
+ ✓ Ready for Collection
+
@elseif($order->status === 'awaiting_collection')
-
-
- Status: Parcel is awaiting collection from courier.
+
+
+ 📦 Parcel awaiting collection from courier
@if($order->courier_waybill_id)
-
- Waybill: {{ $order->courier_waybill_id }}
+
+ Waybill: {{ $order->courier_waybill_id }}
@endif
+
+
+ @if($order->courier_shipment_id)
+
+ 🔄 Re-download Shipment PDFs
+
+
If labels are damaged, re-download them.
+ @endif
@elseif($order->status === 'in_transit')
-
-
- Status: Parcel is in transit to the customer.
+
+
+ ✈️ Parcel in transit to customer
@if($order->courier_tracking_number)
-
- Tracking: {{ $order->courier_tracking_number }}
+
+ Tracking Number: {{ $order->courier_tracking_number }}
@endif
+
+
+ @if($order->courier_shipment_id)
+
+ 🔄 Re-download Shipment PDFs
+
+
If labels are damaged, re-download them.
+ @endif
@else
-
- The order is progressing through the fulfillment pipeline.
+
+ Order is processing through the fulfillment pipeline
@endif
+
+
diff --git a/resources/views/ops/order-detail.blade.php b/resources/views/ops/order-detail.blade.php
index 4880c52..53b83fc 100644
--- a/resources/views/ops/order-detail.blade.php
+++ b/resources/views/ops/order-detail.blade.php
@@ -1,46 +1,31 @@
@extends('layouts.app')
@section('content')
-
-
+
+
-
Order {{ $order->order_number }}
-
{{ $order->uuid }}
+
{{ $order->order_number }}
+
{{ $order->uuid }}
-
+
-
Status
-
{{ str_replace('_', ' ', $order->status) }}
+
Status
+
{{ str_replace('_', ' ', $order->status) }}
-
Order Type
-
{{ $order->is_custom_order ? 'Custom' : 'Standard' }}
+
Order Type
+
{{ $order->is_custom_order ? 'Custom' : 'Standard' }}
-
Customer
-
{{ $order->user->name ?? 'N/A' }}
+
Customer
+
{{ $order->user->name ?? 'N/A' }}
-
Created
-
{{ $order->created_at->format('M d, Y') }}
-
-
-
-
-
-
-
- {!! file_get_contents(storage_path("app/public/qr-codes/{$order->uuid}.svg")) !!}
+
Created
+
{{ $order->created_at->format('M d, Y') }}
diff --git a/routes/shiplogic-mock.php b/routes/shiplogic-mock.php
new file mode 100644
index 0000000..1441a49
--- /dev/null
+++ b/routes/shiplogic-mock.php
@@ -0,0 +1,138 @@
+group(function () {
+ /**
+ * Mock /rates endpoint
+ * Returns sample service level options
+ */
+ Route::post('/rates', function () {
+ return response()->json([
+ 'id' => '550e8400-e29b-41d4-a716-446655440001',
+ 'company_shipment_rates' => [
+ [
+ 'id' => '9f67ff00-7a82-4d81-9481-4c5d8c8f1a00',
+ 'company_code' => 'FEDEX',
+ 'company_name' => 'FedEx',
+ 'service_level' => [
+ 'id' => '123456789',
+ 'code' => 'FEDEX_INTERNATIONAL_PRIORITY_EXPRESS',
+ 'name' => 'International Priority Express',
+ 'description' => 'Fastest service',
+ ],
+ 'rate' => 125.50,
+ 'currency' => 'GBP',
+ 'transit_days' => '1-2',
+ 'delivery_guarantee_date' => '2026-01-05',
+ ],
+ [
+ 'id' => '9f67ff00-7a82-4d81-9481-4c5d8c8f1a01',
+ 'company_code' => 'FEDEX',
+ 'company_name' => 'FedEx',
+ 'service_level' => [
+ 'id' => '123456790',
+ 'code' => 'FEDEX_INTERNATIONAL_ECONOMY',
+ 'name' => 'International Economy',
+ 'description' => 'Economy service (ECO)',
+ ],
+ 'rate' => 45.75,
+ 'currency' => 'GBP',
+ 'transit_days' => '5-7',
+ 'delivery_guarantee_date' => '2026-01-09',
+ ],
+ [
+ 'id' => '9f67ff00-7a82-4d81-9481-4c5d8c8f1a02',
+ 'company_code' => 'DHL',
+ 'company_name' => 'DHL',
+ 'service_level' => [
+ 'id' => '123456791',
+ 'code' => 'DHL_EXPRESS_WORLDWIDE',
+ 'name' => 'Express Worldwide',
+ 'description' => 'Standard express',
+ ],
+ 'rate' => 95.25,
+ 'currency' => 'GBP',
+ 'transit_days' => '2-3',
+ 'delivery_guarantee_date' => '2026-01-06',
+ ],
+ ],
+ ]);
+ });
+
+ /**
+ * Mock /shipments endpoint
+ * Returns created shipment details
+ */
+ Route::post('/shipments', function () {
+ return response()->json([
+ 'id' => '550e8400-e29b-41d4-a716-446655440002',
+ 'short_tracking_reference' => 'SHP123456789',
+ 'tracking_reference' => 'SHP-123456789-ABC',
+ 'customer_reference' => 'ORDER-12345',
+ 'company_code' => 'FEDEX',
+ 'service_level' => [
+ 'code' => 'FEDEX_INTERNATIONAL_ECONOMY',
+ 'name' => 'International Economy',
+ ],
+ 'collection_min_date' => '2026-01-04',
+ 'delivery_min_date' => '2026-01-09',
+ 'status' => 'created',
+ 'created_at' => now()->toIso8601String(),
+ ]);
+ });
+
+ /**
+ * Mock /shipments/label endpoint (waybill/shipping label)
+ * Returns PDF binary data
+ */
+ Route::get('/shipments/label', function () {
+ // Return a minimal PDF (you can replace with a real PDF file)
+ $pdf = $this->generateMockPdf('Shipment Label');
+
+ return response($pdf, 200, [
+ 'Content-Type' => 'application/pdf',
+ 'Content-Disposition' => 'attachment; filename="label.pdf"',
+ ]);
+ });
+
+ /**
+ * Mock /shipments/label/stickers endpoint
+ * Returns PDF with sticker labels
+ */
+ Route::get('/shipments/label/stickers', function () {
+ // Return a minimal PDF (you can replace with a real PDF file)
+ $pdf = $this->generateMockPdf('Shipment Sticker');
+
+ return response($pdf, 200, [
+ 'Content-Type' => 'application/pdf',
+ 'Content-Disposition' => 'attachment; filename="stickers.pdf"',
+ ]);
+ });
+});
+
+/**
+ * Generate a minimal valid PDF for testing
+ * This is a very basic PDF structure that opens in most readers
+ */
+if (! function_exists('generateMockPdf')) {
+ function generateMockPdf($title = 'Document')
+ {
+ $pdf = "%PDF-1.4\n";
+ $pdf .= "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
+ $pdf .= "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n";
+ $pdf .= "3 0 obj\n<< /Type /Page /Parent 2 0 R /Resources << /Font << /F1 4 0 R >> >> /MediaBox [0 0 612 792] /Contents 5 0 R >>\nendobj\n";
+ $pdf .= "4 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n";
+ $pdf .= "5 0 obj\n<< /Length 44 >>\nstream\nBT\n/F1 12 Tf\n100 700 Td\n({$title}) Tj\nET\nendstream\nendobj\n";
+ $pdf .= "xref\n0 6\n0000000000 65535 f\n0000000009 00000 n\n0000000058 00000 n\n0000000115 00000 n\n0000000273 00000 n\n0000000352 00000 n\n";
+ $pdf .= "trailer\n<< /Size 6 /Root 1 0 R >>\nstartxref\n446\n%%EOF";
+
+ return $pdf;
+ }
+}
diff --git a/routes/web.php b/routes/web.php
index 0395d52..c97f409 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -78,6 +78,8 @@ Route::middleware('auth')->group(function () {
Route::post('/ops/orders/{order:uuid}/inspection-passed', [OpsController::class, 'markInspectionPassed'])->name('ops.order.inspection-passed');
Route::post('/ops/orders/{order:uuid}/inspection-failed', [OpsController::class, 'flagInspectionIssue'])->name('ops.order.inspection-failed');
Route::get('/ops/orders/{order:uuid}/sticker/download', [OpsController::class, 'downloadSticker'])->name('ops.order.sticker.download');
+ Route::post('/ops/orders/{order:uuid}/redownload-pdfs', [OpsController::class, 'redownloadShipmentPdfs'])->name('ops.order.redownload-pdfs');
+ Route::post('/ops/orders/{order:uuid}/ready-for-collection', [OpsController::class, 'markReadyForCollection'])->name('ops.order.ready-for-collection');
Route::post('/custom-orders/{customOrder:uuid}/ship', [ShippingController::class, 'createShipment'])->name('custom-orders.ship');
});
diff --git a/storage/framework/views/0ced4ab8b623c6535cabe6077207c3f3.php b/storage/framework/views/0ced4ab8b623c6535cabe6077207c3f3.php
new file mode 100644
index 0000000..dfad156
--- /dev/null
+++ b/storage/framework/views/0ced4ab8b623c6535cabe6077207c3f3.php
@@ -0,0 +1,49 @@
+ true,
+ 'size' => 'md',
+]));
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (in_array($__key, $__propNames)) {
+ $$__key = $$__key ?? $__value;
+ } else {
+ $__newAttributes[$__key] = $__value;
+ }
+}
+
+$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);
+
+unset($__propNames);
+unset($__newAttributes);
+
+foreach (array_filter(([
+ 'circular' => true,
+ 'size' => 'md',
+]), 'is_string', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
+ $$__key = $$__key ?? $__value;
+}
+
+$__defined_vars = get_defined_vars();
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (array_key_exists($__key, $__defined_vars)) unset($$__key);
+}
+
+unset($__defined_vars, $__key, $__value); ?>
+
+
class([
+ 'fi-avatar',
+ 'fi-circular' => $circular,
+ match ($size) {
+ 'sm', 'md', 'lg' => "fi-size-{$size}",
+ default => $size,
+ },
+ ])); ?>
+
+/>
+
\ No newline at end of file
diff --git a/storage/framework/views/1110161c070d609557c8a6c1fcc48147.php b/storage/framework/views/1110161c070d609557c8a6c1fcc48147.php
new file mode 100644
index 0000000..d1f3f43
--- /dev/null
+++ b/storage/framework/views/1110161c070d609557c8a6c1fcc48147.php
@@ -0,0 +1,46 @@
+ [],
+ 'columnStart' => [],
+ 'height' => null,
+]));
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (in_array($__key, $__propNames)) {
+ $$__key = $$__key ?? $__value;
+ } else {
+ $__newAttributes[$__key] = $__value;
+ }
+}
+
+$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);
+
+unset($__propNames);
+unset($__newAttributes);
+
+foreach (array_filter(([
+ 'columnSpan' => [],
+ 'columnStart' => [],
+ 'height' => null,
+]), 'is_string', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
+ $$__key = $$__key ?? $__value;
+}
+
+$__defined_vars = get_defined_vars();
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (array_key_exists($__key, $__defined_vars)) unset($$__key);
+}
+
+unset($__defined_vars, $__key, $__value); ?>
+
+
gridColumn($columnSpan, $columnStart)
+ ->class(['fi-section fi-loading-section'])
+ ->style(['height: ' . ($height ?? '8rem')])); ?>
+
+>
+
\ No newline at end of file
diff --git a/storage/framework/views/1575aae6437543bfaf50d3515c944142.php b/storage/framework/views/1575aae6437543bfaf50d3515c944142.php
new file mode 100644
index 0000000..c51e9e7
--- /dev/null
+++ b/storage/framework/views/1575aae6437543bfaf50d3515c944142.php
@@ -0,0 +1,5 @@
+
class(['fi-dropdown-list'])); ?>>
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/1c1a588d1c9b277d3ae21decb63dcf81.php b/storage/framework/views/1c1a588d1c9b277d3ae21decb63dcf81.php
new file mode 100644
index 0000000..14cf490
--- /dev/null
+++ b/storage/framework/views/1c1a588d1c9b277d3ae21decb63dcf81.php
@@ -0,0 +1,124 @@
+
+
+ '500ms',
+ 'onBlur' => false,
+ 'placeholder' => __('filament-tables::table.fields.search.placeholder'),
+ 'wireModel' => 'tableSearch',
+]));
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (in_array($__key, $__propNames)) {
+ $$__key = $$__key ?? $__value;
+ } else {
+ $__newAttributes[$__key] = $__value;
+ }
+}
+
+$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);
+
+unset($__propNames);
+unset($__newAttributes);
+
+foreach (array_filter(([
+ 'debounce' => '500ms',
+ 'onBlur' => false,
+ 'placeholder' => __('filament-tables::table.fields.search.placeholder'),
+ 'wireModel' => 'tableSearch',
+]), 'is_string', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
+ $$__key = $$__key ?? $__value;
+}
+
+$__defined_vars = get_defined_vars();
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (array_key_exists($__key, $__defined_vars)) unset($$__key);
+}
+
+unset($__defined_vars, $__key, $__value); ?>
+
+
+
+
class(['fi-ta-search-field'])); ?>
+
+>
+
+
+
+
+
+
+
+ 'filament::components.input.wrapper','data' => ['inlinePrefix' => true,'prefixIcon' => \Filament\Support\Icons\Heroicon::MagnifyingGlass,'prefixIconAlias' => \Filament\Tables\View\TablesIconAlias::SEARCH_FIELD,'wire:target' => $wireModel]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::input.wrapper'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['inline-prefix' => true,'prefix-icon' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Filament\Support\Icons\Heroicon::MagnifyingGlass),'prefix-icon-alias' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Filament\Tables\View\TablesIconAlias::SEARCH_FIELD),'wire:target' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($wireModel)]); ?>
+
+
+ 'filament::components.input.index','data' => ['attributes' =>
+ (new ComponentAttributeBag)->merge([
+ 'autocomplete' => 'off',
+ 'inlinePrefix' => true,
+ 'maxlength' => 1000,
+ 'placeholder' => $placeholder,
+ 'type' => 'search',
+ 'wire:key' => $this->getId() . '.table.' . $wireModel . '.field.input',
+ $wireModelAttribute => $wireModel,
+ 'x-bind:id' => '$id(\'input\')',
+ 'x-on:keyup' => 'if ($event.key === \'Enter\') { $wire.$refresh() }',
+ ], escape: false)
+ ]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::input'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['attributes' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(
+ (new ComponentAttributeBag)->merge([
+ 'autocomplete' => 'off',
+ 'inlinePrefix' => true,
+ 'maxlength' => 1000,
+ 'placeholder' => $placeholder,
+ 'type' => 'search',
+ 'wire:key' => $this->getId() . '.table.' . $wireModel . '.field.input',
+ $wireModelAttribute => $wireModel,
+ 'x-bind:id' => '$id(\'input\')',
+ 'x-on:keyup' => 'if ($event.key === \'Enter\') { $wire.$refresh() }',
+ ], escape: false)
+ )]); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/2b91b9a791ff7f292edb8c55adaa6117.php b/storage/framework/views/2b91b9a791ff7f292edb8c55adaa6117.php
new file mode 100644
index 0000000..a302f57
--- /dev/null
+++ b/storage/framework/views/2b91b9a791ff7f292edb8c55adaa6117.php
@@ -0,0 +1,2826 @@
+getAllTableSummaryQuery());
+ $header = $getHeader();
+ $headerActions = array_filter(
+ $getHeaderActions(),
+ fn (\Filament\Actions\Action | \Filament\Actions\ActionGroup $action): bool => $action->isVisible(),
+ );
+ $headerActionsPosition = $getHeaderActionsPosition();
+ $heading = $getHeading();
+ $group = $getGrouping();
+ $toolbarActions = array_filter(
+ $getToolbarActions(),
+ fn (\Filament\Actions\Action | \Filament\Actions\ActionGroup $action): bool => $action->isVisible(),
+ );
+
+ $hasNonBulkToolbarAction = false;
+
+ foreach ($toolbarActions as $toolbarAction) {
+ if ($toolbarAction instanceof \Filament\Actions\BulkActionGroup) {
+ continue;
+ }
+
+ if ($toolbarAction instanceof \Filament\Actions\ActionGroup) {
+ if ($toolbarAction->hasNonBulkAction()) {
+ $hasNonBulkToolbarAction = true;
+
+ break;
+ }
+
+ continue;
+ }
+
+ if (! $toolbarAction->isBulk()) {
+ $hasNonBulkToolbarAction = true;
+
+ break;
+ }
+ }
+
+ $groups = $getGroups();
+ $description = $getDescription();
+ $isGroupsOnly = $isGroupsOnly() && $group;
+ $isReorderable = $isReorderable();
+ $isReordering = $isReordering();
+ $areGroupingSettingsVisible = (! $isReordering) && count($groups) && (! $areGroupingSettingsHidden());
+ $isGroupingDirectionSettingHidden = $isGroupingDirectionSettingHidden();
+ $areGroupsCollapsedByDefault = $areGroupsCollapsedByDefault();
+ $areGroupingSettingsInDropdownOnDesktop = $areGroupingSettingsInDropdownOnDesktop();
+ $isColumnSearchVisible = $isSearchableByColumn();
+ $isGlobalSearchVisible = $isSearchable();
+ $isSearchOnBlur = $isSearchOnBlur();
+ $isSelectionEnabled = $isSelectionEnabled() && (! $isGroupsOnly);
+ $selectsCurrentPageOnly = $selectsCurrentPageOnly();
+ $selectsGroupsOnly = $selectsGroupsOnly();
+ $recordCheckboxPosition = $getRecordCheckboxPosition();
+ $isStriped = $isStriped();
+ $isLoaded = $isLoaded();
+ $hasFilters = $isFilterable();
+ $filtersLayout = $getFiltersLayout();
+ $filtersTriggerAction = $getFiltersTriggerAction();
+ $hasFiltersDialog = $hasFilters && in_array($filtersLayout, [FiltersLayout::Dropdown, FiltersLayout::Modal]);
+ $hasFiltersAboveContent = $hasFilters && in_array($filtersLayout, [FiltersLayout::AboveContent, FiltersLayout::AboveContentCollapsible]);
+ $hasFiltersBelowContent = $hasFilters && ($filtersLayout === FiltersLayout::BelowContent);
+ $hasFiltersBeforeContent = $hasFilters && in_array($filtersLayout, [FiltersLayout::BeforeContent, FiltersLayout::BeforeContentCollapsible]);
+ $hasFiltersAfterContent = $hasFilters && in_array($filtersLayout, [FiltersLayout::AfterContent, FiltersLayout::AfterContentCollapsible]);
+ $hasCollapsibleFilters = $hasFilters && in_array($filtersLayout, [FiltersLayout::AboveContentCollapsible, FiltersLayout::BeforeContentCollapsible, FiltersLayout::AfterContentCollapsible]);
+ $hasFiltersTrigger = $hasFilters && ($hasFiltersDialog || $hasFiltersBeforeContent || $hasFiltersAfterContent);
+ $filtersFormMaxHeight = $getFiltersFormMaxHeight();
+ $hasColumnManagerDropdown = $hasColumnManager();
+ $hasReorderableColumns = $hasReorderableColumns();
+ $hasToggleableColumns = $hasToggleableColumns();
+ $columnManagerApplyAction = $getColumnManagerApplyAction();
+ $columnManagerTriggerAction = $getColumnManagerTriggerAction();
+ $hasHeader = $header || $heading || $description || ($headerActions && (! $isReordering)) || $isReorderable || $areGroupingSettingsVisible || $isGlobalSearchVisible || $hasFilters || count($filterIndicators) || $hasColumnManagerDropdown;
+ $hasHeaderToolbar = $isReorderable || $areGroupingSettingsVisible || $isGlobalSearchVisible || $hasFiltersTrigger || $hasColumnManagerDropdown;
+ $headingTag = $getHeadingTag();
+ $secondLevelHeadingTag = $heading ? $getHeadingTag(1) : $headingTag;
+ $pluralModelLabel = $getPluralModelLabel();
+ $records = $isLoaded ? $getRecords() : null;
+ $hasPagination = (($records instanceof \Illuminate\Contracts\Pagination\Paginator) || ($records instanceof \Illuminate\Contracts\Pagination\CursorPaginator)) && ((! ($records instanceof \Illuminate\Contracts\Pagination\LengthAwarePaginator)) || $records->total());
+ $hasEmptyState = ($records !== null) && ! count($records);
+ $hasContentLayout = $content || $hasColumnsLayout;
+ $searchDebounce = $getSearchDebounce();
+ $allSelectableRecordsCount = ($isSelectionEnabled && $isLoaded) ? $getAllSelectableRecordsCount() : null;
+ $columnsCount = count($columns);
+ $reorderRecordsTriggerAction = $getReorderRecordsTriggerAction($isReordering);
+ $page = $this->getTablePage();
+ $defaultSortOptionLabel = $getDefaultSortOptionLabel();
+ $sortDirection = $getSortDirection();
+
+ if (count($defaultRecordActions) && (! $isReordering)) {
+ $columnsCount++;
+ }
+
+ if ($isSelectionEnabled || $isReordering) {
+ $columnsCount++;
+ }
+
+ if ($group) {
+ $groupedSummarySelectedState = $this->getTableSummarySelectedState($this->getAllTableSummaryQuery(), modifyQueryUsing: fn (\Illuminate\Database\Query\Builder $query) => $group->groupQuery($query, model: $getQuery()->getModel()));
+ }
+
+ if (is_string($filtersFormWidth)) {
+ $filtersFormWidth = Width::tryFrom($filtersFormWidth) ?? $filtersFormWidth;
+ }
+?>
+
+
+ wire:init="loadTable"
+
+ x-data="filamentTable({
+ areGroupsCollapsedByDefault: toHtml() ?>,
+ canTrackDeselectedRecords: toHtml() ?>,
+ currentSelectionLivewireProperty: toHtml() ?>,
+ maxSelectableRecords: toHtml() ?>,
+ selectsCurrentPageOnly: toHtml() ?>,
+ $wire,
+ })"
+ class([
+ 'fi-ta',
+ 'fi-loading' => $records === null,
+ ])); ?>
+
+>
+
+
+
+
+
+
+ 'filament-actions::components.modals','data' => []] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-actions::modals'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes([]); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/2d870470bd6fffdcf1b9facb07d1049b.php b/storage/framework/views/2d870470bd6fffdcf1b9facb07d1049b.php
new file mode 100644
index 0000000..06bb37a
--- /dev/null
+++ b/storage/framework/views/2d870470bd6fffdcf1b9facb07d1049b.php
@@ -0,0 +1,225 @@
+ null,
+]));
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (in_array($__key, $__propNames)) {
+ $$__key = $$__key ?? $__value;
+ } else {
+ $__newAttributes[$__key] = $__value;
+ }
+}
+
+$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);
+
+unset($__propNames);
+unset($__newAttributes);
+
+foreach (array_filter(([
+ 'livewire' => null,
+]), 'is_string', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
+ $$__key = $$__key ?? $__value;
+}
+
+$__defined_vars = get_defined_vars();
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (array_key_exists($__key, $__defined_vars)) unset($$__key);
+}
+
+unset($__defined_vars, $__key, $__value); ?>
+
+getRenderHookScopes();
+?>
+
+
+
+
+
+
+
+
+
+
+
+ getFavicon()): ?>
+
+
+
+ getTitle() ?? ''));
+ $brandName = trim(strip_tags(filament()->getBrandName()));
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getTheme()->getHtml()); ?>
+
+ getFontHtml()); ?>
+
+ getMonoFontHtml()); ?>
+
+ getSerifFontHtml()); ?>
+
+
+
+
+ yieldPushContent('styles'); ?>
+
+
+
+
+ hasDarkMode()): ?>
+
+ hasDarkModeForced()): ?>
+
+
+
+
+
+
+
+
+
+ merge($livewire?->getExtraBodyAttributes() ?? [], escape: false)
+ ->class([
+ 'fi-body',
+ 'fi-panel-' . filament()->getId(),
+ ])); ?>
+
+ >
+
+
+
+
+
+
+ mount($__name, $__params, $key);
+
+echo $__html;
+
+unset($__html);
+unset($__name);
+unset($__params);
+unset($__split);
+if (isset($__slots)) unset($__slots);
+?>
+
+
+
+
+
+
+ hasBroadcasting() && config('filament.broadcasting.echo')): ?>
+
+
+
+ hasDarkMode() && (! filament()->hasDarkModeForced())): ?>
+
+
+
+ yieldPushContent('scripts'); ?>
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/35148eb10cca4a42fac641d4131e91d7.php b/storage/framework/views/35148eb10cca4a42fac641d4131e91d7.php
new file mode 100644
index 0000000..d3d9f2b
--- /dev/null
+++ b/storage/framework/views/35148eb10cca4a42fac641d4131e91d7.php
@@ -0,0 +1,96 @@
+
+
+<
+
+
+
+
+
+ class([
+ 'fi-wi-stats-overview-stat',
+ ])); ?>
+
+>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
color(DescriptionComponent::class, $descriptionColor)->class(['fi-wi-stats-overview-stat-description'])); ?>
+
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
color(StatsOverviewWidgetStatChartComponent::class, $chartColor)->class(['fi-wi-stats-overview-stat-chart'])); ?>
+
+ >
+
+
+
+
+
+
+
+
+>
+
\ No newline at end of file
diff --git a/storage/framework/views/3763665363475be06cdc81b77877ae07.php b/storage/framework/views/3763665363475be06cdc81b77877ae07.php
new file mode 100644
index 0000000..0e37161
--- /dev/null
+++ b/storage/framework/views/3763665363475be06cdc81b77877ae07.php
@@ -0,0 +1,424 @@
+startSection('title', 'Order #' . $customOrder->order_number . ' - Custom Order Details'); ?>
+
+startSection('styles'); ?>
+
+stopSection(); ?>
+
+startSection('content'); ?>
+
+
+
Custom Order Details
+
Order #order_number); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Order Progress
+
+ 'submitted', 'label' => 'Order\nSubmitted', 'completed' => $customOrder->status !== null],
+ ['status' => 'deposit_paid', 'label' => 'Deposit\nPaid', 'completed' => $customOrder->deposit_status === 'paid'],
+ ['status' => 'proof_sent', 'label' => 'Proofs\nSent', 'completed' => $customOrder->proofs()->exists()],
+ ['status' => 'proof_approved', 'label' => 'Proofs\nApproved', 'completed' => $customOrder->proofs()->where('status', 'approved')->exists()],
+ ['status' => 'processing', 'label' => 'Processing', 'completed' => $customOrder->status === 'processing'],
+ ['status' => 'shipped', 'label' => 'Order\nShipped', 'completed' => $customOrder->status === 'completed'],
+ ];
+
+ // Determine current step
+ $currentStep = 0;
+ if ($customOrder->status === 'completed') $currentStep = 5;
+ elseif ($customOrder->status === 'processing') $currentStep = 4;
+ elseif ($customOrder->proofs()->where('status', 'approved')->exists()) $currentStep = 3;
+ elseif ($customOrder->proofs()->exists()) $currentStep = 2;
+ elseif ($customOrder->deposit_status === 'paid') $currentStep = 1;
+ ?>
+
+ addLoop($__currentLoopData); foreach($__currentLoopData as $index => $step): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
+
+ popLoop(); $loop = $__env->getLastLoop(); ?>
+
+
+
+
+
+
+
+
Order Status
+
+
+ status))); ?>
+
+
+
+ Submitted on created_at->format('d M Y \a\t H:i')); ?>
+
+
+
+
+
+
+
+
Design Requirements
+
+
+
Order Type
+
type); ?>
+
+
+
+
Dimensions
+ specifications): ?>
+
+ specifications->length): ?>
+
+
Length:
+ specifications->length); ?>m
+
+
+ specifications->width): ?>
+
+
Width:
+ specifications->width); ?>m
+
+
+ specifications->height): ?>
+
+
Height:
+ specifications->height); ?>m
+
+
+
+
Quantity:
+ specifications->quantity); ?>
+
+
+
+
+
+
+
Print Material
+ specifications->printStock): ?>
+
specifications->printStock->name); ?>
+
+
+
+
+
Design Brief
+
customer_brief); ?>
+
+
+ specifications->special_instructions): ?>
+
+
Special Instructions
+
specifications->special_instructions); ?>
+
+
+
+
+
+ files->count() > 0): ?>
+
+
Reference Images
+
+ files; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $file): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
+
+
+
+ popLoop(); $loop = $__env->getLastLoop(); ?>
+
+
+
+
+
+ proofs->count() > 0): ?>
+
+
Design Proofs
+ proofs; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $proof): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
+
+
+
Proof iteration); ?>
+
+ status)); ?>
+
+
+
+ file_path): ?>
+
+ View Proof File
+
+
+ feedback): ?>
+
+
Feedback:
+
feedback); ?>
+
+
+
+ popLoop(); $loop = $__env->getLastLoop(); ?>
+
+
+
+
+
+
+
+
+
Order Summary
+
+
+
+
+ Design Fee:
+ Rdesign_fee, 2)); ?>
+
+ material_cost > 0): ?>
+
+ Material Cost:
+ Rmaterial_cost, 2)); ?>
+
+
+
+ Total:
+ Rtotal_cost, 2)); ?>
+
+
+
+
+
Payment Status
+
+
+
+ Deposit (20%)
+ deposit_status)); ?>
+
+
Rdeposit_amount, 2)); ?>
+
+
+
+
+ Balance (80%)
+ balance_status)); ?>
+
+
Rbalance_amount, 2)); ?>
+
+
+
+ deposit_status !== 'paid'): ?>
+
+ deposit_status === 'paid' && $customOrder->proofs->where('status', 'approved')->count() > 0 && $customOrder->balance_status !== 'paid'): ?>
+
Pay Balance (Rbalance_amount, 2)); ?>)
+
+
+
+
+
+
Payment Terms
+
+ The 20% deposit is non-refundable
+ Balance of 80% must be paid before printing begins
+ library_discount_applied): ?>
+ Design may be added to our library
+
+ Bespoke, exclusive design
+
+
+
+
+
+
+
+
+stopSection(); ?>
+
+make('layouts.app', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?>
\ No newline at end of file
diff --git a/storage/framework/views/3aca21affd23f33f24070d1b01692c31.php b/storage/framework/views/3aca21affd23f33f24070d1b01692c31.php
new file mode 100644
index 0000000..aad0915
--- /dev/null
+++ b/storage/framework/views/3aca21affd23f33f24070d1b01692c31.php
@@ -0,0 +1,17 @@
+ viewContext->mergeIntoNewEnvironment($__env); ?>
+
+ startComponent($layout->view, $layout->params); ?>
+ slot($layout->slotOrSection); ?>
+
+
+ endSlot(); ?>
+
+ viewContext->slots[-1] ?? [] as $name => $slot) {
+ $__env->slot($name, attributes: $slot->attributes->getAttributes());
+ echo $slot->toHtml();
+ $__env->endSlot();
+ }
+ ?>
+ renderComponent(); ?>
\ No newline at end of file
diff --git a/storage/framework/views/4354f4286f5df87d0ec1b7f2a28b22ae.php b/storage/framework/views/4354f4286f5df87d0ec1b7f2a28b22ae.php
new file mode 100644
index 0000000..d7d4802
--- /dev/null
+++ b/storage/framework/views/4354f4286f5df87d0ec1b7f2a28b22ae.php
@@ -0,0 +1,198 @@
+hasTopbar();
+ $isSidebarCollapsibleOnDesktop = filament()->isSidebarCollapsibleOnDesktop();
+ $isSidebarFullyCollapsibleOnDesktop = filament()->isSidebarFullyCollapsibleOnDesktop();
+ $hasTopNavigation = filament()->hasTopNavigation();
+ $hasNavigation = filament()->hasNavigation();
+ $renderHookScopes = $livewire?->getRenderHookScopes();
+ $maxContentWidth ??= (filament()->getMaxContentWidth() ?? Width::SevenExtraLarge);
+
+ if (is_string($maxContentWidth)) {
+ $maxContentWidth = Width::tryFrom($maxContentWidth) ?? $maxContentWidth;
+ }
+?>
+
+
+
+ 'filament-panels::components.layout.base','data' => ['livewire' => $livewire,'class' => \Illuminate\Support\Arr::toCssClasses([
+ 'fi-body-has-navigation' => $hasNavigation,
+ 'fi-body-has-sidebar-collapsible-on-desktop' => $isSidebarCollapsibleOnDesktop,
+ 'fi-body-has-sidebar-fully-collapsible-on-desktop' => $isSidebarFullyCollapsibleOnDesktop,
+ 'fi-body-has-topbar' => $hasTopbar,
+ 'fi-body-has-top-navigation' => $hasTopNavigation,
+ ])]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-panels::layout.base'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['livewire' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($livewire),'class' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Illuminate\Support\Arr::toCssClasses([
+ 'fi-body-has-navigation' => $hasNavigation,
+ 'fi-body-has-sidebar-collapsible-on-desktop' => $isSidebarCollapsibleOnDesktop,
+ 'fi-body-has-sidebar-fully-collapsible-on-desktop' => $isSidebarFullyCollapsibleOnDesktop,
+ 'fi-body-has-topbar' => $hasTopbar,
+ 'fi-body-has-top-navigation' => $hasTopNavigation,
+ ]))]); ?>
+
+
+
+
+ getTopbarLivewireComponent());
+
+$key = null;
+
+$key ??= \Livewire\Features\SupportCompiledWireKeys\SupportCompiledWireKeys::generateKey('lw-1155085427-0', null);
+
+$__html = app('livewire')->mount($__name, $__params, $key);
+
+echo $__html;
+
+unset($__html);
+unset($__name);
+unset($__params);
+unset($__split);
+if (isset($__slots)) unset($__slots);
+?>
+
+
+
+
+
+ x-data="{}"
+ x-bind:class="{ 'lg:fi-hidden': $store.sidebar.isOpen }"
+
+ class=" ! $isSidebarFullyCollapsibleOnDesktop,
+ ]); ?>"
+ >
+
+
+ 'filament::components.icon-button','data' => ['color' => 'gray','icon' => \Filament\Support\Icons\Heroicon::OutlinedBars3,'iconAlias' => \Filament\View\PanelsIconAlias::SIDEBAR_EXPAND_BUTTON,'iconSize' => 'lg','label' => __('filament-panels::layout.actions.sidebar.expand.label'),'xCloak' => true,'xData' => '{}','xOn:click' => '$store.sidebar.open()','class' => 'fi-layout-sidebar-toggle-btn']] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::icon-button'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['color' => 'gray','icon' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Filament\Support\Icons\Heroicon::OutlinedBars3),'icon-alias' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Filament\View\PanelsIconAlias::SIDEBAR_EXPAND_BUTTON),'icon-size' => 'lg','label' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(__('filament-panels::layout.actions.sidebar.expand.label')),'x-cloak' => true,'x-data' => '{}','x-on:click' => '$store.sidebar.open()','class' => 'fi-layout-sidebar-toggle-btn']); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getSidebarLivewireComponent());
+
+$key = null;
+
+$key ??= \Livewire\Features\SupportCompiledWireKeys\SupportCompiledWireKeys::generateKey('lw-1155085427-1', null);
+
+$__html = app('livewire')->mount($__name, $__params, $key);
+
+echo $__html;
+
+unset($__html);
+unset($__name);
+unset($__params);
+unset($__split);
+if (isset($__slots)) unset($__slots);
+?>
+
+
+
+ x-data="{}"
+ x-bind:class="{
+ 'fi-main-ctn-sidebar-open': $store.sidebar.isOpen,
+ }"
+ x-bind:style="'display: flex; opacity:1;'"
+
+
+ x-data="{}"
+ x-bind:class="{
+ 'fi-main-ctn-sidebar-open': $store.sidebar.isOpen,
+ }"
+ x-bind:style="'display: flex; opacity:1;'"
+
+
+ x-data="{}"
+ x-bind:style="'display: flex; opacity:1;'"
+
+ class="fi-main-ctn"
+ >
+
+
+
+ value}" : $maxContentWidth,
+ ]); ?>"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/455ff850486ccce453ce0236dc39aa1c.php b/storage/framework/views/455ff850486ccce453ce0236dc39aa1c.php
new file mode 100644
index 0000000..72c87c8
--- /dev/null
+++ b/storage/framework/views/455ff850486ccce453ce0236dc39aa1c.php
@@ -0,0 +1,65 @@
+getBrandName();
+ $brandLogo = filament()->getBrandLogo();
+ $brandLogoHeight = filament()->getBrandLogoHeight() ?? '1.5rem';
+ $darkModeBrandLogo = filament()->getDarkModeBrandLogo();
+ $hasDarkModeBrandLogo = filled($darkModeBrandLogo);
+
+ $getLogoClasses = fn (bool $isDarkMode): string => \Illuminate\Support\Arr::toCssClasses([
+ 'fi-logo',
+ 'fi-logo-light' => $hasDarkModeBrandLogo && (! $isDarkMode),
+ 'fi-logo-dark' => $isDarkMode,
+ ]);
+
+ $logoStyles = "height: {$brandLogoHeight}";
+?>
+
+
+
+
+
+
class([$getLogoClasses($isDarkMode)])
+ ->style([$logoStyles])); ?>
+
+ >
+
+
+
+
+
class([$getLogoClasses($isDarkMode)])
+ ->style([$logoStyles])); ?>
+
+ />
+
+
class([
+ $getLogoClasses($isDarkMode),
+ ])); ?>
+
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/4943bc92ebba41e8b0e508149542e0ad.blade.php b/storage/framework/views/4943bc92ebba41e8b0e508149542e0ad.blade.php
new file mode 100644
index 0000000..05aa909
--- /dev/null
+++ b/storage/framework/views/4943bc92ebba41e8b0e508149542e0ad.blade.php
@@ -0,0 +1,16 @@
+ viewContext->mergeIntoNewEnvironment($__env); ?>
+
+ @component($layout->view, $layout->params)
+ @slot($layout->slotOrSection)
+ {!! $content !!}
+ @endslot
+
+ viewContext->slots[-1] ?? [] as $name => $slot) {
+ $__env->slot($name, attributes: $slot->attributes->getAttributes());
+ echo $slot->toHtml();
+ $__env->endSlot();
+ }
+ ?>
+ @endcomponent
\ No newline at end of file
diff --git a/storage/framework/views/4d849bad84da6cde90ae52b9d9f50dec.php b/storage/framework/views/4d849bad84da6cde90ae52b9d9f50dec.php
new file mode 100644
index 0000000..d37c2fe
--- /dev/null
+++ b/storage/framework/views/4d849bad84da6cde90ae52b9d9f50dec.php
@@ -0,0 +1,286 @@
+
+
+ null,
+ 'badgeColor' => 'primary',
+ 'badgeSize' => Size::ExtraSmall,
+ 'color' => 'primary',
+ 'disabled' => false,
+ 'form' => null,
+ 'formId' => null,
+ 'href' => null,
+ 'icon' => null,
+ 'iconAlias' => null,
+ 'iconPosition' => IconPosition::Before,
+ 'iconSize' => null,
+ 'keyBindings' => null,
+ 'labeledFrom' => null,
+ 'labelSrOnly' => false,
+ 'loadingIndicator' => true,
+ 'outlined' => false,
+ 'size' => Size::Medium,
+ 'spaMode' => null,
+ 'tag' => 'button',
+ 'target' => null,
+ 'tooltip' => null,
+ 'type' => 'button',
+]));
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (in_array($__key, $__propNames)) {
+ $$__key = $$__key ?? $__value;
+ } else {
+ $__newAttributes[$__key] = $__value;
+ }
+}
+
+$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);
+
+unset($__propNames);
+unset($__newAttributes);
+
+foreach (array_filter(([
+ 'badge' => null,
+ 'badgeColor' => 'primary',
+ 'badgeSize' => Size::ExtraSmall,
+ 'color' => 'primary',
+ 'disabled' => false,
+ 'form' => null,
+ 'formId' => null,
+ 'href' => null,
+ 'icon' => null,
+ 'iconAlias' => null,
+ 'iconPosition' => IconPosition::Before,
+ 'iconSize' => null,
+ 'keyBindings' => null,
+ 'labeledFrom' => null,
+ 'labelSrOnly' => false,
+ 'loadingIndicator' => true,
+ 'outlined' => false,
+ 'size' => Size::Medium,
+ 'spaMode' => null,
+ 'tag' => 'button',
+ 'target' => null,
+ 'tooltip' => null,
+ 'type' => 'button',
+]), 'is_string', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
+ $$__key = $$__key ?? $__value;
+}
+
+$__defined_vars = get_defined_vars();
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (array_key_exists($__key, $__defined_vars)) unset($$__key);
+}
+
+unset($__defined_vars, $__key, $__value); ?>
+
+ IconSize::Small,
+ default => null,
+ };
+
+ $wireTarget = $loadingIndicator ? $attributes->whereStartsWith(['wire:target', 'wire:click'])->filter(fn ($value): bool => filled($value))->first() : null;
+
+ $hasFormProcessingLoadingIndicator = $type === 'submit' && filled($form);
+ $hasLoadingIndicator = filled($wireTarget) || $hasFormProcessingLoadingIndicator;
+
+ if ($hasLoadingIndicator) {
+ $loadingIndicatorTarget = html_entity_decode($wireTarget ?: $form, ENT_QUOTES);
+ }
+
+ $hasTooltip = filled($tooltip);
+?>
+
+
+
+
+ 'filament::components.icon-button','data' => ['badge' => $badge,'badgeColor' => $badgeColor,'badgeSize' => $badgeSize,'color' => $color,'disabled' => $disabled,'form' => $form,'formId' => $formId,'href' => $href,'icon' => $icon,'iconAlias' => $iconAlias,'iconSize' => $iconSize,'keyBindings' => $keyBindings,'label' => $slot,'loadingIndicator' => $loadingIndicator,'size' => $size,'spaMode' => $spaMode,'tag' => $tag,'target' => $target,'tooltip' => $tooltip,'type' => $type,'attributes' => \Filament\Support\prepare_inherited_attributes($attributes)]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::icon-button'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['badge' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($badge),'badge-color' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($badgeColor),'badge-size' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($badgeSize),'color' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($color),'disabled' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($disabled),'form' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($form),'form-id' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($formId),'href' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($href),'icon' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($icon),'icon-alias' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($iconAlias),'icon-size' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($iconSize),'key-bindings' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($keyBindings),'label' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($slot),'loading-indicator' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($loadingIndicator),'size' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($size),'spa-mode' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($spaMode),'tag' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($tag),'target' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($target),'tooltip' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($tooltip),'type' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($type),'attributes' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Filament\Support\prepare_inherited_attributes($attributes))]); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+<
+
+
+
+
+
+
+ x-bind:id="$id('key-bindings')"
+ x-mousetrap.global.map(fn (string $keyBinding): string => str_replace('+', '-', $keyBinding))->implode('.')); ?>="document.getElementById($el.id)?.click()"
+
+
+ x-tooltip="{
+ content: toHtml() ?>,
+ theme: $store.theme,
+ allowHTML: toHtml() ?>,
+ }"
+
+
+ x-data="filamentFormButton"
+ x-bind:class="{ 'fi-processing': isProcessing }"
+
+ merge([
+ 'aria-disabled' => $disabled ? 'true' : null,
+ 'aria-label' => $labelSrOnly ? trim(strip_tags($slot->toHtml())) : null,
+ 'disabled' => $disabled && blank($tooltip),
+ 'form' => $formId,
+ 'type' => $tag === 'button' ? $type : null,
+ 'wire:loading.attr' => $tag === 'button' ? 'disabled' : null,
+ 'wire:target' => ($hasLoadingIndicator && $loadingIndicatorTarget) ? $loadingIndicatorTarget : null,
+ 'x-bind:disabled' => $hasFormProcessingLoadingIndicator ? 'isProcessing' : null,
+ 'x-bind:aria-label' => ($labelSrOnly && $hasFormProcessingLoadingIndicator) ? ('isProcessing ? processingMessage : ' . \Illuminate\Support\Js::from(trim(strip_tags($slot->toHtml())))) : null,
+ ], escape: false)
+ ->when(
+ $disabled && $hasTooltip,
+ fn (ComponentAttributeBag $attributes) => $attributes->filter(
+ fn (mixed $value, string $key): bool => ! str($key)->startsWith(['href', 'x-on:', 'wire:click']),
+ ),
+ )
+ ->class([
+ 'fi-btn',
+ 'fi-disabled' => $disabled,
+ 'fi-outlined' => $outlined,
+ ($size instanceof Size) ? "fi-size-{$size->value}" : (is_string($size) ? $size : ''),
+ is_string($labeledFrom) ? "fi-labeled-from-{$labeledFrom}" : null,
+ ])
+ ->color(app(ButtonComponent::class, ['isOutlined' => $outlined]), $color)); ?>
+
+>
+
+
+ $hasLoadingIndicator,
+ 'wire:target' => $hasLoadingIndicator ? $loadingIndicatorTarget : false,
+ ])), size: $iconSize)); ?>
+
+
+
+
+ '',
+ 'wire:target' => $loadingIndicatorTarget,
+ ])), size: $iconSize)); ?>
+
+
+
+
+ 'x-cloak',
+ 'x-show' => 'isProcessing',
+ ])), size: $iconSize)); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $hasLoadingIndicator,
+ 'wire:target' => $hasLoadingIndicator ? $loadingIndicatorTarget : false,
+ ])), size: $iconSize)); ?>
+
+
+
+
+ '',
+ 'wire:target' => $loadingIndicatorTarget,
+ ])), size: $iconSize)); ?>
+
+
+
+
+ 'x-cloak',
+ 'x-show' => 'isProcessing',
+ ])), size: $iconSize)); ?>
+
+
+
+
+
+
+
+
+
+
+ color(BadgeComponent::class, $badgeColor)->class([
+ 'fi-badge',
+ ($badgeSize instanceof Size) ? "fi-size-{$badgeSize->value}" : (is_string($badgeSize) ? $badgeSize : ''),
+ ])); ?>
+
+ >
+
+
+
+
+
+
+>
+
\ No newline at end of file
diff --git a/storage/framework/views/51014d56a8722c9e42a71af3c62d81a5.php b/storage/framework/views/51014d56a8722c9e42a71af3c62d81a5.php
index e961c21..8342245 100644
--- a/storage/framework/views/51014d56a8722c9e42a71af3c62d81a5.php
+++ b/storage/framework/views/51014d56a8722c9e42a71af3c62d81a5.php
@@ -24,6 +24,20 @@
Waybill: courier_waybill_id); ?>
+
+
+ courier_shipment_id): ?>
+
+
+ 🔄 Re-download Shipment PDFs
+
+
If the shipment PDFs are corrupted, click this button to re-download them.
+
+
status === 'in_transit'): ?>
@@ -35,6 +49,20 @@
Tracking: courier_tracking_number); ?>
+
+
+ courier_shipment_id): ?>
+
+
+ 🔄 Re-download Shipment PDFs
+
+
If the shipment PDFs are corrupted, click this button to re-download them.
+
+
@@ -46,4 +74,53 @@
+
+
\ No newline at end of file
diff --git a/storage/framework/views/5f17ddd0ec9f99758a0ca6c6c13e21d7.php b/storage/framework/views/5f17ddd0ec9f99758a0ca6c6c13e21d7.php
new file mode 100644
index 0000000..4d91ae5
--- /dev/null
+++ b/storage/framework/views/5f17ddd0ec9f99758a0ca6c6c13e21d7.php
@@ -0,0 +1,23 @@
+
+
+ 'filament-panels::components.page.index','data' => []] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-panels::page'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes([]); ?>
+ content); ?>
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/61c62dff15b13791a69c395cb00e09c8.php b/storage/framework/views/61c62dff15b13791a69c395cb00e09c8.php
new file mode 100644
index 0000000..ce6e6ac
--- /dev/null
+++ b/storage/framework/views/61c62dff15b13791a69c395cb00e09c8.php
@@ -0,0 +1,59 @@
+all() as $__key => $__value) {
+ if (in_array($__key, $__propNames)) {
+ $$__key = $$__key ?? $__value;
+ } else {
+ $__newAttributes[$__key] = $__value;
+ }
+}
+
+$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);
+
+unset($__propNames);
+unset($__newAttributes);
+
+foreach (array_filter(([
+ 'icon',
+ 'theme',
+]), 'is_string', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
+ $$__key = $$__key ?? $__value;
+}
+
+$__defined_vars = get_defined_vars();
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (array_key_exists($__key, $__defined_vars)) unset($$__key);
+}
+
+unset($__defined_vars, $__key, $__value); ?>
+
+
+
+
+ \Filament\View\PanelsIconAlias::THEME_SWITCHER_LIGHT_BUTTON,
+ 'dark' => \Filament\View\PanelsIconAlias::THEME_SWITCHER_DARK_BUTTON,
+ 'system' => \Filament\View\PanelsIconAlias::THEME_SWITCHER_SYSTEM_BUTTON,
+ })); ?>
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/66ae2094a35369fe66cfe9d08e5483c4.php b/storage/framework/views/66ae2094a35369fe66cfe9d08e5483c4.php
new file mode 100644
index 0000000..83b82e0
--- /dev/null
+++ b/storage/framework/views/66ae2094a35369fe66cfe9d08e5483c4.php
@@ -0,0 +1,70 @@
+
+
+ 'gray',
+ 'icon' => null,
+ 'iconSize' => null,
+ 'tag' => 'div',
+]));
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (in_array($__key, $__propNames)) {
+ $$__key = $$__key ?? $__value;
+ } else {
+ $__newAttributes[$__key] = $__value;
+ }
+}
+
+$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);
+
+unset($__propNames);
+unset($__newAttributes);
+
+foreach (array_filter(([
+ 'color' => 'gray',
+ 'icon' => null,
+ 'iconSize' => null,
+ 'tag' => 'div',
+]), 'is_string', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
+ $$__key = $$__key ?? $__value;
+}
+
+$__defined_vars = get_defined_vars();
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (array_key_exists($__key, $__defined_vars)) unset($$__key);
+}
+
+unset($__defined_vars, $__key, $__value); ?>
+
+
+
+<
+
+ class([
+ 'fi-dropdown-header',
+ ])
+ ->color(HeaderComponent::class, $color)); ?>
+
+>
+
+
+
+
+
+
+
+>
+
\ No newline at end of file
diff --git a/storage/framework/views/67a7f07200c3d8fa00ba4fe72a50cf46.php b/storage/framework/views/67a7f07200c3d8fa00ba4fe72a50cf46.php
new file mode 100644
index 0000000..cd3e390
--- /dev/null
+++ b/storage/framework/views/67a7f07200c3d8fa00ba4fe72a50cf46.php
@@ -0,0 +1,52 @@
+
+
+
+
+ addLoop($__currentLoopData); foreach($__currentLoopData as $notification): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
+
+
+ popLoop(); $loop = $__env->getLastLoop(); ?>
+
+
+ getBroadcastChannel()): ?>
+
+
+ push('scripts', $__output, $__scriptKey)
+ ?>
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/6e51499aeff83c919e16b854057abefc.php b/storage/framework/views/6e51499aeff83c919e16b854057abefc.php
new file mode 100644
index 0000000..f265b16
--- /dev/null
+++ b/storage/framework/views/6e51499aeff83c919e16b854057abefc.php
@@ -0,0 +1,342 @@
+ false,
+ 'collapsible' => true,
+ 'icon' => null,
+ 'items' => [],
+ 'label' => null,
+ 'sidebarCollapsible' => true,
+ 'subNavigation' => false,
+]));
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (in_array($__key, $__propNames)) {
+ $$__key = $$__key ?? $__value;
+ } else {
+ $__newAttributes[$__key] = $__value;
+ }
+}
+
+$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);
+
+unset($__propNames);
+unset($__newAttributes);
+
+foreach (array_filter(([
+ 'active' => false,
+ 'collapsible' => true,
+ 'icon' => null,
+ 'items' => [],
+ 'label' => null,
+ 'sidebarCollapsible' => true,
+ 'subNavigation' => false,
+]), 'is_string', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
+ $$__key = $$__key ?? $__value;
+}
+
+$__defined_vars = get_defined_vars();
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (array_key_exists($__key, $__defined_vars)) unset($$__key);
+}
+
+unset($__defined_vars, $__key, $__value); ?>
+
+isSidebarCollapsibleOnDesktop();
+ $hasDropdown = filled($label) && filled($icon) && $sidebarCollapsible;
+?>
+
+
toHtml() ?> }"
+ data-group-label=""
+ x-bind:class="{ 'fi-collapsed': $store.sidebar.groupIsCollapsed(label) }"
+ class([
+ 'fi-sidebar-group',
+ 'fi-active' => $active,
+ 'fi-collapsible' => $collapsible,
+ ])); ?>
+
+>
+
+
+ x-on:click="$store.sidebar.toggleCollapsedGroup(label)"
+ role="button"
+
+
+ x-show="$store.sidebar.isOpen"
+ x-transition:enter="fi-transition-enter"
+ x-transition:enter-start="fi-transition-enter-start"
+ x-transition:enter-end="fi-transition-enter-end"
+
+ class="fi-sidebar-group-btn"
+ >
+
+
+
+
+
+
+
+
+
+
+ 'filament::components.icon-button','data' => ['color' => 'gray','icon' => \Filament\Support\Icons\Heroicon::ChevronUp,'iconAlias' => \Filament\View\PanelsIconAlias::SIDEBAR_GROUP_COLLAPSE_BUTTON,'label' => $label,'xBind:ariaExpanded' => '! $store.sidebar.groupIsCollapsed(label)','xOn:click.stop' => '$store.sidebar.toggleCollapsedGroup(label)','class' => 'fi-sidebar-group-collapse-btn']] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::icon-button'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['color' => 'gray','icon' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Filament\Support\Icons\Heroicon::ChevronUp),'icon-alias' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Filament\View\PanelsIconAlias::SIDEBAR_GROUP_COLLAPSE_BUTTON),'label' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($label),'x-bind:aria-expanded' => '! $store.sidebar.groupIsCollapsed(label)','x-on:click.stop' => '$store.sidebar.toggleCollapsedGroup(label)','class' => 'fi-sidebar-group-collapse-btn']); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'filament::components.dropdown.index','data' => ['placement' => (__('filament-panels::layout.direction') === 'rtl') ? 'left-start' : 'right-start','xShow' => '! $store.sidebar.isOpen']] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::dropdown'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['placement' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute((__('filament-panels::layout.direction') === 'rtl') ? 'left-start' : 'right-start'),'x-show' => '! $store.sidebar.isOpen']); ?>
+ slot('trigger', null, []); ?>
+
+ endSlot(); ?>
+
+ getChildItems()) {
+ $lists[] = [
+ $item,
+ ...$childItems,
+ ];
+ $lists[] = [];
+
+ continue;
+ }
+
+ if (empty($lists)) {
+ $lists[] = [$item];
+
+ continue;
+ }
+
+ $lists[count($lists) - 1][] = $item;
+ }
+
+ if (empty($lists[count($lists) - 1])) {
+ array_pop($lists);
+ }
+ ?>
+
+
+
+
+ 'filament::components.dropdown.header','data' => []] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::dropdown.header'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes([]); ?>
+
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+ addLoop($__currentLoopData); foreach($__currentLoopData as $list): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
+
+
+ 'filament::components.dropdown.list.index','data' => []] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::dropdown.list'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes([]); ?>
+ addLoop($__currentLoopData); foreach($__currentLoopData as $item): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
+ isActive();
+ $itemBadge = $item->getBadge();
+ $itemBadgeColor = $item->getBadgeColor();
+ $itemBadgeTooltip = $item->getBadgeTooltip();
+ $itemUrl = $item->getUrl();
+ $itemIcon = $itemIsActive ? ($item->getActiveIcon() ?? $item->getIcon()) : $item->getIcon();
+ $shouldItemOpenUrlInNewTab = $item->shouldOpenUrlInNewTab();
+ ?>
+
+
+
+ 'filament::components.dropdown.list.item','data' => ['badge' => $itemBadge,'badgeColor' => $itemBadgeColor,'badgeTooltip' => $itemBadgeTooltip,'color' => $itemIsActive ? 'primary' : 'gray','href' => $itemUrl,'icon' => $itemIcon,'tag' => 'a','target' => $shouldItemOpenUrlInNewTab ? '_blank' : null]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::dropdown.list.item'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['badge' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemBadge),'badge-color' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemBadgeColor),'badge-tooltip' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemBadgeTooltip),'color' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemIsActive ? 'primary' : 'gray'),'href' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemUrl),'icon' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemIcon),'tag' => 'a','target' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($shouldItemOpenUrlInNewTab ? '_blank' : null)]); ?>
+ getLabel()); ?>
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+ popLoop(); $loop = $__env->getLastLoop(); ?>
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+ popLoop(); $loop = $__env->getLastLoop(); ?>
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ x-show="$store.sidebar.isOpen ? ! $store.sidebar.groupIsCollapsed(label) : ! toHtml() ?>"
+
+ x-show="! $store.sidebar.groupIsCollapsed(label)"
+
+ x-collapse.duration.200ms
+
+
+ x-transition:enter="fi-transition-enter"
+ x-transition:enter-start="fi-transition-enter-start"
+ x-transition:enter-end="fi-transition-enter-end"
+
+ class="fi-sidebar-group-items"
+ >
+ addLoop($__currentLoopData); foreach($__currentLoopData as $item): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
+ isChildItemsActive();
+ $isItemActive = (! $isItemChildItemsActive) && $item->isActive();
+ $itemActiveIcon = $item->getActiveIcon();
+ $itemBadge = $item->getBadge();
+ $itemBadgeColor = $item->getBadgeColor();
+ $itemBadgeTooltip = $item->getBadgeTooltip();
+ $itemChildItems = $item->getChildItems();
+ $itemIcon = $item->getIcon();
+ $shouldItemOpenUrlInNewTab = $item->shouldOpenUrlInNewTab();
+ $itemUrl = $item->getUrl();
+
+ if ($icon) {
+ if ($hasDropdown || (blank($itemIcon) && blank($itemActiveIcon))) {
+ $itemIcon = null;
+ $itemActiveIcon = null;
+ } else {
+ throw new \Exception('Navigation group [' . $label . '] has an icon but one or more of its items also have icons. Either the group or its items can have icons, but not both. This is to ensure a proper user experience.');
+ }
+ }
+ ?>
+
+
+
+ 'filament-panels::components.sidebar.item','data' => ['active' => $isItemActive,'activeChildItems' => $isItemChildItemsActive,'activeIcon' => $itemActiveIcon,'badge' => $itemBadge,'badgeColor' => $itemBadgeColor,'badgeTooltip' => $itemBadgeTooltip,'childItems' => $itemChildItems,'first' => $loop->first,'grouped' => filled($label),'icon' => $itemIcon,'last' => $loop->last,'shouldOpenUrlInNewTab' => $shouldItemOpenUrlInNewTab,'sidebarCollapsible' => $sidebarCollapsible,'subNavigation' => $subNavigation,'url' => $itemUrl]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-panels::sidebar.item'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['active' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($isItemActive),'active-child-items' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($isItemChildItemsActive),'active-icon' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemActiveIcon),'badge' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemBadge),'badge-color' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemBadgeColor),'badge-tooltip' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemBadgeTooltip),'child-items' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemChildItems),'first' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($loop->first),'grouped' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(filled($label)),'icon' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemIcon),'last' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($loop->last),'should-open-url-in-new-tab' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($shouldItemOpenUrlInNewTab),'sidebar-collapsible' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($sidebarCollapsible),'sub-navigation' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($subNavigation),'url' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemUrl)]); ?>
+ getLabel()); ?>
+
+
+
+ slot('icon', null, []); ?>
+
+
+ endSlot(); ?>
+
+
+
+ slot('activeIcon', null, []); ?>
+
+
+ endSlot(); ?>
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+ popLoop(); $loop = $__env->getLastLoop(); ?>
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/6e571b420efa227e90ca35c72263dced.php b/storage/framework/views/6e571b420efa227e90ca35c72263dced.php
new file mode 100644
index 0000000..82073a8
--- /dev/null
+++ b/storage/framework/views/6e571b420efa227e90ca35c72263dced.php
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+ merge([
+ 'id' => $id,
+ ], escape: false)
+ ->merge($extraAttributes, escape: false)); ?>
+
+ >
+
+
+
+ mount($__name, $__params, $key);
+
+echo $__html;
+
+unset($__html);
+unset($__name);
+unset($__params);
+unset($__split);
+if (isset($__slots)) unset($__slots);
+?>
+
+ mount($__name, $__params, $key);
+
+echo $__html;
+
+unset($__html);
+unset($__name);
+unset($__params);
+unset($__split);
+if (isset($__slots)) unset($__slots);
+?>
+
+
+ '; ?>
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/77a9bf0e867fdca3a11ef51783d0e448.php b/storage/framework/views/77a9bf0e867fdca3a11ef51783d0e448.php
new file mode 100644
index 0000000..e0d777d
--- /dev/null
+++ b/storage/framework/views/77a9bf0e867fdca3a11ef51783d0e448.php
@@ -0,0 +1,12 @@
+
merge([
+ 'id' => $getId(),
+ ], escape: false)
+ ->merge($getExtraAttributes(), escape: false)); ?>
+
+>
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/793878bac7f49084851c6a750de7b680.php b/storage/framework/views/793878bac7f49084851c6a750de7b680.php
new file mode 100644
index 0000000..295acd3
--- /dev/null
+++ b/storage/framework/views/793878bac7f49084851c6a750de7b680.php
@@ -0,0 +1,206 @@
+
+
+ null,
+ 'aside' => false,
+ 'collapsed' => false,
+ 'collapseId' => null,
+ 'collapsible' => false,
+ 'compact' => false,
+ 'contained' => true,
+ 'contentBefore' => false,
+ 'description' => null,
+ 'divided' => false,
+ 'footer' => null,
+ 'hasContentEl' => true,
+ 'heading' => null,
+ 'headingTag' => 'h2',
+ 'icon' => null,
+ 'iconColor' => 'gray',
+ 'iconSize' => null,
+ 'persistCollapsed' => false,
+ 'secondary' => false,
+]));
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (in_array($__key, $__propNames)) {
+ $$__key = $$__key ?? $__value;
+ } else {
+ $__newAttributes[$__key] = $__value;
+ }
+}
+
+$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);
+
+unset($__propNames);
+unset($__newAttributes);
+
+foreach (array_filter(([
+ 'afterHeader' => null,
+ 'aside' => false,
+ 'collapsed' => false,
+ 'collapseId' => null,
+ 'collapsible' => false,
+ 'compact' => false,
+ 'contained' => true,
+ 'contentBefore' => false,
+ 'description' => null,
+ 'divided' => false,
+ 'footer' => null,
+ 'hasContentEl' => true,
+ 'heading' => null,
+ 'headingTag' => 'h2',
+ 'icon' => null,
+ 'iconColor' => 'gray',
+ 'iconSize' => null,
+ 'persistCollapsed' => false,
+ 'secondary' => false,
+]), 'is_string', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
+ $$__key = $$__key ?? $__value;
+}
+
+$__defined_vars = get_defined_vars();
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (array_key_exists($__key, $__defined_vars)) unset($$__key);
+}
+
+unset($__defined_vars, $__key, $__value); ?>
+
+
+
+
+ x-on:collapse-section.window="if ($event.detail.id == toHtml() ?> ?? $el.id) isCollapsed = true"
+ x-on:expand="isCollapsed = false"
+ x-on:expand-section.window="if ($event.detail.id == toHtml() ?> ?? $el.id) isCollapsed = false"
+ x-on:open-section.window="if ($event.detail.id == toHtml() ?> ?? $el.id) isCollapsed = false"
+ x-on:toggle-section.window="if ($event.detail.id == toHtml() ?> ?? $el.id) isCollapsed = ! isCollapsed"
+ x-bind:class="isCollapsed && 'fi-collapsed'"
+
+ class([
+ 'fi-section',
+ 'fi-section-not-contained' => ! $contained,
+ 'fi-section-has-content-before' => $contentBefore,
+ 'fi-section-has-header' => $hasHeader,
+ 'fi-aside' => $aside,
+ 'fi-compact' => $compact,
+ 'fi-collapsible' => $collapsible,
+ 'fi-divided' => $divided,
+ 'fi-secondary' => $secondary,
+ ])); ?>
+
+>
+
+
+ x-on:click="isCollapsed = ! isCollapsed"
+
+ class="fi-section-header"
+ >
+ color(IconComponent::class, $iconColor), size: $iconSize ?? IconSize::Large)); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'filament::components.icon-button','data' => ['color' => 'gray','icon' => \Filament\Support\Icons\Heroicon::ChevronUp,'iconAlias' => \Filament\Support\View\SupportIconAlias::SECTION_COLLAPSE_BUTTON,'xOn:click.stop' => 'isCollapsed = ! isCollapsed','class' => 'fi-section-collapse-btn']] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::icon-button'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['color' => 'gray','icon' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Filament\Support\Icons\Heroicon::ChevronUp),'icon-alias' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Filament\Support\View\SupportIconAlias::SECTION_COLLAPSE_BUTTON),'x-on:click.stop' => 'isCollapsed = ! isCollapsed','class' => 'fi-section-collapse-btn']); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ x-bind:aria-expanded="(! isCollapsed).toString()"
+
+ x-cloak
+
+
+ class="fi-section-content-ctn"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/80c9a0a6308b17fbb7bf93fb62efe7a3.php b/storage/framework/views/80c9a0a6308b17fbb7bf93fb62efe7a3.php
new file mode 100644
index 0000000..d1f2526
--- /dev/null
+++ b/storage/framework/views/80c9a0a6308b17fbb7bf93fb62efe7a3.php
@@ -0,0 +1,18 @@
+hasUnsavedChangesAlerts()): ?>
+
+
+ push('scripts', $__output, $__scriptKey)
+ ?>
+
+
\ No newline at end of file
diff --git a/storage/framework/views/81d5610fd0a2b1fb1ea0942d4e877062.php b/storage/framework/views/81d5610fd0a2b1fb1ea0942d4e877062.php
new file mode 100644
index 0000000..7e0e64f
--- /dev/null
+++ b/storage/framework/views/81d5610fd0a2b1fb1ea0942d4e877062.php
@@ -0,0 +1,378 @@
+
+ getNavigation();
+ $isRtl = __('filament-panels::layout.direction') === 'rtl';
+ $isSidebarCollapsibleOnDesktop = filament()->isSidebarCollapsibleOnDesktop();
+ $isSidebarFullyCollapsibleOnDesktop = filament()->isSidebarFullyCollapsibleOnDesktop();
+ $hasNavigation = filament()->hasNavigation();
+ $hasTopbar = filament()->hasTopbar();
+ ?>
+
+
+
+ x-cloak
+
+ x-cloak="-lg"
+
+ x-bind:class="{ 'fi-sidebar-open': $store.sidebar.isOpen }"
+ class="fi-sidebar fi-main-sidebar"
+ >
+
+
+
+
+
+ hasTenancy() && filament()->hasTenantMenu()): ?>
+
+
+ 'filament-panels::components.tenant-menu','data' => []] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-panels::tenant-menu'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes([]); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+ isGlobalSearchEnabled() && filament()->getGlobalSearchPosition() === \Filament\Enums\GlobalSearchPosition::Sidebar): ?>
+
+ x-show="$store.sidebar.isOpen"
+
+ >
+ mount($__name, $__params, $key);
+
+echo $__html;
+
+unset($__html);
+unset($__name);
+unset($__params);
+unset($__split);
+if (isset($__slots)) unset($__slots);
+?>
+
+
+
+
+
+ auth()->check();
+ $hasDatabaseNotificationsInSidebar = filament()->hasDatabaseNotifications() && filament()->getDatabaseNotificationsPosition() === \Filament\Enums\DatabaseNotificationsPosition::Sidebar;
+ $hasUserMenuInSidebar = filament()->hasUserMenu() && filament()->getUserMenuPosition() === \Filament\Enums\UserMenuPosition::Sidebar;
+ $shouldRenderFooter = $isAuthenticated && ($hasDatabaseNotificationsInSidebar || $hasUserMenuInSidebar);
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+ 'filament-actions::components.modals','data' => []] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-actions::modals'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes([]); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/8225b4ab5e177c6ce756bf6bd43eb60c.php b/storage/framework/views/8225b4ab5e177c6ce756bf6bd43eb60c.php
new file mode 100644
index 0000000..c65fd1a
--- /dev/null
+++ b/storage/framework/views/8225b4ab5e177c6ce756bf6bd43eb60c.php
@@ -0,0 +1,45 @@
+ false,
+ 'inlineSuffix' => false,
+]));
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (in_array($__key, $__propNames)) {
+ $$__key = $$__key ?? $__value;
+ } else {
+ $__newAttributes[$__key] = $__value;
+ }
+}
+
+$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);
+
+unset($__propNames);
+unset($__newAttributes);
+
+foreach (array_filter(([
+ 'inlinePrefix' => false,
+ 'inlineSuffix' => false,
+]), 'is_string', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
+ $$__key = $$__key ?? $__value;
+}
+
+$__defined_vars = get_defined_vars();
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (array_key_exists($__key, $__defined_vars)) unset($$__key);
+}
+
+unset($__defined_vars, $__key, $__value); ?>
+
+
class([
+ 'fi-input',
+ 'fi-input-has-inline-prefix' => $inlinePrefix,
+ 'fi-input-has-inline-suffix' => $inlineSuffix,
+ ])); ?>
+
+/>
+
\ No newline at end of file
diff --git a/storage/framework/views/8848a11e123e6274892c44c05c02e2c0.php b/storage/framework/views/8848a11e123e6274892c44c05c02e2c0.php
new file mode 100644
index 0000000..cb93c1f
--- /dev/null
+++ b/storage/framework/views/8848a11e123e6274892c44c05c02e2c0.php
@@ -0,0 +1,21 @@
+hasActionsModalRendered)): ?>
+
+ getMountedActions(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $action): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
+ last) || $this->mountedActionShouldOpenModal()): ?>
+ toModalHtmlable()); ?>
+
+
+ popLoop(); $loop = $__env->getLastLoop(); ?>
+
+
+ hasActionsModalRendered = true;
+ ?>
+
+
\ No newline at end of file
diff --git a/storage/framework/views/8e965d04afbd3d125b04967406aafa03.php b/storage/framework/views/8e965d04afbd3d125b04967406aafa03.php
new file mode 100644
index 0000000..52df10d
--- /dev/null
+++ b/storage/framework/views/8e965d04afbd3d125b04967406aafa03.php
@@ -0,0 +1,189 @@
+
+
+ null,
+ 'badgeColor' => 'primary',
+ 'badgeSize' => Size::ExtraSmall,
+ 'color' => 'primary',
+ 'disabled' => false,
+ 'form' => null,
+ 'formId' => null,
+ 'href' => null,
+ 'icon' => null,
+ 'iconAlias' => null,
+ 'iconSize' => null,
+ 'keyBindings' => null,
+ 'label' => null,
+ 'loadingIndicator' => true,
+ 'size' => Size::Medium,
+ 'spaMode' => null,
+ 'tag' => 'button',
+ 'target' => null,
+ 'tooltip' => null,
+ 'type' => 'button',
+]));
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (in_array($__key, $__propNames)) {
+ $$__key = $$__key ?? $__value;
+ } else {
+ $__newAttributes[$__key] = $__value;
+ }
+}
+
+$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);
+
+unset($__propNames);
+unset($__newAttributes);
+
+foreach (array_filter(([
+ 'badge' => null,
+ 'badgeColor' => 'primary',
+ 'badgeSize' => Size::ExtraSmall,
+ 'color' => 'primary',
+ 'disabled' => false,
+ 'form' => null,
+ 'formId' => null,
+ 'href' => null,
+ 'icon' => null,
+ 'iconAlias' => null,
+ 'iconSize' => null,
+ 'keyBindings' => null,
+ 'label' => null,
+ 'loadingIndicator' => true,
+ 'size' => Size::Medium,
+ 'spaMode' => null,
+ 'tag' => 'button',
+ 'target' => null,
+ 'tooltip' => null,
+ 'type' => 'button',
+]), 'is_string', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
+ $$__key = $$__key ?? $__value;
+}
+
+$__defined_vars = get_defined_vars();
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (array_key_exists($__key, $__defined_vars)) unset($$__key);
+}
+
+unset($__defined_vars, $__key, $__value); ?>
+
+ IconSize::Small,
+ Size::Large, Size::ExtraLarge => IconSize::Large,
+ default => null,
+ };
+
+ $wireTarget = $loadingIndicator ? $attributes->whereStartsWith(['wire:target', 'wire:click'])->filter(fn ($value): bool => filled($value))->first() : null;
+
+ $hasLoadingIndicator = filled($wireTarget) || ($type === 'submit' && filled($form));
+
+ if ($hasLoadingIndicator) {
+ $loadingIndicatorTarget = html_entity_decode($wireTarget ?: $form, ENT_QUOTES);
+ }
+
+ $hasTooltip = $hasTooltip = filled($tooltip);
+?>
+
+<
+
+
+
+
+
+
+ x-bind:id="$id('key-bindings')"
+ x-mousetrap.global.map(fn (string $keyBinding): string => str_replace('+', '-', $keyBinding))->implode('.')); ?>="document.getElementById($el.id)?.click()"
+
+
+ x-tooltip="{
+ content: toHtml() ?>,
+ theme: $store.theme,
+ allowHTML: toHtml() ?>,
+ }"
+
+ merge([
+ 'aria-disabled' => $disabled ? 'true' : null,
+ 'aria-label' => $label,
+ 'disabled' => $disabled && blank($tooltip),
+ 'form' => $formId,
+ 'type' => $tag === 'button' ? $type : null,
+ 'wire:loading.attr' => $tag === 'button' ? 'disabled' : null,
+ 'wire:target' => ($hasLoadingIndicator && $loadingIndicatorTarget) ? $loadingIndicatorTarget : null,
+ ], escape: false)
+ ->merge([
+ 'title' => $hasTooltip ? null : $label,
+ ], escape: true)
+ ->when(
+ $disabled && $hasTooltip,
+ fn (ComponentAttributeBag $attributes) => $attributes->filter(
+ fn (mixed $value, string $key): bool => ! str($key)->startsWith(['href', 'x-on:', 'wire:click']),
+ ),
+ )
+ ->class([
+ 'fi-icon-btn',
+ 'fi-disabled' => $disabled,
+ ($size instanceof Size) ? "fi-size-{$size->value}" : (is_string($size) ? $size : ''),
+ ])
+ ->color(IconButtonComponent::class, $color)); ?>
+
+>
+ $hasLoadingIndicator,
+ 'wire:target' => $hasLoadingIndicator ? $loadingIndicatorTarget : false,
+ ])), size: $iconSize)); ?>
+
+
+
+ '',
+ 'wire:target' => $loadingIndicatorTarget,
+ ])), size: $iconSize)); ?>
+
+
+
+
+
+
+
+
+
+ color(BadgeComponent::class, $badgeColor)->class([
+ 'fi-badge',
+ ($badgeSize instanceof Size) ? "fi-size-{$badgeSize->value}" : (is_string($badgeSize) ? $badgeSize : ''),
+ ])); ?>
+
+ >
+
+
+
+
+
+
+>
+
\ No newline at end of file
diff --git a/storage/framework/views/8f77655fced827f2335dc0fd5b0b86c6.php b/storage/framework/views/8f77655fced827f2335dc0fd5b0b86c6.php
new file mode 100644
index 0000000..2a7e0be
--- /dev/null
+++ b/storage/framework/views/8f77655fced827f2335dc0fd5b0b86c6.php
@@ -0,0 +1,112 @@
+ null,
+ 'availableWidth' => null,
+ 'flip' => true,
+ 'maxHeight' => null,
+ 'offset' => 8,
+ 'placement' => null,
+ 'shift' => false,
+ 'size' => false,
+ 'sizePadding' => 16,
+ 'teleport' => false,
+ 'trigger' => null,
+ 'width' => null,
+]));
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (in_array($__key, $__propNames)) {
+ $$__key = $$__key ?? $__value;
+ } else {
+ $__newAttributes[$__key] = $__value;
+ }
+}
+
+$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);
+
+unset($__propNames);
+unset($__newAttributes);
+
+foreach (array_filter(([
+ 'availableHeight' => null,
+ 'availableWidth' => null,
+ 'flip' => true,
+ 'maxHeight' => null,
+ 'offset' => 8,
+ 'placement' => null,
+ 'shift' => false,
+ 'size' => false,
+ 'sizePadding' => 16,
+ 'teleport' => false,
+ 'trigger' => null,
+ 'width' => null,
+]), 'is_string', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
+ $$__key = $$__key ?? $__value;
+}
+
+$__defined_vars = get_defined_vars();
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (array_key_exists($__key, $__defined_vars)) unset($$__key);
+}
+
+unset($__defined_vars, $__key, $__value); ?>
+
+ $availableHeight,
+ 'availableWidth' => $availableWidth,
+ 'padding' => $sizePadding,
+ ])->filter()->toJson();
+
+ if (is_string($width)) {
+ $width = Width::tryFrom($width) ?? $width;
+ }
+?>
+
+
class(['fi-dropdown'])); ?>
+
+>
+
attributes->class(['fi-dropdown-trigger'])); ?>
+
+ >
+
+
+
+
+
+
="{ offset: , }"
+ x-ref="panel"
+ x-transition:enter-start="fi-opacity-0"
+ x-transition:leave-end="fi-opacity-0"
+ has('wire:key')): ?>
+ wire:ignore.self
+ wire:key="get('wire:key')); ?>.panel"
+
+ class="value}" : (is_string($width) ? $width : ''),
+ 'fi-scrollable' => $maxHeight || $size,
+ ]); ?>"
+ style=" $maxHeight,
+ ]) ?>"
+ >
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/963a42c38bd8e0fdfe0b4e765ea9f100.php b/storage/framework/views/963a42c38bd8e0fdfe0b4e765ea9f100.php
index a5d2f47..c4eb84f 100644
--- a/storage/framework/views/963a42c38bd8e0fdfe0b4e765ea9f100.php
+++ b/storage/framework/views/963a42c38bd8e0fdfe0b4e765ea9f100.php
@@ -41,7 +41,9 @@
}
.form-group input,
- .form-group textarea {
+ .form-group textarea,
+ .form-group select,
+ .address-search-group input {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
@@ -56,12 +58,52 @@
}
.form-group input:focus,
- .form-group textarea:focus {
+ .form-group textarea:focus,
+ .form-group select:focus,
+ .address-search-group input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(45, 80, 71, 0.1);
}
+ /* Google Places Autocomplete styling */
+ .pac-container {
+ border-radius: 4px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ margin-top: 4px;
+ }
+
+ .pac-item {
+ padding: 0.75rem;
+ cursor: pointer;
+ }
+
+ .pac-item:hover {
+ background-color: #f0f0f0;
+ }
+
+ .address-search-group {
+ margin-bottom: 1.5rem;
+ position: relative;
+ }
+
+ .address-clear-btn {
+ position: absolute;
+ right: 12px;
+ top: 32px;
+ background: none;
+ border: none;
+ color: #999;
+ cursor: pointer;
+ font-size: 1.2rem;
+ padding: 0;
+ display: none;
+ }
+
+ .address-clear-btn.show {
+ display: block;
+ }
+
.order-summary {
height: fit-content;
}
@@ -195,58 +237,76 @@
Delivery Information
- Full Name
-
+ Full Name*
+
- Email Address
-
+ Email Address*
+
- Phone Number
+ Phone Number*
- Street Address
-
-
-
-
- Suburb/Local Area
-
-
-
-
- City
-
-
-
-
- Province/Zone
-
-
-
-
- Postal Code
-
-
-
-
- Country
-
-
-
-
- Address Type
+ Address Type *
>Residential
>Business
+
+ Business Name *
+
+
+
+
+
+ Delivery Address (Type to search)
+
+ ✕
+
+
+
+
Order Notes (Optional)
@@ -319,6 +379,184 @@
+
+
+
+
+
+ ⚠️ Configuration Issue: Google Places API key is not configured. Please add GOOGLE_PLACES_API_KEY to your .env file.
+
+
+
+
+
+
+
+
+
+
+
+
+
stopSection(); ?>
make('layouts.app', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?>
\ No newline at end of file
diff --git a/storage/framework/views/963fa7931519a2bda4d512a9065cf86a.php b/storage/framework/views/963fa7931519a2bda4d512a9065cf86a.php
new file mode 100644
index 0000000..d722589
--- /dev/null
+++ b/storage/framework/views/963fa7931519a2bda4d512a9065cf86a.php
@@ -0,0 +1,21 @@
+
+
+
+
+addLoop($__currentLoopData); foreach($__currentLoopData as $asset): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
+ isLoadedOnRequest()): ?>
+ getHtml()); ?>
+
+
+popLoop(); $loop = $__env->getLastLoop(); ?>
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/97867fde05c61ffbb4be32bd2ec46ec7.php b/storage/framework/views/97867fde05c61ffbb4be32bd2ec46ec7.php
new file mode 100644
index 0000000..3035094
--- /dev/null
+++ b/storage/framework/views/97867fde05c61ffbb4be32bd2ec46ec7.php
@@ -0,0 +1,127 @@
+ [],
+ 'actionsAlignment' => null,
+ 'breadcrumbs' => [],
+ 'heading' => null,
+ 'subheading' => null,
+]));
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (in_array($__key, $__propNames)) {
+ $$__key = $$__key ?? $__value;
+ } else {
+ $__newAttributes[$__key] = $__value;
+ }
+}
+
+$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);
+
+unset($__propNames);
+unset($__newAttributes);
+
+foreach (array_filter(([
+ 'actions' => [],
+ 'actionsAlignment' => null,
+ 'breadcrumbs' => [],
+ 'heading' => null,
+ 'subheading' => null,
+]), 'is_string', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
+ $$__key = $$__key ?? $__value;
+}
+
+$__defined_vars = get_defined_vars();
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (array_key_exists($__key, $__defined_vars)) unset($$__key);
+}
+
+unset($__defined_vars, $__key, $__value); ?>
+
+
class([
+ 'fi-header',
+ 'fi-header-has-breadcrumbs' => $breadcrumbs,
+ ])); ?>
+
+>
+
+
+
+
+ 'filament::components.breadcrumbs','data' => ['breadcrumbs' => $breadcrumbs]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::breadcrumbs'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['breadcrumbs' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($breadcrumbs)]); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getRenderHookScopes());
+ $afterActions = \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::PAGE_HEADER_ACTIONS_AFTER, scopes: $this->getRenderHookScopes());
+ ?>
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/9ef0dfd29e0ee94f00e7cbd0892f433c.php b/storage/framework/views/9ef0dfd29e0ee94f00e7cbd0892f433c.php
new file mode 100644
index 0000000..ab482c7
--- /dev/null
+++ b/storage/framework/views/9ef0dfd29e0ee94f00e7cbd0892f433c.php
@@ -0,0 +1,75 @@
+
+
+
+ 'filament-panels::components.theme-switcher.button','data' => ['icon' => \Filament\Support\Icons\Heroicon::Sun,'theme' => 'light']] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-panels::theme-switcher.button'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['icon' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Filament\Support\Icons\Heroicon::Sun),'theme' => 'light']); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+ 'filament-panels::components.theme-switcher.button','data' => ['icon' => \Filament\Support\Icons\Heroicon::Moon,'theme' => 'dark']] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-panels::theme-switcher.button'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['icon' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Filament\Support\Icons\Heroicon::Moon),'theme' => 'dark']); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+ 'filament-panels::components.theme-switcher.button','data' => ['icon' => \Filament\Support\Icons\Heroicon::ComputerDesktop,'theme' => 'system']] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-panels::theme-switcher.button'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['icon' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Filament\Support\Icons\Heroicon::ComputerDesktop),'theme' => 'system']); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/9f8ad04ac0e5cad2ba5cbdf9e6b839ac.php b/storage/framework/views/9f8ad04ac0e5cad2ba5cbdf9e6b839ac.php
new file mode 100644
index 0000000..f83ada1
--- /dev/null
+++ b/storage/framework/views/9f8ad04ac0e5cad2ba5cbdf9e6b839ac.php
@@ -0,0 +1,325 @@
+ null,
+]));
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (in_array($__key, $__propNames)) {
+ $$__key = $$__key ?? $__value;
+ } else {
+ $__newAttributes[$__key] = $__value;
+ }
+}
+
+$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);
+
+unset($__propNames);
+unset($__newAttributes);
+
+foreach (array_filter(([
+ 'position' => null,
+]), 'is_string', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
+ $$__key = $$__key ?? $__value;
+}
+
+$__defined_vars = get_defined_vars();
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (array_key_exists($__key, $__defined_vars)) unset($$__key);
+}
+
+unset($__defined_vars, $__key, $__value); ?>
+
+auth()->user();
+
+ $items = $this->getUserMenuItems();
+
+ $itemsBeforeAndAfterThemeSwitcher = collect($items)
+ ->groupBy(fn (Action $item): bool => $item->getSort() < 0, preserveKeys: true)
+ ->all();
+ $itemsBeforeThemeSwitcher = $itemsBeforeAndAfterThemeSwitcher[true] ?? collect();
+ $itemsAfterThemeSwitcher = $itemsBeforeAndAfterThemeSwitcher[false] ?? collect();
+
+ $hasProfileHeader = $itemsBeforeThemeSwitcher->has('profile') &&
+ blank(($item = Arr::first($itemsBeforeThemeSwitcher))->getUrl()) &&
+ (! $item->hasAction());
+
+ if ($itemsBeforeThemeSwitcher->has('profile')) {
+ $itemsBeforeThemeSwitcher = $itemsBeforeThemeSwitcher->prepend($itemsBeforeThemeSwitcher->pull('profile'), 'profile');
+ }
+
+ $position ??= filament()->getUserMenuPosition();
+
+ $isSidebarCollapsibleOnDesktop = filament()->isSidebarCollapsibleOnDesktop();
+?>
+
+
+
+
+
+
+ 'filament::components.dropdown.index','data' => ['placement' => ($position === UserMenuPosition::Topbar) ? 'bottom-end' : 'top-end','teleport' => $position === UserMenuPosition::Topbar,'attributes' =>
+ \Filament\Support\prepare_inherited_attributes($attributes)
+ ->class(['fi-user-menu'])
+ ]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::dropdown'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['placement' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(($position === UserMenuPosition::Topbar) ? 'bottom-end' : 'top-end'),'teleport' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($position === UserMenuPosition::Topbar),'attributes' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(
+ \Filament\Support\prepare_inherited_attributes($attributes)
+ ->class(['fi-user-menu'])
+ )]); ?>
+ slot('trigger', null, []); ?>
+
+
+
+
+
+ endSlot(); ?>
+
+
+ getColor();
+ $itemIcon = $item->getIcon();
+
+ unset($itemsBeforeThemeSwitcher['profile']);
+ ?>
+
+
+
+
+
+
+ 'filament::components.dropdown.header','data' => ['color' => $itemColor,'icon' => $itemIcon]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::dropdown.header'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['color' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemColor),'icon' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemIcon)]); ?>
+ getLabel()); ?>
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ isNotEmpty()): ?>
+
+
+ 'filament::components.dropdown.list.index','data' => []] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::dropdown.list'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes([]); ?>
+ addLoop($__currentLoopData); foreach($__currentLoopData as $key => $item): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ popLoop(); $loop = $__env->getLastLoop(); ?>
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+ hasDarkMode() && (! filament()->hasDarkModeForced())): ?>
+
+
+ 'filament::components.dropdown.list.index','data' => []] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::dropdown.list'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes([]); ?>
+
+
+ 'filament-panels::components.theme-switcher.index','data' => []] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-panels::theme-switcher'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes([]); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+ isNotEmpty()): ?>
+
+
+ 'filament::components.dropdown.list.index','data' => []] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::dropdown.list'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes([]); ?>
+ addLoop($__currentLoopData); foreach($__currentLoopData as $key => $item): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ popLoop(); $loop = $__env->getLastLoop(); ?>
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/a01f2f0ad64ffb4bec6c224e0a32b065.php b/storage/framework/views/a01f2f0ad64ffb4bec6c224e0a32b065.php
new file mode 100644
index 0000000..1b26742
--- /dev/null
+++ b/storage/framework/views/a01f2f0ad64ffb4bec6c224e0a32b065.php
@@ -0,0 +1,29 @@
+
+
+ 'filament-widgets::components.widget','data' => ['class' => 'fi-wi-table']] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-widgets::widget'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['class' => 'fi-wi-table']); ?>
+
+
+
+ table); ?>
+
+
+
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/af830d64c98ea537dd47cb4334f3a451.php b/storage/framework/views/af830d64c98ea537dd47cb4334f3a451.php
new file mode 100644
index 0000000..83e2eb5
--- /dev/null
+++ b/storage/framework/views/af830d64c98ea537dd47cb4334f3a451.php
@@ -0,0 +1,517 @@
+
+ isSidebarCollapsibleOnDesktop();
+ $isSidebarFullyCollapsibleOnDesktop = filament()->isSidebarFullyCollapsibleOnDesktop();
+ $hasTopNavigation = filament()->hasTopNavigation();
+ $hasNavigation = filament()->hasNavigation();
+ $hasTenancy = filament()->hasTenancy();
+ ?>
+
+
+
+
+
+
+
+
+ 'filament::components.icon-button','data' => ['color' => 'gray','icon' => \Filament\Support\Icons\Heroicon::OutlinedBars3,'iconAlias' => \Filament\View\PanelsIconAlias::TOPBAR_OPEN_SIDEBAR_BUTTON,'iconSize' => 'lg','label' => __('filament-panels::layout.actions.sidebar.expand.label'),'xCloak' => true,'xData' => '{}','xOn:click' => '$store.sidebar.open()','xShow' => '! $store.sidebar.isOpen','class' => 'fi-topbar-open-sidebar-btn']] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::icon-button'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['color' => 'gray','icon' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Filament\Support\Icons\Heroicon::OutlinedBars3),'icon-alias' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Filament\View\PanelsIconAlias::TOPBAR_OPEN_SIDEBAR_BUTTON),'icon-size' => 'lg','label' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(__('filament-panels::layout.actions.sidebar.expand.label')),'x-cloak' => true,'x-data' => '{}','x-on:click' => '$store.sidebar.open()','x-show' => '! $store.sidebar.isOpen','class' => 'fi-topbar-open-sidebar-btn']); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+ 'filament::components.icon-button','data' => ['color' => 'gray','icon' => \Filament\Support\Icons\Heroicon::OutlinedXMark,'iconAlias' => \Filament\View\PanelsIconAlias::TOPBAR_CLOSE_SIDEBAR_BUTTON,'iconSize' => 'lg','label' => __('filament-panels::layout.actions.sidebar.collapse.label'),'xCloak' => true,'xData' => '{}','xOn:click' => '$store.sidebar.close()','xShow' => '$store.sidebar.isOpen','class' => 'fi-topbar-close-sidebar-btn']] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::icon-button'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['color' => 'gray','icon' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Filament\Support\Icons\Heroicon::OutlinedXMark),'icon-alias' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Filament\View\PanelsIconAlias::TOPBAR_CLOSE_SIDEBAR_BUTTON),'icon-size' => 'lg','label' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(__('filament-panels::layout.actions.sidebar.collapse.label')),'x-cloak' => true,'x-data' => '{}','x-on:click' => '$store.sidebar.close()','x-show' => '$store.sidebar.isOpen','class' => 'fi-topbar-close-sidebar-btn']); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ hasTenantMenu()): ?>
+
+
+ 'filament-panels::components.tenant-menu','data' => ['teleport' => true]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-panels::tenant-menu'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['teleport' => true]); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+ getNavigation();
+ ?>
+
+
+ addLoop($__currentLoopData); foreach($__currentLoopData as $group): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
+ getLabel();
+ $groupExtraTopbarAttributeBag = $group->getExtraTopbarAttributeBag();
+ $isGroupActive = $group->isActive();
+ $groupIcon = $group->getIcon();
+ ?>
+
+
+
+
+ 'filament::components.dropdown.index','data' => ['placement' => 'bottom-start','teleport' => true,'attributes' => \Filament\Support\prepare_inherited_attributes($groupExtraTopbarAttributeBag)]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::dropdown'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['placement' => 'bottom-start','teleport' => true,'attributes' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Filament\Support\prepare_inherited_attributes($groupExtraTopbarAttributeBag))]); ?>
+ slot('trigger', null, []); ?>
+
+
+ 'filament-panels::components.topbar.item','data' => ['active' => $isGroupActive,'icon' => $groupIcon]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-panels::topbar.item'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['active' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($isGroupActive),'icon' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($groupIcon)]); ?>
+
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+ endSlot(); ?>
+
+ getItems() as $item) {
+ if ($childItems = $item->getChildItems()) {
+ $lists[] = [
+ $item,
+ ...$childItems,
+ ];
+ $lists[] = [];
+
+ continue;
+ }
+
+ if (empty($lists)) {
+ $lists[] = [$item];
+
+ continue;
+ }
+
+ $lists[count($lists) - 1][] = $item;
+ }
+
+ if (empty($lists[count($lists) - 1])) {
+ array_pop($lists);
+ }
+ ?>
+
+ addLoop($__currentLoopData); foreach($__currentLoopData as $list): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
+
+
+ 'filament::components.dropdown.list.index','data' => []] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::dropdown.list'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes([]); ?>
+ addLoop($__currentLoopData); foreach($__currentLoopData as $item): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
+ isActive();
+ $itemBadge = $item->getBadge();
+ $itemBadgeColor = $item->getBadgeColor();
+ $itemBadgeTooltip = $item->getBadgeTooltip();
+ $itemUrl = $item->getUrl();
+ $itemIcon = $isItemActive ? ($item->getActiveIcon() ?? $item->getIcon()) : $item->getIcon();
+ $shouldItemOpenUrlInNewTab = $item->shouldOpenUrlInNewTab();
+ ?>
+
+
+
+ 'filament::components.dropdown.list.item','data' => ['badge' => $itemBadge,'badgeColor' => $itemBadgeColor,'badgeTooltip' => $itemBadgeTooltip,'color' => $isItemActive ? 'primary' : 'gray','href' => $itemUrl,'icon' => $itemIcon,'tag' => 'a','target' => $shouldItemOpenUrlInNewTab ? '_blank' : null]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::dropdown.list.item'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['badge' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemBadge),'badge-color' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemBadgeColor),'badge-tooltip' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemBadgeTooltip),'color' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($isItemActive ? 'primary' : 'gray'),'href' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemUrl),'icon' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemIcon),'tag' => 'a','target' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($shouldItemOpenUrlInNewTab ? '_blank' : null)]); ?>
+ getLabel()); ?>
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+ popLoop(); $loop = $__env->getLastLoop(); ?>
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+ popLoop(); $loop = $__env->getLastLoop(); ?>
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+ getItems(); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $item): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
+ isActive();
+ $itemActiveIcon = $item->getActiveIcon();
+ $itemBadge = $item->getBadge();
+ $itemBadgeColor = $item->getBadgeColor();
+ $itemBadgeTooltip = $item->getBadgeTooltip();
+ $itemIcon = $item->getIcon();
+ $shouldItemOpenUrlInNewTab = $item->shouldOpenUrlInNewTab();
+ $itemUrl = $item->getUrl();
+ ?>
+
+
+
+ 'filament-panels::components.topbar.item','data' => ['active' => $isItemActive,'activeIcon' => $itemActiveIcon,'badge' => $itemBadge,'badgeColor' => $itemBadgeColor,'badgeTooltip' => $itemBadgeTooltip,'icon' => $itemIcon,'shouldOpenUrlInNewTab' => $shouldItemOpenUrlInNewTab,'url' => $itemUrl]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-panels::topbar.item'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['active' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($isItemActive),'active-icon' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemActiveIcon),'badge' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemBadge),'badge-color' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemBadgeColor),'badge-tooltip' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemBadgeTooltip),'icon' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemIcon),'should-open-url-in-new-tab' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($shouldItemOpenUrlInNewTab),'url' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($itemUrl)]); ?>
+ getLabel()); ?>
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+ popLoop(); $loop = $__env->getLastLoop(); ?>
+
+ popLoop(); $loop = $__env->getLastLoop(); ?>
+
+
+
+
+
+ x-persist="topbar.end.panel-getId()); ?>.tenant-getTenant()?->getKey()); ?>"
+
+ x-persist="topbar.end.panel-getId()); ?>"
+
+ class="fi-topbar-end"
+ >
+
+
+
+ isGlobalSearchEnabled() && filament()->getGlobalSearchPosition() === \Filament\Enums\GlobalSearchPosition::Topbar): ?>
+ mount($__name, $__params, $key);
+
+echo $__html;
+
+unset($__html);
+unset($__name);
+unset($__params);
+unset($__split);
+if (isset($__slots)) unset($__slots);
+?>
+
+
+
+
+
+ auth()->check()): ?>
+ hasDatabaseNotifications() && filament()->getDatabaseNotificationsPosition() === \Filament\Enums\DatabaseNotificationsPosition::Topbar): ?>
+ filament()->hasLazyLoadedDatabaseNotifications(),
+ ]);
+
+$key = null;
+
+$key ??= \Livewire\Features\SupportCompiledWireKeys\SupportCompiledWireKeys::generateKey('lw-1441774602-1', null);
+
+$__html = app('livewire')->mount($__name, $__params, $key);
+
+echo $__html;
+
+unset($__html);
+unset($__name);
+unset($__params);
+unset($__split);
+if (isset($__slots)) unset($__slots);
+?>
+
+
+ hasUserMenu() && filament()->getUserMenuPosition() === \Filament\Enums\UserMenuPosition::Topbar): ?>
+
+
+ 'filament-panels::components.user-menu','data' => []] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-panels::user-menu'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes([]); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'filament-actions::components.modals','data' => []] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-actions::modals'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes([]); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/b235d3c77d869dcdade57f7a1b61c8ff.php b/storage/framework/views/b235d3c77d869dcdade57f7a1b61c8ff.php
new file mode 100644
index 0000000..2a7392a
--- /dev/null
+++ b/storage/framework/views/b235d3c77d869dcdade57f7a1b61c8ff.php
@@ -0,0 +1,212 @@
+ null,
+ 'alpineValid' => null,
+ 'disabled' => false,
+ 'inlinePrefix' => false,
+ 'inlineSuffix' => false,
+ 'prefix' => null,
+ 'prefixActions' => [],
+ 'prefixIcon' => null,
+ 'prefixIconColor' => 'gray',
+ 'prefixIconAlias' => null,
+ 'suffix' => null,
+ 'suffixActions' => [],
+ 'suffixIcon' => null,
+ 'suffixIconColor' => 'gray',
+ 'suffixIconAlias' => null,
+ 'valid' => true,
+]));
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (in_array($__key, $__propNames)) {
+ $$__key = $$__key ?? $__value;
+ } else {
+ $__newAttributes[$__key] = $__value;
+ }
+}
+
+$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);
+
+unset($__propNames);
+unset($__newAttributes);
+
+foreach (array_filter(([
+ 'alpineDisabled' => null,
+ 'alpineValid' => null,
+ 'disabled' => false,
+ 'inlinePrefix' => false,
+ 'inlineSuffix' => false,
+ 'prefix' => null,
+ 'prefixActions' => [],
+ 'prefixIcon' => null,
+ 'prefixIconColor' => 'gray',
+ 'prefixIconAlias' => null,
+ 'suffix' => null,
+ 'suffixActions' => [],
+ 'suffixIcon' => null,
+ 'suffixIconColor' => 'gray',
+ 'suffixIconAlias' => null,
+ 'valid' => true,
+]), 'is_string', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
+ $$__key = $$__key ?? $__value;
+}
+
+$__defined_vars = get_defined_vars();
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (array_key_exists($__key, $__defined_vars)) unset($$__key);
+}
+
+unset($__defined_vars, $__key, $__value); ?>
+
+ $prefixAction->isVisible(),
+ );
+
+ $suffixActions = array_filter(
+ $suffixActions,
+ fn (\Filament\Actions\Action $suffixAction): bool => $suffixAction->isVisible(),
+ );
+
+ $hasPrefix = count($prefixActions) || $prefixIcon || filled($prefix);
+ $hasSuffix = count($suffixActions) || $suffixIcon || filled($suffix);
+
+ $hasAlpineDisabledClasses = filled($alpineDisabled);
+ $hasAlpineValidClasses = filled($alpineValid);
+ $hasAlpineClasses = $hasAlpineDisabledClasses || $hasAlpineValidClasses;
+
+ $wireTarget = $attributes->whereStartsWith(['wire:target'])->first();
+
+ $hasLoadingIndicator = filled($wireTarget);
+
+ if ($hasLoadingIndicator) {
+ $loadingIndicatorTarget = html_entity_decode($wireTarget, ENT_QUOTES);
+ }
+?>
+
+
+ x-bind:class="{
+
+
+
+
+ }"
+
+ except(['wire:target', 'tabindex'])
+ ->class([
+ 'fi-input-wrp',
+ 'fi-disabled' => (! $hasAlpineClasses) && $disabled,
+ 'fi-invalid' => (! $hasAlpineClasses) && (! $valid),
+ ])); ?>
+
+>
+
+
+ wire:loading.delay..flex
+ wire:target=""
+ wire:key=""
+
+ class=" $hasPrefix,
+ 'fi-inline' => $inlinePrefix,
+ 'fi-input-wrp-prefix-has-label' => filled($prefix),
+ ]); ?>"
+ >
+
+
+ addLoop($__currentLoopData); foreach($__currentLoopData as $prefixAction): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
+
+
+ popLoop(); $loop = $__env->getLastLoop(); ?>
+
+
+
+ merge([
+ 'wire:loading.remove.delay.' . config('filament.livewire_loading_delay', 'default') => $hasLoadingIndicator,
+ 'wire:target' => $hasLoadingIndicator ? $loadingIndicatorTarget : false,
+ ], escape: false)
+ ->color(IconComponent::class, $prefixIconColor))); ?>
+
+
+
+ $hasPrefix,
+ 'wire:target' => $hasPrefix ? $loadingIndicatorTarget : null,
+ ]))->color(IconComponent::class, 'gray'))); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ wire:loading.delay..class.remove="ps-3"
+
+
+ wire:target=""
+
+ class=" $hasLoadingIndicator && (! $hasPrefix) && $inlinePrefix,
+ ]); ?>"
+ >
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/be6715616843b225cf6deff982e77b14.php b/storage/framework/views/be6715616843b225cf6deff982e77b14.php
new file mode 100644
index 0000000..672236a
--- /dev/null
+++ b/storage/framework/views/be6715616843b225cf6deff982e77b14.php
@@ -0,0 +1,396 @@
+ false,
+]));
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (in_array($__key, $__propNames)) {
+ $$__key = $$__key ?? $__value;
+ } else {
+ $__newAttributes[$__key] = $__value;
+ }
+}
+
+$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);
+
+unset($__propNames);
+unset($__newAttributes);
+
+foreach (array_filter(([
+ 'fullHeight' => false,
+]), 'is_string', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
+ $$__key = $$__key ?? $__value;
+}
+
+$__defined_vars = get_defined_vars();
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (array_key_exists($__key, $__defined_vars)) unset($$__key);
+}
+
+unset($__defined_vars, $__key, $__value); ?>
+
+getCachedSubNavigation();
+ $subNavigationPosition = $this->getSubNavigationPosition();
+ $widgetData = $this->getWidgetData();
+?>
+
+
class([
+ 'fi-page',
+ 'fi-height-full' => $fullHeight,
+ 'fi-page-has-sub-navigation' => $subNavigation,
+ "fi-page-has-sub-navigation-{$subNavigationPosition->value}" => $subNavigation,
+ ...$this->getPageClasses(),
+ ])); ?>
+
+>
+ getRenderHookScopes())); ?>
+
+
+
+
+
+ getRenderHookScopes())); ?>
+
+
+
+
+
+ 'filament-panels::components.page.sub-navigation.mobile-menu','data' => ['navigation' => $subNavigation]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-panels::page.sub-navigation.mobile-menu'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['navigation' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($subNavigation)]); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+ getRenderHookScopes())); ?>
+
+
+
+
+ getHeader()): ?>
+
+
+
+ getHeading();
+ $headerActions = $this->getCachedHeaderActions();
+ $headerActionsAlignment = $this->getHeaderActionsAlignment();
+ $breadcrumbs = filament()->hasBreadcrumbs() ? $this->getBreadcrumbs() : [];
+ $subheading = $this->getSubheading();
+ ?>
+
+
+
+
+ 'filament-panels::components.header.index','data' => ['actions' => $headerActions,'actionsAlignment' => $headerActionsAlignment,'breadcrumbs' => $breadcrumbs,'heading' => $heading,'subheading' => $subheading]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-panels::header'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['actions' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($headerActions),'actions-alignment' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($headerActionsAlignment),'breadcrumbs' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($breadcrumbs),'heading' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($heading),'subheading' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($subheading)]); ?>
+
+ slot('heading', null, []); ?>
+
+
+ endSlot(); ?>
+
+
+
+ slot('subheading', null, []); ?>
+
+
+ endSlot(); ?>
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getRenderHookScopes())); ?>
+
+
+
+
+ 'filament-panels::components.page.sub-navigation.sidebar','data' => ['navigation' => $subNavigation]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-panels::page.sub-navigation.sidebar'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['navigation' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($subNavigation)]); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+ getRenderHookScopes())); ?>
+
+
+
+
+ getRenderHookScopes())); ?>
+
+
+
+
+ 'filament-panels::components.page.sub-navigation.tabs','data' => ['navigation' => $subNavigation]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-panels::page.sub-navigation.tabs'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['navigation' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($subNavigation)]); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+ getRenderHookScopes())); ?>
+
+
+
+
+
+ getRenderHookScopes())); ?>
+
+
+ headerWidgets); ?>
+
+
+ getRenderHookScopes())); ?>
+
+
+
+
+
+ getRenderHookScopes())); ?>
+
+
+ footerWidgets); ?>
+
+
+ getRenderHookScopes())); ?>
+
+
+
+
+ getRenderHookScopes())); ?>
+
+
+
+
+ 'filament-panels::components.page.sub-navigation.sidebar','data' => ['navigation' => $subNavigation]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-panels::page.sub-navigation.sidebar'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['navigation' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($subNavigation)]); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+ getRenderHookScopes())); ?>
+
+
+
+
+ getFooter()): ?>
+
+
+
+
+
+
+
+
+ 'filament-actions::components.modals','data' => []] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-actions::modals'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes([]); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+ isTableLoaded() && filled($this->defaultTableAction)): ?>
+
+
+
+ defaultAction)): ?>
+
+
+
+ getRenderHookScopes())); ?>
+
+
+ hasUnsavedDataChangesAlert()): ?>
+
+
+
+ push('scripts', $__output, $__scriptKey)
+ ?>
+
+
+
+ push('scripts', $__output, $__scriptKey)
+ ?>
+
+
+
+ hasDebugModeEnabled()) && $this->hasErrorNotifications()): ?>
+
+
+ push('scripts', $__output, $__scriptKey)
+ ?>
+
+
+
+
+ 'filament-panels::components.unsaved-action-changes-alert','data' => []] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-panels::unsaved-action-changes-alert'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes([]); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/bfd832b53fad5948a8706f00a290c0cf.php b/storage/framework/views/bfd832b53fad5948a8706f00a290c0cf.php
new file mode 100644
index 0000000..642f416
--- /dev/null
+++ b/storage/framework/views/bfd832b53fad5948a8706f00a290c0cf.php
@@ -0,0 +1,217 @@
+ false,
+ 'activeChildItems' => false,
+ 'activeIcon' => null,
+ 'badge' => null,
+ 'badgeColor' => null,
+ 'badgeTooltip' => null,
+ 'childItems' => [],
+ 'first' => false,
+ 'grouped' => false,
+ 'icon' => null,
+ 'last' => false,
+ 'shouldOpenUrlInNewTab' => false,
+ 'sidebarCollapsible' => true,
+ 'subGrouped' => false,
+ 'subNavigation' => false,
+ 'url',
+]));
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (in_array($__key, $__propNames)) {
+ $$__key = $$__key ?? $__value;
+ } else {
+ $__newAttributes[$__key] = $__value;
+ }
+}
+
+$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);
+
+unset($__propNames);
+unset($__newAttributes);
+
+foreach (array_filter(([
+ 'active' => false,
+ 'activeChildItems' => false,
+ 'activeIcon' => null,
+ 'badge' => null,
+ 'badgeColor' => null,
+ 'badgeTooltip' => null,
+ 'childItems' => [],
+ 'first' => false,
+ 'grouped' => false,
+ 'icon' => null,
+ 'last' => false,
+ 'shouldOpenUrlInNewTab' => false,
+ 'sidebarCollapsible' => true,
+ 'subGrouped' => false,
+ 'subNavigation' => false,
+ 'url',
+]), 'is_string', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
+ $$__key = $$__key ?? $__value;
+}
+
+$__defined_vars = get_defined_vars();
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (array_key_exists($__key, $__defined_vars)) unset($$__key);
+}
+
+unset($__defined_vars, $__key, $__value); ?>
+
+isSidebarCollapsibleOnDesktop();
+?>
+
+
class([
+ 'fi-sidebar-item',
+ 'fi-active' => $active,
+ 'fi-sidebar-item-has-active-child-items' => $activeChildItems,
+ 'fi-sidebar-item-has-url' => filled($url),
+ ])); ?>
+
+>
+
+
+ x-on:click="window.matchMedia(`(max-width: 1024px)`).matches && $store.sidebar.close()"
+
+ x-data="{ tooltip: false }"
+ x-effect="
+ tooltip = $store.sidebar.isOpen
+ ? false
+ : {
+ content: toHtml())->toHtml() ?>,
+ placement: document.dir === 'rtl' ? 'left' : 'right',
+ theme: $store.theme,
+ }
+ "
+ x-tooltip.html="tooltip"
+
+ class="fi-sidebar-item-btn"
+ >
+
+ ($subGrouped && $sidebarCollapsible) ? '! $store.sidebar.isOpen' : false,
+ ]))->class(['fi-sidebar-item-icon']), size: \Filament\Support\Enums\IconSize::Large)); ?>
+
+
+
+
+
+ x-show="$store.sidebar.isOpen"
+
+ class="fi-sidebar-item-grouped-border"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ x-show="$store.sidebar.isOpen"
+ x-transition:enter="fi-transition-enter"
+ x-transition:enter-start="fi-transition-enter-start"
+ x-transition:enter-end="fi-transition-enter-end"
+
+ class="fi-sidebar-item-label"
+ >
+
+
+
+
+
+
+ x-show="$store.sidebar.isOpen"
+ x-transition:enter="fi-transition-enter"
+ x-transition:enter-start="fi-transition-enter-start"
+ x-transition:enter-end="fi-transition-enter-end"
+
+ class="fi-sidebar-item-badge-ctn"
+ >
+
+
+ 'filament::components.badge','data' => ['color' => $badgeColor,'tooltip' => $badgeTooltip]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::badge'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['color' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($badgeColor),'tooltip' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($badgeTooltip)]); ?>
+
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/c6d1a975364ea77cf193b26f4d0c05e1.php b/storage/framework/views/c6d1a975364ea77cf193b26f4d0c05e1.php
new file mode 100644
index 0000000..2a13e26
--- /dev/null
+++ b/storage/framework/views/c6d1a975364ea77cf193b26f4d0c05e1.php
@@ -0,0 +1,213 @@
+startSection('title', 'Custom Printed Fabrics'); ?>
+
+startSection('content'); ?>
+
+
+
+
+
Custom Printed Fabrics
+
Extend your favorite wallpaper designs to premium fabrics. From upholstery to curtains, cushions to bedding, create a cohesive interior where your designs flow seamlessly across all textiles.
+
+
+
+
+
+
+
+
Fabric Types & Applications
+
Choose from our range of premium fabrics, each optimized for different applications and uses.
+
+
+
+
+
+
+
Upholstery Fabrics
+
Durable, premium fabrics perfect for furniture upholstery. Available in various weights and finishes. Ideal for sofas, chairs, and ottomans.
+
+ Explore Collection
+
+
+
+
+
+
+
+
+
Curtain & Drape Fabrics
+
Elegant draping fabrics in various opacities. Perfect for creating custom curtains, drapes, and window treatments that complement your wallpaper.
+
+ Explore Collection
+
+
+
+
+
+
+
+
+
Decorative Textiles
+
Versatile fabrics for cushions, pillows, throws, and home accents. Perfect for adding coordinated touches throughout your space.
+
+ Explore Collection
+
+
+
+
+
+
+
+
+
+
+
Featured Fabric Collections
+
Our most popular custom-printed fabric options, available in various widths and finishes.
+
+
+
+
+
+
+
Botanical Garden
+
Nature-inspired floral patterns printed on premium linen blend. Perfect for upholstery and curtains.
+
+ From $45/yard
+ View
+
+
+
+
+
+
+
+
+
Geometric Modern
+
Contemporary geometric patterns on cotton canvas. Durable and perfect for high-traffic furniture.
+
+ From $48/yard
+ View
+
+
+
+
+
+
+
+
+
Vintage Elegance
+
Classic ornamental patterns on silk blend fabric. Ideal for luxury upholstery and formal drapery.
+
+ From $65/yard
+ View
+
+
+
+
+
+
+
+
+
Tropical Vibrancy
+
Bright tropical foliage patterns on cotton velvet. Perfect for statement cushions and accent furniture.
+
+ From $52/yard
+ View
+
+
+
+
+
+
+
+
+
Minimalist Serenity
+
Subtle minimalist patterns on natural linen. Versatile for any interior design style.
+
+ From $42/yard
+ View
+
+
+
+
+
+
+
+
+
Luxe Textured
+
High-end jacquard weave fabrics with dimensional textures. Perfect for luxury applications.
+
+ From $75/yard
+ View
+
+
+
+
+
+
+
+
+
+
+
Fully Customizable
+
+
+
Create Your Own Custom Fabric
+
Love a wallpaper design? We can print it on any of our premium fabric bases. Upload your own design or choose from our wallpaper collections and we'll print it on your fabric of choice.
+
+ ✓ Any Wallpaper Design: Choose from our full wallpaper catalog
+ ✓ Custom Uploads: Print your own designs or artwork
+ ✓ Multiple Base Fabrics: Select from cotton, linen, silk blends, and more
+ ✓ Custom Sizing: Order exact yardage you need
+ ✓ Sample Swatches: Order fabric samples before committing
+
+
Start Custom Design
+
+
+
+
+
+
+
+
+
+
+
+
Fabric Specifications & Care
+
+
+
+
Standard Widths
+
+ • 54" (Standard upholstery)
+ • 60" (Premium upholstery)
+ • 45" (Quilting & crafts)
+ • 118" (Curtain & drape)
+
+
+
+
Care Instructions
+
+ • Dry clean or gentle hand wash recommended
+ • Use cool water with mild detergent
+ • Air dry away from direct heat
+ • Professional upholstery cleaning safe
+
+
+
+
+
+
+
+
+
+
+
Ready to Start Your Fabric Project?
+
+ Contact our team to discuss your custom fabric needs. We'll provide samples, pricing, and personalized recommendations.
+
+
Request Custom Quote
+
+
+stopSection(); ?>
+
+make('layouts.app', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?>
\ No newline at end of file
diff --git a/storage/framework/views/cb60a36db4e92d8fcf5107fb500e9e22.php b/storage/framework/views/cb60a36db4e92d8fcf5107fb500e9e22.php
new file mode 100644
index 0000000..3ea5179
--- /dev/null
+++ b/storage/framework/views/cb60a36db4e92d8fcf5107fb500e9e22.php
@@ -0,0 +1,225 @@
+
+
+ null,
+ 'badgeColor' => 'primary',
+ 'badgeSize' => Size::ExtraSmall,
+ 'color' => 'primary',
+ 'disabled' => false,
+ 'form' => null,
+ 'formId' => null,
+ 'href' => null,
+ 'icon' => null,
+ 'iconAlias' => null,
+ 'iconPosition' => IconPosition::Before,
+ 'iconSize' => null,
+ 'keyBindings' => null,
+ 'labelSrOnly' => false,
+ 'loadingIndicator' => true,
+ 'size' => Size::Medium,
+ 'spaMode' => null,
+ 'tag' => 'a',
+ 'target' => null,
+ 'tooltip' => null,
+ 'type' => 'button',
+ 'weight' => null,
+]));
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (in_array($__key, $__propNames)) {
+ $$__key = $$__key ?? $__value;
+ } else {
+ $__newAttributes[$__key] = $__value;
+ }
+}
+
+$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);
+
+unset($__propNames);
+unset($__newAttributes);
+
+foreach (array_filter(([
+ 'badge' => null,
+ 'badgeColor' => 'primary',
+ 'badgeSize' => Size::ExtraSmall,
+ 'color' => 'primary',
+ 'disabled' => false,
+ 'form' => null,
+ 'formId' => null,
+ 'href' => null,
+ 'icon' => null,
+ 'iconAlias' => null,
+ 'iconPosition' => IconPosition::Before,
+ 'iconSize' => null,
+ 'keyBindings' => null,
+ 'labelSrOnly' => false,
+ 'loadingIndicator' => true,
+ 'size' => Size::Medium,
+ 'spaMode' => null,
+ 'tag' => 'a',
+ 'target' => null,
+ 'tooltip' => null,
+ 'type' => 'button',
+ 'weight' => null,
+]), 'is_string', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
+ $$__key = $$__key ?? $__value;
+}
+
+$__defined_vars = get_defined_vars();
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (array_key_exists($__key, $__defined_vars)) unset($$__key);
+}
+
+unset($__defined_vars, $__key, $__value); ?>
+
+ IconSize::Small,
+ default => null,
+ };
+
+ if (! $weight instanceof FontWeight) {
+ $weight = filled($weight) ? (FontWeight::tryFrom($weight) ?? $weight) : null;
+ }
+
+ $wireTarget = $loadingIndicator ? $attributes->whereStartsWith(['wire:target', 'wire:click'])->filter(fn ($value): bool => filled($value))->first() : null;
+
+ $hasLoadingIndicator = filled($wireTarget) || ($type === 'submit' && filled($form));
+
+ if ($hasLoadingIndicator) {
+ $loadingIndicatorTarget = html_entity_decode($wireTarget ?: $form, ENT_QUOTES);
+ }
+
+ $hasTooltip = filled($tooltip);
+?>
+
+<
+
+
+
+
+
+
+ x-bind:id="$id('key-bindings')"
+ x-mousetrap.global.map(fn (string $keyBinding): string => str_replace('+', '-', $keyBinding))->implode('.')); ?>="document.getElementById($el.id)?.click()"
+
+
+ x-tooltip="{
+ content: toHtml() ?>,
+ theme: $store.theme,
+ allowHTML: toHtml() ?>,
+ }"
+
+ merge([
+ 'aria-disabled' => $disabled ? 'true' : null,
+ 'disabled' => $disabled && blank($tooltip),
+ 'form' => $formId,
+ 'type' => $tag === 'button' ? $type : null,
+ 'wire:loading.attr' => $tag === 'button' ? 'disabled' : null,
+ 'wire:target' => ($hasLoadingIndicator && $loadingIndicatorTarget) ? $loadingIndicatorTarget : null,
+ ], escape: false)
+ ->when(
+ $disabled && $hasTooltip,
+ fn (ComponentAttributeBag $attributes) => $attributes->filter(
+ fn (mixed $value, string $key): bool => ! str($key)->startsWith(['href', 'x-on:', 'wire:click']),
+ ),
+ )
+ ->class([
+ 'fi-link',
+ 'fi-disabled' => $disabled,
+ ($size instanceof Size) ? "fi-size-{$size->value}" : (is_string($size) ? $size : ''),
+ ($weight instanceof FontWeight) ? "fi-font-{$weight->value}" : (is_string($weight) ? $weight : ''),
+ ])
+ ->color(LinkComponent::class, $color)); ?>
+
+>
+
+
+ $hasLoadingIndicator,
+ 'wire:target' => $hasLoadingIndicator ? $loadingIndicatorTarget : false,
+ ])), size: $iconSize)); ?>
+
+
+
+
+ '',
+ 'wire:target' => $loadingIndicatorTarget,
+ ])), size: $iconSize)); ?>
+
+
+
+
+
+
+
+
+
+
+
+ $hasLoadingIndicator,
+ 'wire:target' => $hasLoadingIndicator ? $loadingIndicatorTarget : false,
+ ])), size: $iconSize)); ?>
+
+
+
+
+ '',
+ 'wire:target' => $loadingIndicatorTarget,
+ ])), size: $iconSize)); ?>
+
+
+
+
+
+
+
+
+
+
+ color(BadgeComponent::class, $badgeColor)->class([
+ 'fi-badge',
+ ($badgeSize instanceof Size) ? "fi-size-{$badgeSize->value}" : (is_string($badgeSize) ? $badgeSize : ''),
+ ])); ?>
+
+ >
+
+
+
+
+
+
+>
\ No newline at end of file
diff --git a/storage/framework/views/d136669968d8cce7e458e5d7777f6a32.php b/storage/framework/views/d136669968d8cce7e458e5d7777f6a32.php
new file mode 100644
index 0000000..6e2a02b
--- /dev/null
+++ b/storage/framework/views/d136669968d8cce7e458e5d7777f6a32.php
@@ -0,0 +1,49 @@
+getColumns();
+ $pollingInterval = $this->getPollingInterval();
+
+ $heading = $this->getHeading();
+ $description = $this->getDescription();
+ $hasHeading = filled($heading);
+ $hasDescription = filled($description);
+?>
+
+
+
+ 'filament-widgets::components.widget','data' => ['attributes' =>
+ (new \Illuminate\View\ComponentAttributeBag)
+ ->merge([
+ 'wire:poll.' . $pollingInterval => $pollingInterval ? true : null,
+ ], escape: false)
+ ->class([
+ 'fi-wi-stats-overview',
+ ])
+ ]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-widgets::widget'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['attributes' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(
+ (new \Illuminate\View\ComponentAttributeBag)
+ ->merge([
+ 'wire:poll.' . $pollingInterval => $pollingInterval ? true : null,
+ ], escape: false)
+ ->class([
+ 'fi-wi-stats-overview',
+ ])
+ )]); ?>
+ content); ?>
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/e5d7be41700811dd965c7910cdd63b16.php b/storage/framework/views/e5d7be41700811dd965c7910cdd63b16.php
new file mode 100644
index 0000000..ebc812e
--- /dev/null
+++ b/storage/framework/views/e5d7be41700811dd965c7910cdd63b16.php
@@ -0,0 +1,128 @@
+
+
+ 'filament-widgets::components.widget','data' => ['class' => 'fi-filament-info-widget']] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-widgets::widget'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['class' => 'fi-filament-info-widget']); ?>
+
+
+ 'filament::components.section.index','data' => []] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::section'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes([]); ?>
+
+
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/e5d92f0df5c965b06cb33f9117a8c00d.php b/storage/framework/views/e5d92f0df5c965b06cb33f9117a8c00d.php
new file mode 100644
index 0000000..1aa4341
--- /dev/null
+++ b/storage/framework/views/e5d92f0df5c965b06cb33f9117a8c00d.php
@@ -0,0 +1,8 @@
+
gridColumn($this->getColumnSpan(), $this->getColumnStart())->class(['fi-wi-widget'])); ?>
+
+>
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/ec6b175cafc2ad7bc060e67f10781d51.php b/storage/framework/views/ec6b175cafc2ad7bc060e67f10781d51.php
new file mode 100644
index 0000000..f283a4a
--- /dev/null
+++ b/storage/framework/views/ec6b175cafc2ad7bc060e67f10781d51.php
@@ -0,0 +1,66 @@
+ filament()->auth()->user(),
+]));
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (in_array($__key, $__propNames)) {
+ $$__key = $$__key ?? $__value;
+ } else {
+ $__newAttributes[$__key] = $__value;
+ }
+}
+
+$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);
+
+unset($__propNames);
+unset($__newAttributes);
+
+foreach (array_filter(([
+ 'user' => filament()->auth()->user(),
+]), 'is_string', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
+ $$__key = $$__key ?? $__value;
+}
+
+$__defined_vars = get_defined_vars();
+
+foreach ($attributes->all() as $__key => $__value) {
+ if (array_key_exists($__key, $__defined_vars)) unset($$__key);
+}
+
+unset($__defined_vars, $__key, $__value); ?>
+
+getUserAvatarUrl($user);
+ $alt = __('filament-panels::layout.avatar.alt', ['name' => filament()->getUserName($user)]);
+?>
+
+
+
+ 'filament::components.avatar','data' => ['src' => $src,'alt' => $alt,'attributes' =>
+ \Filament\Support\prepare_inherited_attributes($attributes)
+ ->class(['fi-user-avatar'])
+ ]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::avatar'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['src' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($src),'alt' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($alt),'attributes' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(
+ \Filament\Support\prepare_inherited_attributes($attributes)
+ ->class(['fi-user-avatar'])
+ )]); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/f08ad9fca67479dc124a5f64a7514f97.php b/storage/framework/views/f08ad9fca67479dc124a5f64a7514f97.php
new file mode 100644
index 0000000..d02e145
--- /dev/null
+++ b/storage/framework/views/f08ad9fca67479dc124a5f64a7514f97.php
@@ -0,0 +1,108 @@
+auth()->user();
+?>
+
+
+
+ 'filament-widgets::components.widget','data' => ['class' => 'fi-account-widget']] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-widgets::widget'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['class' => 'fi-account-widget']); ?>
+
+
+ 'filament::components.section.index','data' => []] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::section'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes([]); ?>
+
+
+ 'filament-panels::components.avatar.user','data' => ['size' => 'lg','user' => $user,'loading' => 'lazy']] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-panels::avatar.user'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['size' => 'lg','user' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($user),'loading' => 'lazy']); ?>
+renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getUserName($user)); ?>
+
+
+
+
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/f909bcbbb630bac1bb471924797df459.php b/storage/framework/views/f909bcbbb630bac1bb471924797df459.php
new file mode 100644
index 0000000..327b2f9
--- /dev/null
+++ b/storage/framework/views/f909bcbbb630bac1bb471924797df459.php
@@ -0,0 +1,80 @@
+toHtmlString();
+ $isAside = $isAside();
+ $isCollapsed = $isCollapsed();
+ $isCollapsible = $isCollapsible();
+ $isCompact = $isCompact();
+ $isContained = $isContained();
+ $isDivided = $isDivided();
+ $isFormBefore = $isFormBefore();
+ $description = $getDescription();
+ $footer = $getChildSchema($schemaComponent::FOOTER_SCHEMA_KEY)?->toHtmlString();
+ $heading = $getHeading();
+ $headingTag = $getHeadingTag();
+ $icon = $getIcon();
+ $iconColor = $getIconColor();
+ $iconSize = $getIconSize();
+ $shouldPersistCollapsed = $shouldPersistCollapsed();
+ $isSecondary = $isSecondary();
+ $id = $getId();
+?>
+
+
merge([
+ 'id' => $id,
+ ], escape: false)
+ ->merge($getExtraAttributes(), escape: false)
+ ->merge($getExtraAlpineAttributes(), escape: false)
+ ->class(['fi-sc-section'])); ?>
+
+>
+
+
+
+
+ toHtmlString()): ?>
+
+
+
+
+
+
+ 'filament::components.section.index','data' => ['afterHeader' => $afterHeader,'aside' => $isAside,'collapsed' => $isCollapsed,'collapseId' => $id,'collapsible' => $isCollapsible && (! $isAside),'compact' => $isCompact,'contained' => $isContained,'contentBefore' => $isFormBefore,'description' => $description,'divided' => $isDivided,'footer' => $footer,'hasContentEl' => false,'heading' => $heading,'headingTag' => $headingTag,'icon' => $icon,'iconColor' => $iconColor,'iconSize' => $iconSize,'persistCollapsed' => $shouldPersistCollapsed,'secondary' => $isSecondary]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::section'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['after-header' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($afterHeader),'aside' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($isAside),'collapsed' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($isCollapsed),'collapse-id' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($id),'collapsible' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($isCollapsible && (! $isAside)),'compact' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($isCompact),'contained' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($isContained),'content-before' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($isFormBefore),'description' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($description),'divided' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($isDivided),'footer' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($footer),'has-content-el' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(false),'heading' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($heading),'heading-tag' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($headingTag),'icon' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($icon),'icon-color' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($iconColor),'icon-size' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($iconSize),'persist-collapsed' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($shouldPersistCollapsed),'secondary' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($isSecondary)]); ?>
+ gap(! $isDivided)->extraAttributes(['class' => 'fi-section-content'])); ?>
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+ toHtmlString()): ?>
+
+
+
+
+
\ No newline at end of file
diff --git a/storage/framework/views/f98c182f4ecf0c1b66c11b701478d8d6.php b/storage/framework/views/f98c182f4ecf0c1b66c11b701478d8d6.php
new file mode 100644
index 0000000..bd4c2f5
--- /dev/null
+++ b/storage/framework/views/f98c182f4ecf0c1b66c11b701478d8d6.php
@@ -0,0 +1,187 @@
+getColor();
+ $heading = $this->getHeading();
+ $description = $this->getDescription();
+ $filters = $this->getFilters();
+ $isCollapsible = $this->isCollapsible();
+ $type = $this->getType();
+?>
+
+
+
+ 'filament-widgets::components.widget','data' => ['class' => 'fi-wi-chart']] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament-widgets::widget'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['class' => 'fi-wi-chart']); ?>
+
+
+ 'filament::components.section.index','data' => ['description' => $description,'heading' => $heading,'collapsible' => $isCollapsible]] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::section'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['description' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($description),'heading' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($heading),'collapsible' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute($isCollapsible)]); ?>
+
+ slot('afterHeader', null, []); ?>
+
+
+
+ 'filament::components.input.wrapper','data' => ['inlinePrefix' => true,'wire:target' => 'filter','class' => 'fi-wi-chart-filter']] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::input.wrapper'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['inline-prefix' => true,'wire:target' => 'filter','class' => 'fi-wi-chart-filter']); ?>
+
+
+ 'filament::components.input.select','data' => ['inlinePrefix' => true,'wire:model.live' => 'filter']] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::input.select'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['inline-prefix' => true,'wire:model.live' => 'filter']); ?>
+ addLoop($__currentLoopData); foreach($__currentLoopData as $value => $label): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
+
+
+
+
+ popLoop(); $loop = $__env->getLastLoop(); ?>
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'filament::components.dropdown.index','data' => ['placement' => 'bottom-end','shift' => true,'width' => 'xs','class' => 'fi-wi-chart-filter']] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?>
+withName('filament::dropdown'); ?>
+shouldRender()): ?>
+startComponent($component->resolveView(), $component->data()); ?>
+
+except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?>
+
+withAttributes(['placement' => 'bottom-end','shift' => true,'width' => 'xs','class' => 'fi-wi-chart-filter']); ?>
+ slot('trigger', null, []); ?>
+ getFiltersTriggerAction()); ?>
+
+ endSlot(); ?>
+
+
+ getFiltersSchema()); ?>
+
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
+ endSlot(); ?>
+
+
+
getPollingInterval()): ?>
+ wire:poll.="updateChartData"
+
+ >
+
color(ChartWidgetComponent::class, $color)
+ ->class([
+ 'fi-wi-chart-canvas-ctn',
+ 'fi-wi-chart-canvas-ctn-no-aspect-ratio' => filled($maxHeight = $this->getMaxHeight()),
+ ])
+ ->style([
+ 'max-height: ' . $maxHeight => filled($maxHeight),
+ ])); ?>
+
+ >
+
+
+
+
+
+
+
+
+
+
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+ renderComponent(); ?>
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Feature/ShipmentCreationTest.php b/tests/Feature/ShipmentCreationTest.php
new file mode 100644
index 0000000..142e688
--- /dev/null
+++ b/tests/Feature/ShipmentCreationTest.php
@@ -0,0 +1,84 @@
+create([
+ 'order_number' => 'TEST-001',
+ 'customer_name' => 'John Doe',
+ 'customer_email' => 'john@example.com',
+ 'customer_phone' => '+1234567890',
+ 'delivery_street' => '123 Main Street',
+ 'delivery_unit' => 'Apt 5B',
+ 'delivery_city' => 'New York',
+ 'delivery_zone' => 'NY',
+ 'delivery_code' => '10001',
+ 'delivery_country' => 'US',
+ 'packing_length' => 30,
+ 'packing_width' => 20,
+ 'packing_height' => 10,
+ 'packing_weight' => 2.5,
+ 'status' => 'ready_to_ship',
+ ]);
+
+ // Dispatch the ReadyToShipIntent event to trigger shipment creation
+ // This would normally be triggered by user action in Filament
+ event(new \App\Events\ReadyToShipIntent($order));
+
+ // Verify the order now has shipment metadata
+ $order->refresh();
+ $this->assertNotNull($order->courier_shipment_id);
+ $this->assertNotNull($order->courier_service_level_code);
+ $this->assertNotNull($order->courier_rate);
+
+ // Verify PDFs were stored
+ $this->assertTrue(\Illuminate\Support\Facades\Storage::exists(
+ "public/shipments/{$order->uuid}/Shipment Label.pdf"
+ ));
+ $this->assertTrue(\Illuminate\Support\Facades\Storage::exists(
+ "public/shipments/{$order->uuid}/Shipment Sticker.pdf"
+ ));
+ }
+
+ /**
+ * Test that ECO service level is selected when available
+ */
+ public function test_eco_service_level_selection(): void
+ {
+ ShiplogicMockClient::setup();
+
+ $order = Order::factory()->create([
+ 'order_number' => 'TEST-002',
+ 'customer_name' => 'Jane Smith',
+ 'customer_email' => 'jane@example.com',
+ 'delivery_street' => '456 Oak Ave',
+ 'delivery_city' => 'London',
+ 'delivery_country' => 'GB',
+ 'packing_weight' => 1.5,
+ 'status' => 'ready_to_ship',
+ ]);
+
+ event(new \App\Events\ReadyToShipIntent($order));
+
+ $order->refresh();
+ // ECO variant should be selected (FEDEX_INTERNATIONAL_ECONOMY)
+ $this->assertStringContainsString('ECONOMY', $order->courier_service_level_code);
+ }
+}