Compare commits

..

No commits in common. "main" and "0.9" have entirely different histories.
main ... 0.9

330 changed files with 4571 additions and 19236 deletions

View File

@ -1,5 +0,0 @@
[Dolphin]
Timestamp=2024,11,17,16,40,18.95
Version=4
ViewMode=1
VisibleRoles=Icons_text,Icons_size

View File

@ -1,25 +0,0 @@
# .dockerignore
/deploy/docker-compose.yml
/deploy/Dockerfile
/.phpunit.cache
/node_modules
/public/build
/public/hot
/public/storage
/public/bucket
/storage/*.key
/vendor
.env
.env.example
.env.backup
.env.production
.phpunit.result.cache
Homestead.json
Homestead.yaml
auth.json
npm-debug.log
yarn-error.log
/.fleet
/.idea
/.vscode
.git

View File

@ -1,69 +0,0 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:vRgghlbIdXQxXIEvgUArbI9FURhgdyqx3LDXDwHYSmA=
APP_DEBUG=true
APP_TIMEZONE=UTC
APP_URL=http://localhost
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=testing
DB_USERNAME=sail
DB_PASSWORD=password
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"
SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://meilisearch:7700
MEILISEARCH_NO_ANALYTICS=false

View File

@ -1,27 +0,0 @@
name: Deploy
# Trigger the workflow on push and
# pull request events on the production branch
on:
push:
branches:
- main
pull_request:
branches:
- main
# Authenticate to the the server via ssh
# and run our deployment script
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
port: ${{ secrets.PORT }}
key: ${{ secrets.SSHKEY }}
script: "cd /var/www/sewtopnotch.com && ./.scripts/deploy.sh"

6
.gitignore vendored
View File

@ -103,9 +103,3 @@ fabric.properties
.idea/caches/build_file_checksums.ser
.idea
.directory
.directory
.directory
public
_ide_helper.php
public/build

View File

@ -1,9 +0,0 @@
const {join} = require('path');
/**
* @type {import("puppeteer").Configuration}
*/
module.exports = {
// Changes the cache location for Puppeteer.
cacheDirectory: join(__dirname, '.cache', 'puppeteer'),
};

View File

@ -1,32 +0,0 @@
#!/bin/bash
set -e
echo "Deployment started ..."
# Enter maintenance mode or return true
# if already is in maintenance mode
(php artisan down) || true
# Pull the latest version of the app from main branch
git pull origin main
# Install composer dependencies
composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader
# Clear the old cache
php artisan clear-compiled
# Recreate cache
php artisan optimize
# Npm stuff (ci will fail when lockfile modified)
npm ci
npm run build
# Run database migrations
php artisan migrate --force
# Exit maintenance mode
php artisan up
echo "Deployment finished!"

View File

@ -1,8 +1,66 @@
# Changelog
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
**2025-03-11**
- Fixed #122 - Non-admins can see payments
- Fixed #107 - Fix dashboard
- Fixed #118 - Improved customer form and re-add 'create customer' to order form
- Fixed #117 - Draft orders should not show up in order tabs
- Fixed #116 - 'Ready for invoice'-badge shows 0 instead of hiding
<p align="center">
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
## About Laravel
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, powerful, and provides tools required for large, robust applications.
## Learning Laravel
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
## Laravel Sponsors
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
### Premium Partners
- **[Vehikl](https://vehikl.com/)**
- **[Tighten Co.](https://tighten.co)**
- **[WebReinvent](https://webreinvent.com/)**
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
- **[64 Robots](https://64robots.com)**
- **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
- **[Cyber-Duck](https://cyber-duck.co.uk)**
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
- **[Jump24](https://jump24.co.uk)**
- **[Redberry](https://redberry.international/laravel/)**
- **[Active Logic](https://activelogic.com)**
- **[byte5](https://byte5.de)**
- **[OP.GG](https://op.gg)**
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
## License
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).

View File

@ -1,62 +0,0 @@
<?php
namespace App\Enums;
enum IconEnum: string
{
// Sidebar Icons
case DEFAULT = 'heroicon-o-rectangle-stack';
case INVOICE = 'lucide-file-text';
case ORDER = 'lucide-shopping-cart';
case QUOTE = 'lucide-quote';
case CUSTOMER = 'lucide-building';
case PACKING_SLIP = 'lucide-package';
case SHIPPING_ENTRY = 'lucide-truck';
case PAYMENTS = 'lucide-hand-coins';
case USER = 'lucide-users';
case TAX_RATE = 'lucide-circle-dollar-sign';
case PRODUCT_SERVICE = 'heroicon-o-rectangle';
case CUSTOMER_SALES = 'lucide-book-user';
case INVOICE_REPORT = 'lucide-file-spreadsheet';
case DISTRIBUTE_PAYMENTS = 'lucide-rotate-cw';
// Tabs
case TAB_ALL = 'lucide-layout-grid';
case TAB_OVERDUE = 'lucide-calendar-clock';
// Action Icons
case PRINT = 'lucide-printer';
case TRASH = 'lucide-trash-2';
case SAVE = 'lucide-save';
case COPY = 'lucide-copy';
case NEW = 'lucide-plus';
// Invoice Status
case UNPAID = 'lucide-circle-x';
case PARTIALLY_PAID = 'lucide-circle-minus';
case PAID = 'lucide-circle-check';
case VOID = 'lucide-circle-slash';
// Order Attributes
case NEW_ART = 'lucide-brush';
case REPEAT = 'lucide-files';
case RUSH = 'lucide-bell-ring';
case EVENT = 'lucide-calendar-range';
case DIGITIZING = 'lucide-computer';
case GARMENTS = 'lucide-shirt';
case SUPPLIED_FILE = 'lucide-file-check';
// Order Status
case DRAFT = 'lucide-pencil';
case APPROVED = 'lucide-check-check';
case PRODUCTION = 'lucide-refresh-cw';
case SHIPPED = 'lucide-send';
case INVOICING = 'lucide-calendar';
case INVOICED = 'lucide-credit-card';
// Shipping Types (THEY_SHIP => SHIPPING_ENTRY)
case WE_SHIP = 'lucide-house';
case PICKUP = 'lucide-handshake';
case SHIPPING_OTHER = 'lucide-ellipsis';
}

View File

@ -1,45 +0,0 @@
<?php
namespace App\Enums;
use Filament\Support\Contracts\HasColor;
use Filament\Support\Contracts\HasIcon;
use Filament\Support\Contracts\HasLabel;
enum InvoiceStatus: string implements HasColor, HasIcon, HasLabel
{
case UNPAID = 'not_paid';
case PARTIALLY_PAID = 'partially_paid';
case PAID = 'paid';
case VOID = 'void';
public function getLabel(): string
{
return match ($this) {
self::UNPAID => 'Not paid',
self::PARTIALLY_PAID => 'Partially paid',
self::PAID => 'Paid',
self::VOID => 'Void',
};
}
public function getColor(): string|array|null
{
return match ($this) {
self::UNPAID => 'danger',
self::PARTIALLY_PAID => 'warning',
self::PAID => 'success',
self::VOID => 'gray'
};
}
public function getIcon(): ?string
{
return match ($this) {
self::UNPAID => IconEnum::UNPAID->value,
self::PARTIALLY_PAID => IconEnum::PARTIALLY_PAID->value,
self::PAID => IconEnum::PAID->value,
self::VOID => IconEnum::VOID->value,
};
}
}

View File

@ -1,35 +0,0 @@
<?php
namespace App\Enums;
use Filament\Support\Contracts\HasIcon;
use Filament\Support\Contracts\HasLabel;
enum OrderAttributes: string implements HasIcon, HasLabel
{
case new_art = 'New Art';
case repeat = 'Repeat';
case rush = 'Rush';
case event = 'Event';
case digitizing = 'Digitizing';
case garments = 'Garments';
case supplied_file = 'Customer Supplied File';
public function getLabel(): ?string
{
return $this->value;
}
public function getIcon(): ?string
{
return match ($this) {
self::new_art => IconEnum::NEW_ART->value,
self::repeat => IconEnum::REPEAT->value,
self::rush => IconEnum::RUSH->value,
self::event => IconEnum::EVENT->value,
self::digitizing => IconEnum::DIGITIZING->value,
self::garments => IconEnum::GARMENTS->value,
self::supplied_file => IconEnum::SUPPLIED_FILE->value,
};
}
}

View File

@ -2,52 +2,10 @@
namespace App\Enums;
use Filament\Support\Contracts\HasColor;
use Filament\Support\Contracts\HasIcon;
use Filament\Support\Contracts\HasLabel;
enum OrderStatus: string implements HasColor, HasIcon, HasLabel
enum OrderStatus: string
{
case DRAFT = 'draft';
case APPROVED = 'approved';
case PRODUCTION = 'production';
case SHIPPED = 'shipped';
case READY_FOR_INVOICE = 'ready_for_invoice';
case INVOICED = 'invoiced';
public function getLabel(): string
{
return match ($this) {
self::DRAFT => 'Draft',
self::APPROVED => 'Approved',
self::PRODUCTION => 'Production',
self::SHIPPED => 'Shipped',
self::READY_FOR_INVOICE => 'Ready for Invoice',
self::INVOICED => 'Invoiced',
};
}
public function getColor(): string|array|null
{
return match ($this) {
self::DRAFT => 'gray',
self::APPROVED => 'success',
self::PRODUCTION => 'primary',
self::SHIPPED => 'warning',
self::READY_FOR_INVOICE => 'invoicing',
self::INVOICED => 'invoiced',
};
}
public function getIcon(): ?string
{
return match ($this) {
self::DRAFT => IconEnum::DRAFT->value,
self::APPROVED => IconEnum::APPROVED->value,
self::PRODUCTION => IconEnum::PRODUCTION->value,
self::SHIPPED => IconEnum::SHIPPED->value,
self::READY_FOR_INVOICE => IconEnum::INVOICING->value,
self::INVOICED => IconEnum::INVOICED->value,
};
}
case APPROVED = 'Approved';
case PRODUCTION = 'Production';
case SHIPPED = 'Shipped';
case INVOICED = 'Invoiced';
}

View File

@ -2,24 +2,11 @@
namespace App\Enums;
use Filament\Support\Contracts\HasLabel;
enum OrderType: string implements HasLabel
enum OrderType: string
{
case EMB = 'embroidery';
case SCP = 'screen_printing';
case DTG = 'direct_to_garment';
case VINYL = 'vinyl';
case MISC = 'misc';
public function getLabel(): string
{
return match ($this) {
self::EMB => 'Embroidery',
self::SCP => 'Screen printing',
self::DTG => 'Direct-to-garment',
self::VINYL => 'Vinyl',
self::MISC => 'Misc',
};
}
case EMBROIDERY = 'Embroidery';
case SCREEN = 'Screen printing';
case DTG = 'Direct-to-garment';
case VINYL = 'Vinyl';
case MISC = 'Misc';
}

View File

@ -2,28 +2,9 @@
namespace App\Enums;
use Filament\Support\Contracts\HasIcon;
use Filament\Support\Contracts\HasLabel;
enum ShippingType: string implements HasIcon, HasLabel
enum ShippingType: string
{
case THEY_SHIP = 'They ship';
case WE_SHIP = 'We ship';
case PICKUP = 'Pickup';
case OTHER = 'Other';
public function getLabel(): ?string
{
return $this->value;
}
public function getIcon(): ?string
{
return match ($this) {
self::THEY_SHIP => IconEnum::SHIPPING_ENTRY->value,
self::WE_SHIP => IconEnum::WE_SHIP->value,
self::PICKUP => IconEnum::PICKUP->value,
self::OTHER => IconEnum::SHIPPING_OTHER->value,
};
}
}

View File

@ -1,33 +0,0 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class InvoiceCreated
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*/
public function __construct()
{
//
}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('channel-name'),
];
}
}

View File

@ -1,87 +0,0 @@
<?php
namespace App\Filament\Admin\Pages;
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
use Filament\Facades\Filament;
use Filament\Forms\Components\Component;
use Filament\Forms\Components\TextInput;
use Filament\Http\Responses\Auth\Contracts\LoginResponse;
use Filament\Models\Contracts\FilamentUser;
use Filament\Pages\Auth\Login;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Validation\ValidationException;
class UsernameLogin extends Login
{
protected function getEmailFormComponent(): Component
{
return TextInput::make('username')
->label('Username')
->required()
->autofocus()
->extraInputAttributes(['tabindex' => 1])
->autocomplete();
}
protected function getCredentialsFromFormData(array $data): array
{
return [
'username' => $data['username'],
'password' => $data['password'],
];
}
public function authenticate(): ?LoginResponse
{
try {
$this->rateLimit(5);
} catch (TooManyRequestsException $exception) {
$this->getRateLimitedNotification($exception)?->send();
return null;
}
$data = $this->form->getState();
if (! Filament::auth()->attempt($this->getCredentialsFromFormData($data), $data['remember'] ?? false)) {
$this->throwFailureValidationException();
}
$user = Filament::auth()->user();
if (($user instanceof FilamentUser) && (! $user->canAccessPanel(Filament::getCurrentPanel()))) {
Filament::auth()->logout();
$this->throwFailureValidationException();
} elseif ($user->customer_id !== null) {
Filament::auth()->logout();
throw ValidationException::withMessages([
'data.username' => 'Incorrect username or password.',
]);
}
session()->regenerate();
return app(LoginResponse::class);
}
protected function throwFailureValidationException(): never
{
throw ValidationException::withMessages([
'data.username' => __('filament-panels::pages/auth/login.messages.failed'),
]);
}
public function getTitle(): Htmlable|string
{
return __('Login');
}
public function getHeading(): Htmlable|string
{
return __('Login');
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\ContactResource\Pages;
use App\Filament\Resources\ContactResource;
use Filament\Resources\Pages\CreateRecord;
class CreateContact extends CreateRecord
{
protected static string $resource = ContactResource::class;
}

View File

@ -1,19 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\ContactResource\Pages;
use App\Filament\Resources\ContactResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditContact extends EditRecord
{
protected static string $resource = ContactResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}

View File

@ -1,21 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\ContactResource\Pages;
use App\Enums\IconEnum;
use App\Filament\Resources\ContactResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListContacts extends ListRecords
{
protected static string $resource = ContactResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->icon(IconEnum::NEW->value),
];
}
}

View File

@ -1,146 +0,0 @@
<?php
namespace App\Filament\Admin\Resources;
use App\Enums\IconEnum;
use App\Models\Customer;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Support\Enums\FontWeight;
use Filament\Tables;
use Filament\Tables\Columns\Summarizers\Sum;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Query\Builder;
class CustomerReportResource extends Resource
{
protected static ?string $model = Customer::class;
protected static ?string $navigationIcon = IconEnum::CUSTOMER_SALES->value;
protected static ?string $navigationGroup = 'Reports';
protected static ?string $navigationLabel = 'Customer Reports';
protected static ?int $navigationSort = 4;
public static function form(Form $form): Form
{
return $form
->schema([
//
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('company_name')
->label('Customer')
->sortable()
// ->searchable()
->extraHeaderAttributes(['class' => 'w-full']),
Tables\Columns\TextColumn::make('invoices.subtotal')
->label('Subtotal')
->money()
->alignRight()
->getStateUsing(fn (Table $table, Model $record) => $record->invoices()->tap(fn ($q) => self::applyDateFilters($q, $table))->sum('subtotal'))
->summarize(Sum::make('subtotal')
->label('')
->money()
->using(fn (Table $table, $query) => $query->tap(fn ($q) => self::applyDateFilters($q, $table))->sum('subtotal'))
),
Tables\Columns\TextColumn::make('invoices.hst_amount')
->label('HST')
->money()
->alignRight()
->getStateUsing(fn (Table $table, Model $record) => $record->invoices()->tap(fn ($q) => self::applyDateFilters($q, $table))->sum('hst_amount'))
->summarize(Sum::make('hst_amount')
->label('')
->money()
->using(fn (Table $table, Builder $query) => $query->tap(fn ($q) => self::applyDateFilters($q, $table))->sum('hst_amount'))
),
Tables\Columns\TextColumn::make('invoices.gst_amount')
->label('GST')
->money()
->alignRight()
->getStateUsing(fn (Table $table, Model $record) => $record->invoices()->tap(fn ($q) => self::applyDateFilters($q, $table))->sum('gst_amount'))
->summarize(Sum::make('gst_amount')
->label('')
->money()
->using(fn (Table $table, $query) => $query->tap(fn ($q) => self::applyDateFilters($q, $table))->sum('gst_amount'))
),
Tables\Columns\TextColumn::make('invoices.pst_amount')
->label('PST')
->money()
->alignRight()
->getStateUsing(fn (Table $table, Model $record) => $record->invoices()->tap(fn ($q) => self::applyDateFilters($q, $table))->sum('pst_amount'))
->summarize(Sum::make('pst_amount')
->label('')
->money()
->using(fn (Table $table, $query) => $query->tap(fn ($q) => self::applyDateFilters($q, $table))->sum('pst_amount'))
),
Tables\Columns\TextColumn::make('invoices.total')
->label('Total')
->money()
->weight(FontWeight::Bold)
->alignRight()
->getStateUsing(fn (Table $table, Model $record) => $record->invoices()->tap(fn ($q) => self::applyDateFilters($q, $table))->sum('total'))
->summarize(Sum::make('total')
->label('')
->money()
->using(fn (Table $table, $query) => $query->tap(fn ($q) => self::applyDateFilters($q, $table))->sum('total'))
),
])
->filters([
Tables\Filters\Filter::make('created_at')
->form([
DatePicker::make('created_at')
->label('From date'),
]),
Tables\Filters\Filter::make('created_until')
->form([
DatePicker::make('created_until')
->label('Until date'),
]),
], layout: Tables\Enums\FiltersLayout::AboveContent);
}
protected static function applyDateFilters($query, Table $table): void
{
$createdAt = $table->getFilter('created_at')?->getState()['created_at'] ?? null;
$createdUntil = $table->getFilter('created_until')?->getState()['created_until'] ?? null;
$query->when($createdAt, fn ($q, $date) => $q->whereDate('created_at', '>=', $date));
$query->when($createdUntil, fn ($q, $date) => $q->whereDate('created_at', '<=', $date));
}
public static function canAccess(): bool
{
return auth()->user()->is_admin ?? false;
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => \App\Filament\Admin\Resources\CustomerReportResource\Pages\ListCustomerReports::route('/'),
];
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\CustomerReportResource\Pages;
use App\Filament\Admin\Resources\CustomerReportResource;
use Filament\Resources\Pages\CreateRecord;
class CreateCustomerReport extends CreateRecord
{
protected static string $resource = CustomerReportResource::class;
}

View File

@ -1,19 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\CustomerReportResource\Pages;
use App\Filament\Admin\Resources\CustomerReportResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditCustomerReport extends EditRecord
{
protected static string $resource = CustomerReportResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}

View File

@ -1,20 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\CustomerReportResource\Pages;
use App\Filament\Admin\Resources\CustomerReportResource;
use Filament\Resources\Pages\ListRecords;
class ListCustomerReports extends ListRecords
{
protected static string $resource = CustomerReportResource::class;
protected static ?string $title = 'Customer Reports';
protected function getHeaderActions(): array
{
return [
// Actions\CreateAction::make(),
];
}
}

View File

@ -1,118 +0,0 @@
<?php
namespace App\Filament\Admin\Resources;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\CustomerResource\RelationManagers\ContactsRelationManager;
use App\Filament\Admin\Resources\CustomerResource\RelationManagers\InvoicesRelationManager;
use App\Filament\Admin\Resources\CustomerResource\RelationManagers\PaymentsRelationManager;
use App\Filament\Admin\Resources\CustomerResource\RelationManagers\ShippingEntriesRelationManager;
use App\Models\Customer;
use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class CustomerResource extends Resource
{
protected static ?string $model = Customer::class;
protected static ?string $navigationIcon = IconEnum::CUSTOMER->value;
protected static ?string $navigationGroup = 'Management';
protected static ?int $navigationSort = 1;
public static function form(Form $form): Form
{
return $form
->columns(1)
->schema([
Section::make([
Fieldset::make('Primary Information')
->columns(1)
->columnSpan(fn (?Customer $record) => $record ? 1 : 3)
->schema([
TextInput::make('company_name')
->required(),
TextInput::make('phone'),
]),
Fieldset::make('Shipping Address')
->columns(1)
->columnSpan(fn (?Customer $record) => $record ? 1 : 3)
->schema([
TextInput::make('shipping_address_line_1')
->label('Line 1')
->placeholder('618 East Kent Ave S #108'),
TextInput::make('shipping_address_line_2')
->label('Line 2')
->placeholder('Vancouver, BC V5X 0B2, Canada'),
]),
Fieldset::make('Billing Address')
->columns(1)
->columnSpan(fn (?Customer $record) => $record ? 1 : 3)
->schema([
TextInput::make('billing_address_line_1')
->label('Line 1')
->placeholder('618 East Kent Ave S #108'),
TextInput::make('billing_address_line_2')
->label('Line 2')
->placeholder('Vancouver, BC V5X 0B2, Canada'),
]),
])
->columns(3)
->columnSpan(fn (?Customer $record) => $record ? 1 : 3),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('company_name')
->extraHeaderAttributes(['class' => 'w-full'])
->searchable()
->sortable(),
TextColumn::make('phone'),
TextColumn::make('balance')
->getStateUsing(fn (Customer $customer) => $customer->calculateBalance())
->money()
->hidden(! auth()->user()->is_admin ?? false),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
InvoicesRelationManager::class,
PaymentsRelationManager::class,
ContactsRelationManager::class,
ShippingEntriesRelationManager::class,
];
}
public static function getPages(): array
{
return [
'index' => \App\Filament\Admin\Resources\CustomerResource\Pages\ListCustomers::route('/'),
'edit' => \App\Filament\Admin\Resources\CustomerResource\Pages\EditCustomer::route('/{record}/edit'),
];
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\CustomerResource\Pages;
use App\Filament\Admin\Resources\CustomerResource;
use Filament\Resources\Pages\CreateRecord;
class CreateCustomer extends CreateRecord
{
protected static string $resource = CustomerResource::class;
}

View File

@ -1,21 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\CustomerResource\Pages;
use App\Filament\Admin\Resources\CustomerResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditCustomer extends EditRecord
{
protected static string $resource = CustomerResource::class;
protected function getHeaderActions(): array
{
// todo: make report
return [
Actions\DeleteAction::make(),
];
}
}

View File

@ -1,24 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\CustomerResource\Pages;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\CustomerResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListCustomers extends ListRecords
{
protected static string $resource = CustomerResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->modal()
->modalWidth('lg')
->icon(IconEnum::NEW->value)
->successRedirectUrl(fn ($record) => CustomerResource::getUrl('edit', ['record' => $record->id])),
];
}
}

View File

@ -1,58 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\CustomerResource\RelationManagers;
use App\Enums\IconEnum;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class ContactsRelationManager extends RelationManager
{
protected static string $relationship = 'contacts';
public function form(Form $form): Form
{
return $form
->schema([
TextInput::make('first_name'),
TextInput::make('last_name'),
TextInput::make('email')
->email(),
TextInput::make('phone'),
TextInput::make('notes'),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('id')
->columns([
TextColumn::make('full_name'),
TextColumn::make('email'),
TextColumn::make('phone'),
TextColumn::make('notes')
->extraHeaderAttributes(['class' => 'w-full']),
])
->filters([
//
])
->headerActions([
Tables\Actions\CreateAction::make()
->icon(IconEnum::NEW->value),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
// Tables\Actions\BulkActionGroup::make([
// Tables\Actions\DeleteBulkAction::make(),
// ]),
]);
}
}

View File

@ -1,17 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\CustomerResource\RelationManagers;
use App\Filament\Admin\Resources\InvoiceResource;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables\Table;
class InvoicesRelationManager extends RelationManager
{
protected static string $relationship = 'invoices';
public function table(Table $table): Table
{
return InvoiceResource::table($table);
}
}

View File

@ -1,36 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\CustomerResource\RelationManagers;
use App\Filament\Admin\Resources\PaymentResource;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
class PaymentsRelationManager extends RelationManager
{
protected static string $relationship = 'payments';
public function form(Form $form): Form
{
return $form
->schema([
// PaymentResource
// Forms\Components\TextInput::make('amount')
// ->required()
// ->maxLength(255),
]);
}
public function table(Table $table): Table
{
return PaymentResource::table($table);
}
public static function canViewForRecord(Model $ownerRecord, string $pageClass): bool
{
return auth()->user()->is_admin ?? false;
}
}

View File

@ -1,52 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\CustomerResource\RelationManagers;
use App\Enums\IconEnum;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Table;
class ShippingEntriesRelationManager extends RelationManager
{
protected static string $relationship = 'ShippingEntries';
public function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('id')
->required()
->maxLength(255),
]);
}
public function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('courier'),
Tables\Columns\TextColumn::make('account_title'),
Tables\Columns\TextColumn::make('account_username')
->label('Username'),
Tables\Columns\TextColumn::make('account_password')
->label('Password'),
Tables\Columns\TextColumn::make('info_needed'),
// ->extraHeaderAttributes(['class' => 'w-full']),
Tables\Columns\TextColumn::make('notes'),
])
->filters([
//
])
->headerActions([
Tables\Actions\CreateAction::make()
->icon(IconEnum::NEW->value),
])
->actions([
// Tables\Actions\EditAction::make(),
// Tables\Actions\DeleteAction::make(),
]);
}
}

View File

@ -1,130 +0,0 @@
<?php
namespace App\Filament\Admin\Resources;
use App\Enums\IconEnum;
use App\Enums\InvoiceStatus;
use App\Filament\Admin\Resources\InvoiceReportResource\RelationManagers\InvoicesRelationManager;
use App\Models\InvoiceReport;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Group;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Support\Enums\FontFamily;
use Filament\Support\Enums\FontWeight;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class InvoiceReportResource extends Resource
{
protected static ?string $navigationIcon = IconEnum::INVOICE_REPORT->value;
protected static ?string $navigationGroup = 'Reports';
protected static ?string $navigationLabel = 'Invoice Reports';
protected static ?int $navigationSort = 2;
public static function form(Form $form): Form
{
return $form
->schema([
Section::make([
Group::make([
Select::make('customer_id')
->relationship('customer', 'company_name')
->preload()
->required()
->columnSpanFull()
->searchable(),
ToggleButtons::make('payment_types')
->required()
->options(InvoiceStatus::class)
->multiple()
->columnSpanFull()
->inline(),
DatePicker::make('date_start')
->required()
->columnSpan(1),
DatePicker::make('date_end')
->required()
->default(today())
->columnSpan(1),
])->columnSpan(fn (?InvoiceReport $record) => $record === null ? 5 : 3)
->columns(2),
])
->columns(5)
->columnSpan(fn ($record) => $record === null ? 3 : 2),
Section::make([
Placeholder::make('created_at')
->label('Created')
->content(fn (InvoiceReport $record): ?string => $record->created_at?->diffForHumans()),
Placeholder::make('updated_at')
->label('Last modified')
->content(fn (InvoiceReport $record): ?string => $record->updated_at?->diffForHumans()),
])
->columnSpan(1)
->hidden(fn (?InvoiceReport $record) => $record === null)
->extraAttributes(['class' => 'h-full']),
])->columns(3);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('internal_id')
->label('ID')
->fontFamily(FontFamily::Mono)
->color('primary')
->searchable()
->sortable(),
TextColumn::make('customer.company_name')
->extraHeaderAttributes(['class' => 'w-full'])
->searchable(),
TextColumn::make('date_start')
->label('Start Date')
->date('Y-m-d'),
TextColumn::make('date_end')
->label('End Date')
->date('Y-m-d'),
TextColumn::make('total')
->weight(FontWeight::Bold)
->money(),
TextColumn::make('balance')
->weight(FontWeight::Bold)
->money(),
])
->defaultSort('id', 'desc');
}
public static function canAccess(): bool
{
return auth()->user()->is_admin ?? false;
}
public static function getRelations(): array
{
return [
InvoicesRelationManager::class,
];
}
public static function getPages(): array
{
return [
'index' => \App\Filament\Admin\Resources\InvoiceReportResource\Pages\ListInvoiceReports::route('/'),
// 'create' => \App\Filament\Admin\Resources\InvoiceReportResource\Pages\CreateInvoiceReport::route('/create'),
'view' => \App\Filament\Admin\Resources\InvoiceReportResource\Pages\ViewInvoiceReport::route('/{record}'),
];
}
}

View File

@ -1,7 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\InvoiceReportResource\Pages;
use Filament\Resources\Pages\CreateRecord;
class CreateInvoiceReport extends CreateRecord {}

View File

@ -1,20 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\InvoiceReportResource\Pages;
use App\Filament\Admin\Resources\InvoiceReportResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditInvoiceReport extends EditRecord
{
protected static string $resource = InvoiceReportResource::class;
protected function getHeaderActions(): array
{
return [
Actions\ViewAction::make(),
Actions\DeleteAction::make(),
];
}
}

View File

@ -1,48 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\InvoiceReportResource\Pages;
use App\Enums\IconEnum;
use App\Enums\InvoiceStatus;
use App\Filament\Admin\Resources\InvoiceReportResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListInvoiceReports extends ListRecords
{
protected static string $resource = InvoiceReportResource::class;
protected static ?string $title = 'Invoice Reports';
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->modalWidth('xl')
->icon(IconEnum::NEW->value)
->mutateFormDataUsing(function ($data) {
/* Initialize all payment statues to false,
map selected payment types to corresponding status,
assign filtered statuses to specific keys */
$paymentTypes = array_fill_keys(array_map(fn ($status) => $status->name, InvoiceStatus::cases()), false);
if (! empty($data['payment_types'])) {
foreach ($data['payment_types'] as $type) {
$statusName = InvoiceStatus::from($type)->name;
$paymentTypes[$statusName] = true;
}
}
foreach ($paymentTypes as $status => $value) {
$data['with_'.strtolower($status)] = $value;
}
unset($data['payment_types']);
return $data;
})
->successRedirectUrl(fn ($record) => InvoiceReportResource::getUrl('view', ['record' => $record->id])),
];
}
}

View File

@ -1,45 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\InvoiceReportResource\Pages;
use App\Enums\InvoiceStatus;
use App\Filament\Admin\Resources\InvoiceReportResource;
use App\Models\InvoiceReport;
use Filament\Actions\Action;
use Filament\Resources\Pages\ViewRecord;
use Illuminate\Contracts\Support\Htmlable;
class ViewInvoiceReport extends ViewRecord
{
protected static string $resource = InvoiceReportResource::class;
protected static ?string $title = 'View Invoice Report';
public function getTitle(): string|Htmlable
{
return parent::getTitle().' '.$this->record->internal_id;
}
public function mutateFormDataBeforeFill(array $data): array
{
foreach (InvoiceStatus::cases() as $case) {
$name = 'with_'.strtolower($case->name);
if ($data[$name]) {
$data['payment_types'][] = $case->value ?? null;
}
}
return $data;
}
protected function getHeaderActions(): array
{
return [
Action::make('print')
->icon('lucide-printer')
->url(fn (InvoiceReport $record) => route('pdf.invoice-report', $record))
->openUrlInNewTab(),
];
}
}

View File

@ -1,87 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\InvoiceReportResource\RelationManagers;
use App\Filament\Admin\Resources\InvoiceResource;
use App\Models\Invoice;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Support\Enums\FontWeight;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class InvoicesRelationManager extends RelationManager
{
protected static string $relationship = 'invoices';
public function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('id')
->required()
->maxLength(255),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('internal_id')
->recordUrl(fn ($record) => InvoiceResource::getUrl('edit', ['record' => $record->id]))
->columns([
TextColumn::make('internal_id')
->label('ID')
->extraHeaderAttributes(['class' => 'w-full'])
->color('primary'),
TextColumn::make('date')
->label('Created')
->date('Y-m-d'),
TextColumn::make('subtotal')
->alignRight()
->money(),
TextColumn::make('gst_amount')
->label('GST/HST')
->getStateUsing(function (Invoice $record) {
return $record->has_gst
? '$'.number_format($record->gst_amount, 2)
: ($record->has_hst ? '$'.number_format($record->hst_amount, 2) : '-');
})
->alignRight()
->money(),
TextColumn::make('pst_amount')
->label('PST')
->alignRight()
->formatStateUsing(function ($state) {
return $state == 0.00 ? '-' : '$'.$state;
}),
TextColumn::make('total')
->money()
->alignRight()
->weight(FontWeight::Medium),
TextColumn::make('balance')
->alignRight()
->getStateUsing(fn (Invoice $record) => $record->remainingBalance())
->money()
->weight(FontWeight::Bold),
TextColumn::make('status'),
])
->filters([
//
])
->headerActions([
])
->defaultSort('invoices.id')
->actions([
])
->bulkActions([
]);
}
}

View File

@ -1,330 +0,0 @@
<?php
namespace App\Filament\Admin\Resources;
use App\Enums\IconEnum;
use App\Enums\InvoiceStatus;
use App\Filament\Admin\Resources\CustomerResource\RelationManagers\InvoicesRelationManager;
use App\Filament\Admin\Resources\InvoiceResource\RelationManagers\OrdersRelationManager;
use App\Filament\Admin\Resources\InvoiceResource\RelationManagers\PaymentsRelationManager;
use App\Filament\Admin\Resources\InvoiceResource\RelationManagers\ProductServicesRelationManager;
use App\Models\Customer;
use App\Models\Invoice;
use App\Models\Payment;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Group;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Split;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Form;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
use Filament\Support\Enums\FontWeight;
use Filament\Tables;
use Filament\Tables\Actions\BulkAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
class InvoiceResource extends Resource
{
protected static ?string $model = Invoice::class;
protected static ?string $navigationIcon = IconEnum::INVOICE->value;
protected static ?string $navigationGroup = 'Financial';
protected static ?int $navigationSort = 1;
public static function form(Form $form): Form
{
return $form
->schema([
Group::make()
->schema([
Section::make([
Group::make([
Select::make('customer_id')
->required()
->label('Customer')
->options(Customer::all()->pluck('company_name', 'id'))
->reactive()
->searchable()
->disabledOn('edit')
->columnSpan(2),
Split::make([
DatePicker::make('date')
->required()
->default(today()),
DatePicker::make('due_date'),
])->columnSpan(2),
ToggleButtons::make('status')
->options(InvoiceStatus::class)
->required()
->inline()
->default(InvoiceStatus::UNPAID)
->columnSpan(2),
Grid::make(3)
->schema([
Toggle::make('has_gst')
->label('GST')
->inline(false)
->default(true),
Toggle::make('has_pst')
->label('PST')
->inline(false)
->default(false),
Toggle::make('has_hst')
->label('HST')
->inline(false)
->default(false),
]),
])->columnSpan(fn (?Invoice $record) => $record === null ? 2 : 1),
])
->columns(2)
->columnSpan(fn (?Invoice $record) => $record === null ? 3 : 2),
Section::make()
->schema([
Placeholder::make('Id')
->label('ID')
->content(fn (Invoice $record): ?string => $record->internal_id),
Placeholder::make('Amounts')
->content(fn (Invoice $record): ?string => 'Total: $'.number_format($record->total, 2).', balance: $'.number_format($record->remainingBalance(), 2)),
Placeholder::make('Tax Rates when created')
->content(fn (Invoice $record): ?string => $record->gst_rate.'% GST, '.$record->pst_rate.'% PST, '.$record->hst_rate.'% HST'),
Placeholder::make('created_at')
->label('Timestamps')
->content(fn (Invoice $record): ?string => 'Created at '.$record->created_at->format('Y-m-d').', updated at '.$record->updated_at->format('Y-m-d')),
])
->columnSpan(1)
->hidden(fn (?Invoice $record) => $record === null)
->extraAttributes(['class' => 'h-full']),
])
->columns(3)
->columnSpan(2),
])->columns(2);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('internal_id')
->extraHeaderAttributes(fn ($livewire) => $livewire::class === InvoicesRelationManager::class ? ['class' => 'w-full'] : [false])
->label('ID')
->fontFamily('mono')
->color('primary')
->sortable()
->searchable(query: function (Builder $query, $search) {
return $query->where('internal_id', 'like', "%{$search}%")
->orWhereHas('orders', function (Builder $query) use ($search) {
return $query->where('customer_po', 'like', "%{$search}%")
->orWhere('internal_po', 'like', "%{$search}%");
});
}),
TextColumn::make('customer.company_name')
->hidden(fn ($livewire) => $livewire::class === InvoicesRelationManager::class)
->sortable()
->extraHeaderAttributes(['class' => 'w-full'])
->searchable(),
TextColumn::make('created_at')
->label('Created')
->date('Y-m-d')
->searchable()
->sortable(),
TextColumn::make('subtotal')
->money()
->alignRight()
->sortable()
->searchable(),
// FIXME: sortable doesn't sort correctly
TextColumn::make('gst_amount')
->label('GST/HST')
->money()
->getStateUsing(function (Invoice $record) {
return $record->has_gst
? '$'.number_format($record->gst_amount, 2)
: ($record->has_hst ? '$'.number_format($record->hst_amount, 2) : '-');
})
->alignRight()
->searchable(query: function (Builder $query, string $search) {
$query->where(function ($query) use ($search) {
$query->where('hst_amount', 'like', "%{$search}%")
->orWhere('gst_amount', 'like', "%{$search}%");
});
}),
// ->sortable(query: function (Builder $query, string $direction) {
// $query->orderByRaw("COALESCE(hst_amount, gst_amount, 0) $direction");
// }),
TextColumn::make('pst_amount')
->label('PST')
->getStateUsing(function (Invoice $record) {
return $record->has_pst ? '$'.number_format($record->pst_amount, 2) : '-';
})
->alignRight()
->sortable()
->searchable(),
TextColumn::make('total')
->money()
->alignRight()
->weight(FontWeight::Medium)
->sortable()
->searchable(),
TextColumn::make('balance')
->getStateUsing(fn (Invoice $record) => $record->remainingBalance())
->searchable(query: fn (Builder $query, string $search) => $query->searchByBalance($search))
->label('Balance')
->money()
->alignRight()
->weight(FontWeight::Bold),
TextColumn::make('status')
->badge(InvoiceStatus::class)
->sortable(),
])
->filters([
Tables\Filters\Filter::make('created_at')
->form([
DatePicker::make('created_from')
->label('From date'),
DatePicker::make('created_until')
->label('Until date'),
])
->query(function (Builder $query, array $data): Builder {
return $query
->when(
$data['created_from'],
fn (Builder $query, $date): Builder => $query->whereDate('date', '>=', $date),
)
->when(
$data['created_until'],
fn (Builder $query, $date): Builder => $query->whereDate('date', '<=', $date),
);
}),
Tables\Filters\SelectFilter::make('status')
->options(InvoiceStatus::class),
], )
->groups([
'status',
])
->defaultSort('id', 'desc')
->actions([
Tables\Actions\EditAction::make()
->hidden(fn ($livewire) => $livewire::class === InvoicesRelationManager::class),
])
->bulkActions([
BulkAction::make('Create Payment')
->icon(IconEnum::PAYMENTS->value)
->form(fn ($form) => PaymentResource::form($form))
->action(function (Collection $records, array $data) {
if ($records->pluck('customer_id')->unique()->count() !== 1) {
Notification::make()
->title('Invalid order combination')
->body('Make sure all orders are from the same customer')
->danger()
->send();
return;
}
$payment = Payment::create([
'customer_id' => $records->pluck('customer_id')->first(),
'amount' => $data['amount'],
'date' => $data['date'],
'check_number' => $data['check_number'],
'notes' => $data['notes'],
]);
$payment->applyToInvoices($records);
Notification::make()
->title('Payment created successfully')
->success()
->send();
}),
Tables\Actions\BulkActionGroup::make([
Tables\Actions\BulkAction::make('Mark as paid')
->action(function (Collection $records) {
$records->each->setStatus(InvoiceStatus::PAID);
Notification::make()
->title(count($records).' item(s) saved successfully')
->success()
->send();
})
->icon('lucide-circle-check')
->deselectRecordsAfterCompletion(),
Tables\Actions\BulkAction::make('Mark as unpaid')
->action(function (Collection $records) {
$records->each->setStatus(InvoiceStatus::UNPAID);
Notification::make()
->title(count($records).' item(s) saved successfully')
->success()
->send();
})
->icon('lucide-circle-x')
->deselectRecordsAfterCompletion(),
Tables\Actions\DeleteBulkAction::make(),
])
->label('Other actions'),
])
->selectCurrentPageOnly();
}
public static function canAccess(): bool
{
return auth()->user()->is_admin ?? false;
}
public static function getRelations(): array
{
return [
OrdersRelationManager::class,
ProductServicesRelationManager::class,
PaymentsRelationManager::class,
];
}
public static function getPages(): array
{
return [
'index' => \App\Filament\Admin\Resources\InvoiceResource\Pages\ListInvoices::route('/'),
// 'create' => \App\Filament\Admin\Resources\InvoiceResource\Pages\CreateInvoice::route('/create'),
'edit' => \App\Filament\Admin\Resources\InvoiceResource\Pages\EditInvoice::route('/{record}/edit'),
];
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\InvoiceResource\Pages;
use App\Filament\Admin\Resources\InvoiceResource;
use Filament\Resources\Pages\CreateRecord;
class CreateInvoice extends CreateRecord
{
protected static string $resource = InvoiceResource::class;
}

View File

@ -1,32 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\InvoiceResource\Pages;
use App\Filament\Admin\Resources\InvoiceResource;
use App\Models\Invoice;
use Filament\Actions;
use Filament\Actions\Action;
use Filament\Resources\Pages\EditRecord;
use Illuminate\Contracts\Support\Htmlable;
class EditInvoice extends EditRecord
{
protected static string $resource = InvoiceResource::class;
public function getTitle(): string|Htmlable
{
return parent::getTitle().' '.$this->record->internal_id;
}
protected function getHeaderActions(): array
{
return [
Action::make('print')
->icon('lucide-printer')
->url(fn (Invoice $record) => route('invoice.pdf', $record))
->openUrlInNewTab(),
Actions\DeleteAction::make()
->icon('lucide-trash-2'),
];
}
}

View File

@ -1,51 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\InvoiceResource\Pages;
use App\Enums\IconEnum;
use App\Enums\InvoiceStatus;
use App\Filament\Admin\Resources\InvoiceResource;
use Filament\Actions;
use Filament\Resources\Components\Tab;
use Filament\Resources\Pages\ListRecords;
class ListInvoices extends ListRecords
{
protected static string $resource = InvoiceResource::class;
public function getTabs(): array
{
return [
'all' => Tab::make('All')
->icon(IconEnum::TAB_ALL->value),
'unpaid' => Tab::make('Unpaid')
->query(fn ($query) => $query->where('status', InvoiceStatus::UNPAID))
->icon(InvoiceStatus::UNPAID->getIcon()),
'partially_paid' => Tab::make('Partially Paid')
->query(fn ($query) => $query->where('status', InvoiceStatus::PARTIALLY_PAID))
->icon(InvoiceStatus::PARTIALLY_PAID->getIcon()),
'paid' => Tab::make('Paid')
->query(fn ($query) => $query->where('status', InvoiceStatus::PAID))
->icon(InvoiceStatus::PAID->getIcon()),
'void' => Tab::make('Void')
->query(fn ($query) => $query->where('status', InvoiceStatus::VOID))
->icon(InvoiceStatus::VOID->getIcon()),
];
}
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->modal()
->modalWidth('lg')
->icon(IconEnum::NEW->value)
->successRedirectUrl(fn ($record) => InvoiceResource::getUrl('edit', ['record' => $record->id])),
];
}
}

View File

@ -1,79 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\InvoiceResource\RelationManagers;
use App\Filament\Admin\Resources\OrderResource;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Support\Enums\FontFamily;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
class OrdersRelationManager extends RelationManager
{
protected static string $relationship = 'orders';
public function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('customer_po')
->required()
->maxLength(100),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('customer_po')
->recordUrl(fn ($record) => OrderResource::getUrl('edit', ['record' => $record->id]))
->columns([
Tables\Columns\TextColumn::make('internal_po')
->label('Internal PO')
->color('primary')
->fontFamily(FontFamily::Mono),
Tables\Columns\TextColumn::make('customer_po')
->label('Customer PO')
->color('code')
->weight('bold')
->extraHeaderAttributes(['class' => 'w-full']),
Tables\Columns\TextColumn::make('total_product_quantity')
->label('Total QTY')
->alignRight(),
Tables\Columns\TextColumn::make('total_service_price')
->alignRight()
->label('Total price')
->money(),
])
->filters([
//
])
->headerActions([
Tables\Actions\AssociateAction::make()
->multiple()
->preloadRecordSelect()
->recordSelectOptionsQuery(fn (Builder $query) => $query->where('customer_id', $this->ownerRecord->customer->id))
->after(function () {
$this->ownerRecord->calculateTotals();
}),
])
->actions([
Tables\Actions\DissociateAction::make()
->after(function () {
$this->ownerRecord->calculateTotals();
}),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DissociateBulkAction::make(),
]),
])
->inverseRelationship('invoice');
}
}

View File

@ -1,24 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\InvoiceResource\RelationManagers;
use App\Filament\Admin\Resources\PaymentResource;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables\Table;
class PaymentsRelationManager extends RelationManager
{
protected static string $relationship = 'payments';
public function form(Form $form): Form
{
return PaymentResource::form($form);
}
public function table(Table $table): Table
{
return PaymentResource::paymentRelationManagerTable($table)
->recordTitleAttribute('date');
}
}

View File

@ -1,64 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\InvoiceResource\RelationManagers;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Table;
class ProductServicesRelationManager extends RelationManager
{
protected static string $relationship = 'ProductServices';
public function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('id')
->required()
->maxLength(255),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('id')
->columns([
Tables\Columns\TextColumn::make('order.internal_po')
->label('WO')
->color('primary')
->fontFamily('mono')
->sortable(),
Tables\Columns\TextColumn::make('order.customer_po')
->label('PO')
->color('code')
->weight('bold')
->sortable(),
Tables\Columns\TextColumn::make('serviceType.name')
->label('Type')
->weight('bold')
->sortable(),
Tables\Columns\TextColumn::make('service_details'),
Tables\Columns\TextColumn::make('amount')
->label('QTY'),
Tables\Columns\TextColumn::make('amount_price')
->label('Rate')
->prefix('$'),
Tables\Columns\TextColumn::make('price')
->label('Amount')
->prefix('$'),
])
->filters([
])
->headerActions([
])
->actions([
])
->bulkActions([
])
->defaultPaginationPageOption('all');
}
}

View File

@ -1,471 +0,0 @@
<?php
namespace App\Filament\Admin\Resources;
use App\Enums\IconEnum;
use App\Enums\InvoiceStatus;
use App\Enums\OrderAttributes;
use App\Enums\OrderStatus;
use App\Enums\OrderType;
use App\Models\Invoice;
use App\Models\Order;
use App\Models\OrderProduct;
use App\Models\ProductService;
use App\Models\ServiceFile;
use App\Models\ServiceType;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Group;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Split;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Form;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
use Filament\Support\Enums\MaxWidth;
use Filament\Tables;
use Filament\Tables\Actions\BulkAction;
use Filament\Tables\Actions\BulkActionGroup;
use Filament\Tables\Columns\IconColumn\IconColumnSize;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Guava\FilamentClusters\Forms\Cluster;
use Icetalker\FilamentTableRepeater\Forms\Components\TableRepeater;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
class OrderResource extends Resource
{
protected static ?string $model = Order::class;
protected static ?string $navigationIcon = IconEnum::ORDER->value;
protected static ?string $navigationGroup = 'Production';
public static function form(Form $form): Form
{
return $form->schema([
Group::make()
->schema([
Section::make([
Grid::make(1)
->schema([
Select::make('order_type')
->required()
->options(OrderType::class)
->searchable(),
Select::make('customer_id')
->required()
->label('Customer')
->relationship(name: 'customer', titleAttribute: 'company_name')
->preload()
->createOptionForm(fn ($form) => CustomerResource::form($form))
->createOptionAction(fn ($action) => $action->modalWidth('lg'))
->searchable(),
TextInput::make('customer_po')
->required()
->label('Customer PO'),
Split::make([
DatePicker::make('order_date')
->required()
->default(today()),
DatePicker::make('due_date')
->required()
->default(today()->add('10 days')),
]),
Textarea::make('notes')
->rows(3),
])->columnSpan(1),
Grid::make(1)
->schema([
ToggleButtons::make('status')
->required()
->default(OrderStatus::DRAFT->value)
->options(OrderStatus::class)
->inline(),
ToggleButtons::make('order_attributes')
->options(OrderAttributes::class)
->multiple()
->inline(),
ToggleButtons::make('printed')
->boolean()
->default(false)
->inline(),
ToggleButtons::make('pre_production')
->label('Pre-production')
->default(false)
->boolean()
->inline()
->colors([
'true' => 'info',
'false' => 'info',
]),
])->columnSpan(1),
])->columns(2)
->columnSpan(fn (?Order $record) => $record === null ? 6 : 5),
Section::make()
->schema([
Placeholder::make('ID')
->label('Order ID')
->content(fn (Order $record): ?string => $record->internal_po),
Placeholder::make('total_service_price')
->label('Total service price')
->content(fn (Order $record): ?string => '$'.number_format($record->total_service_price, 2)),
Placeholder::make('created_at')
->label('Created')
->content(fn (Order $record): ?string => $record->created_at?->diffForHumans()),
Placeholder::make('updated_at')
->label('Last modified')
->content(fn (Order $record): ?string => $record->updated_at?->diffForHumans()),
])
->columnSpan(1)
->hidden(fn (?Order $record) => $record === null)
->extraAttributes(['class' => 'h-full']),
])
->columns(6)
->columnSpan(2),
TableRepeater::make('order_products')
->label('Garments')
->schema([
TextInput::make('sku')
->datalist(OrderProduct::all()->unique('sku')->pluck('sku')->toArray()),
TextInput::make('product_name')
->datalist(OrderProduct::all()->unique('product_name')->pluck('product_name')->toArray())
->required(),
TextInput::make('color')
->datalist(OrderProduct::all()->unique('color')->pluck('color')->toArray()),
Cluster::make([
TextInput::make('xs')
->placeholder('xs')
->rules('numeric'),
TextInput::make('s')
->placeholder('s')
->rules('numeric'),
TextInput::make('m')
->placeholder('m')
->rules('numeric'),
TextInput::make('l')
->placeholder('l')
->rules('numeric'),
TextInput::make('xl')
->placeholder('xl')
->rules('numeric'),
TextInput::make('2xl')
->placeholder('2xl')
->rules('numeric'),
TextInput::make('3xl')
->placeholder('3xl')
->rules('numeric'),
TextInput::make('osfa')
->placeholder('osfa')
->rules('numeric'),
])
->label('Sizes'),
])
->reorderable()
->cloneable()
->defaultItems(1),
Repeater::make('services')
->view('filament.forms.compact-repeater')
->label('Product Services')
->schema([
Grid::make(19)
->schema([
Select::make('serviceType')
->options(ServiceType::all()->pluck('value', 'id'))
->columnSpan(4)
->placeholder('Select...')
->searchable()
->createOptionForm([
TextInput::make('name')
->label('Code')
->placeholder('Abbreviation here (example: \'Emb\'')
->required(),
TextInput::make('value')
->placeholder('Full name here (example: \'Embroidery\'')
->required(),
])
->createOptionUsing(function (array $data): int {
return ServiceType::create($data)->getKey();
}),
TextInput::make('serviceFileName')
->datalist(ServiceFile::all()->unique('name')->pluck('name')->toArray())
->columnSpan(3)
->label('Logo Name'),
TextInput::make('placement')
->datalist(ProductService::all()->unique('placement')->pluck('placement')->toArray())
->columnSpan(3),
TextInput::make('serviceFileSetupNumber')
->label('Setup')
->columnSpan(1)
->rules('numeric'),
Cluster::make([
TextInput::make('serviceFileWidth')
->prefix('w')
->rules('numeric'),
TextInput::make('serviceFileHeight')
->prefix('h')
->rules('numeric'),
])
->label('Dimensions (inches)')
->columnSpan(4),
TextInput::make('amount')
->label('Quantity')
->live()
->prefix('#')
->columnSpan(2)
->rules('numeric'),
TextInput::make('amount_price')
->label('Amount')
->prefix('$')
->columnSpan(2)
->rules('numeric'),
]),
Grid::make(9)
->schema([
TextInput::make('serviceFileCode')
->label('Code')
->datalist(ServiceFile::all()->unique('code')->pluck('code')->toArray())
->columnSpan(1)
->placeholder('A1234'),
Textarea::make('notes')
->placeholder('Thread colors...')
->columnSpan(8),
]),
])
->reorderable()
->cloneable()
->columns(4)
->columnSpan(2)
->defaultItems(1),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\IconColumn::make('alert')
->getStateUsing(fn ($record) => $record->is_alert_danger || $record->is_alert_warning)
->label('')
->color(fn ($record) => $record->is_alert_danger ? 'danger' : 'warning')
->icon(function ($record) {
return $record->is_alert_danger
? 'lucide-calendar-clock' : ($record->rush
? OrderAttributes::rush->getIcon() : null);
})
->size(IconColumnSize::Medium),
TextColumn::make('internal_po')
->label('Internal PO')
->fontFamily('mono')
->color('info')
->searchable(query: function (Builder $query, $search) {
return $query
->where('internal_po', 'like', "%{$search}%")
->orWhereHas('productServices', function (Builder $query) use ($search) {
return $query->where('placement', 'like', "%{$search}%")
->orWhereHas('serviceFile', function (Builder $query) use ($search) {
return $query->where('code', 'like', "%{$search}%")
->orWhere('name', 'like', "%{$search}%");
});
});
})
->sortable(),
TextColumn::make('customer.company_name')
->searchable()
->sortable(),
TextColumn::make('customer_po')
->label('PO')
->wrap()
->weight('bold')
->color('code')
->searchable()
->sortable()
->extraHeaderAttributes([
'class' => 'w-full',
]),
TextColumn::make('order_date')
->searchable()
->sortable(),
TextColumn::make('due_date')
->searchable()
->sortable(),
TextColumn::make('status')
->badge()
->searchable()
->sortable(),
])
->defaultSort('order_date', 'desc')
->filters([
Tables\Filters\Filter::make('order_date')
->form([
DatePicker::make('created_from'),
DatePicker::make('created_until'),
])
->query(function (Builder $query, array $data): Builder {
return $query
->when(
$data['created_from'],
fn (Builder $query, $date): Builder => $query->whereDate('order_date', '>=', $date),
)
->when(
$data['created_until'],
fn (Builder $query, $date): Builder => $query->whereDate('order_date', '<=', $date),
);
}),
], )
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkAction::make('updateStatus')
->form([
Select::make('status')
->options(OrderStatus::class),
])
->modalHeading('Change selected orders status')
->modalWidth(MaxWidth::Medium)
->action(function (array $data, Collection $records): void {
foreach ($records as $record) {
$record->status = $data['status'];
$record->save();
}
Notification::make()
->title(count($records).' item(s) updated successfully')
->success()
->send();
})
->icon('lucide-pen')
->color('info')
->deselectRecordsAfterCompletion(),
BulkActionGroup::make([
BulkAction::make('Create individual invoices')
->icon(IconEnum::INVOICE->value)
->action(function (Collection $records): void {
[$invoiced, $toInvoice] = $records->partition(fn ($record) => $record->invoice);
$toInvoice->each(function ($record) {
$invoice = Invoice::create([
'customer_id' => $record->customer->id,
'date' => today(),
'status' => InvoiceStatus::UNPAID->value,
]);
$invoice->orders()->save($record);
$invoice->calculateTotals();
$record->update(['status' => OrderStatus::INVOICED->value]);
});
if ($invoiced->isNotEmpty()) {
Notification::make()
->title("{$invoiced->count()} orders are already invoiced")
->warning()
->send();
}
if ($toInvoice->isNotEmpty()) {
Notification::make()
->title("Successfully created {$toInvoice->count()} invoice(s)")
->success()
->send();
}
}),
BulkAction::make('Add all to new invoice')
->icon(IconEnum::REPEAT->value)
->action(function (Collection $records): void {
if ($records->pluck('customer_id')->unique()->count() !== 1) {
Notification::make()
->title('Invalid order combination')
->body('Make sure all orders are from the same customer')
->danger()
->send();
return;
}
[$invoiced, $validOrders] = $records->partition(fn ($record) => $record->invoice);
if ($validOrders->isNotEmpty()) {
$invoice = Invoice::create([
'customer_id' => $records->first()->customer_id,
'date' => today(),
'status' => InvoiceStatus::UNPAID->value,
]);
$invoice->orders()->saveMany($validOrders);
$invoice->calculateTotals(); // FIXME: Investigate why this is needed.
Order::whereIn('id', $validOrders->pluck('id'))->update([
'status' => OrderStatus::INVOICED->value,
]);
}
if ($invoiced->isNotEmpty()) {
Notification::make()
->title('Some orders are already invoiced')
->body("{$invoiced->count()} orders are already invoiced and will not be added")
->warning()
->send();
}
if ($validOrders->isNotEmpty()) {
Notification::make()
->title('Invoice created')
->body("{$validOrders->count()} orders have been added to this invoice")
->success()
->send();
}
}),
])
->label('Invoicing')
->hidden(fn () => ! auth()->user()->is_admin),
BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
])->label('Other actions'),
]);
}
public static function getPages(): array
{
return [
'index' => \App\Filament\Admin\Resources\OrderResource\Pages\ListOrders::route('/'),
'create' => \App\Filament\Admin\Resources\OrderResource\Pages\CreateOrder::route('/create'),
'edit' => \App\Filament\Admin\Resources\OrderResource\Pages\EditOrder::route('/{record}/edit'),
];
}
}

View File

@ -1,82 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\OrderResource\Pages;
use App\Enums\OrderAttributes;
use App\Filament\Admin\Resources\OrderResource;
use App\Models\Order;
use App\Models\OrderProduct;
use App\Models\ProductService;
use App\Models\ProductSize;
use App\Models\ServiceFile;
use App\Models\ServiceType;
use Filament\Resources\Pages\CreateRecord;
class CreateOrder extends CreateRecord
{
protected static string $resource = OrderResource::class;
protected function handleRecordCreation(array $data): Order
{
// Attributes
foreach (OrderAttributes::cases() as $case) {
$data[$case->name] = false;
}
$data['order_attributes'] = array_filter($data['order_attributes']);
foreach ($data['order_attributes'] as $attribute) {
$data[OrderAttributes::from($attribute)->name] = true;
}
unset($data['order_attributes']);
$order = Order::create($data);
// Create Order Products
foreach ($data['order_products'] as $product) {
$orderProduct = OrderProduct::create([
'sku' => $product['sku'],
'product_name' => $product['product_name'],
'color' => $product['color'],
'order_id' => $order->id,
]);
$sizes = ['xs', 's', 'm', 'l', 'xl', '2xl', '3xl', 'osfa'];
foreach ($sizes as $size) {
if ($product[$size] > 0) {
ProductSize::create([
'amount' => $product[$size],
'size' => $size,
'order_product_id' => $orderProduct->id,
]);
}
}
}
// ProductServices and ServiceFiles
foreach ($data['services'] as $service) {
$serviceFile = ServiceFile::create([
'name' => strtoupper($service['serviceFileName']) ?? '',
'code' => strtoupper($service['serviceFileCode']) ?? '',
'width' => $service['serviceFileWidth'] ?? null,
'height' => $service['serviceFileHeight'] ?? null,
'setup_number' => $service['serviceFileSetupNumber'] ?? null,
]);
ProductService::create([
'service_type_id' => ServiceType::findOrFail($service['serviceType'])->id ?? null,
'placement' => strtoupper($service['placement']) ?? null,
'notes' => strtoupper($service['notes']) ?? null,
'amount' => $service['amount'] ?? null,
'amount_price' => $service['amount_price'] ?? null,
'total_price' => $service['total_price'] ?? null,
'service_file_id' => $serviceFile->id,
'order_id' => $order->id,
]);
}
return $order;
}
}

View File

@ -1,241 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\OrderResource\Pages;
use App\Enums\IconEnum;
use App\Enums\InvoiceStatus;
use App\Enums\OrderAttributes;
use App\Enums\OrderStatus;
use App\Filament\Admin\Resources\InvoiceResource;
use App\Filament\Admin\Resources\OrderResource;
use App\Models\Invoice;
use App\Models\Order;
use App\Models\OrderProduct;
use App\Models\ProductService;
use App\Models\ProductSize;
use App\Models\ServiceFile;
use App\Models\ServiceType;
use Filament\Actions;
use Filament\Actions\Action;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\EditRecord;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Database\Eloquent\Model;
class EditOrder extends EditRecord
{
protected static string $resource = OrderResource::class;
public function getTitle(): string|Htmlable
{
return parent::getTitle().' '.$this->record->internal_po;
}
protected function mutateFormDataBeforeFill(array $data): array
{
$order = Order::findOrFail($data['id']);
// Order Products
foreach ($order->orderProducts as $key => $product) {
$data['order_products'][$key] = [
'sku' => $product->sku,
'product_name' => $product->product_name,
'color' => $product->color,
'xs' => $product->productSizes->where('size', 'xs')->first()->amount ?? null,
's' => $product->productSizes->where('size', 's')->first()->amount ?? null,
'm' => $product->productSizes->where('size', 'm')->first()->amount ?? null,
'l' => $product->productSizes->where('size', 'l')->first()->amount ?? null,
'xl' => $product->productSizes->where('size', 'xl')->first()->amount ?? null,
'2xl' => $product->productSizes->where('size', '2xl')->first()->amount ?? null,
'3xl' => $product->productSizes->where('size', '3xl')->first()->amount ?? null,
'osfa' => $product->productSizes->where('size', 'osfa')->first()->amount ?? null,
];
}
// Product Services
foreach ($order->productServices as $key => $service) {
$data['services'][$key] = [
'placement' => $service->placement ?? '',
'amount' => $service->amount ?? '',
'amount_price' => $service->amount_price ?? '',
'notes' => $service->notes ?? '',
'serviceType' => $service->serviceType->id ?? '',
'serviceFileName' => $service->serviceFile->name ?? '',
'serviceFileWidth' => $service->serviceFile->width ?? '',
'serviceFileHeight' => $service->serviceFile->height ?? '',
'serviceFileCode' => $service->serviceFile->code ?? '',
'serviceFileSetupNumber' => $service->serviceFile->setup_number ?? '',
];
}
foreach (OrderAttributes::cases() as $case) {
if ($data[$case->name]) {
$data['order_attributes'][] = $case->value ?? null;
}
}
return $data;
}
public function handleRecordUpdate(Model $record, array $data): Model
{
// Correctly set attribute booleans
foreach (OrderAttributes::cases() as $case) {
$data[$case->name] = false;
}
$data['order_attributes'] = array_filter($data['order_attributes']);
foreach ($data['order_attributes'] as $attribute) {
$data[OrderAttributes::from($attribute)->name] = true;
}
unset($data['order_attributes']);
$record->update($data);
// Delete old and create new Order Products
foreach ($record->orderProducts as $product) {
foreach ($product->productSizes as $size) {
$size->delete();
}
$product->delete();
}
foreach ($data['order_products'] as $product) {
$orderProduct = OrderProduct::create([
'sku' => $product['sku'],
'product_name' => $product['product_name'],
'color' => $product['color'],
'order_id' => $record->id,
]);
$sizes = ['xs', 's', 'm', 'l', 'xl', '2xl', '3xl', 'osfa'];
foreach ($sizes as $size) {
if ($product[$size] > 0) {
ProductSize::create([
'amount' => $product[$size],
'size' => $size,
'order_product_id' => $orderProduct->id,
]);
}
}
}
// Delete old and create new services
foreach ($record->productServices as $service) {
$service->delete();
}
foreach ($data['services'] as $service) {
$serviceFile = ServiceFile::create([
'name' => strtoupper($service['serviceFileName']) ?? '',
'code' => strtoupper($service['serviceFileCode']) ?? '',
'width' => $service['serviceFileWidth'] ?? null,
'height' => $service['serviceFileHeight'] ?? null,
'setup_number' => $service['serviceFileSetupNumber'] ?? null,
]);
ProductService::create([
'service_type_id' => ServiceType::findOrFail($service['serviceType'])->id ?? null,
'placement' => strtoupper($service['placement']) ?? null,
'notes' => strtoupper($service['notes']) ?? null,
'amount' => $service['amount'] ?? null,
'amount_price' => $service['amount_price'] ?? null,
'total_price' => $service['total_price'] ?? null,
'service_file_id' => $serviceFile->id,
'order_id' => $record->id,
]);
}
return $record;
}
protected function getHeaderActions(): array
{
return [
Action::make('save')
->label('Save changes')
->action('save')
->icon(IconEnum::SAVE->value),
Actions\ReplicateAction::make()
->label('Duplicate')
->icon(IconEnum::COPY->value)
->mutateRecordDataUsing(function (array $data): array {
$po = 'Duplicate of '.$data['customer_po'];
$data['customer_po'] = $po;
return $data;
})
->beforeReplicaSaved(function (Order $replica): void {
$replica->customer_po = 'Repeat of '.$replica->customer_po;
$replica->status = OrderStatus::DRAFT;
$replica->printed = false;
$replica->pre_production = false;
$replica->order_date = today();
$replica->due_date = today()->addDays(10);
$replica->save();
foreach ($this->record->orderProducts as $product) {
$newProduct = $product->replicate();
$newProduct->order_id = $replica->id;
$newProduct->save();
foreach ($product->productSizes as $size) {
$newSize = $size->replicate();
$newSize->order_product_id = $newProduct->id;
$newSize->save();
}
}
/** @var ProductService $service */
foreach ($this->record->productServices as $service) {
/** @var ServiceFile $newServiceFile */
$newServiceFile = $service->serviceFile->replicate();
$newService = $service->replicate();
$newService->order_id = $replica->id;
$newService->service_file_id = $newServiceFile->id;
$newService->save();
}
})
->successRedirectUrl(fn (Model $replica): string => OrderResource::getUrl('edit', [$replica])),
Action::make('invoice')
->visible(fn () => auth()->user()->is_admin)
->label(fn (Order $record) => $record->invoice()->exists() ? 'To Invoice' : 'Make Invoice')
->icon(IconEnum::INVOICE->value)
->action(function (Order $record) {
if ($record->invoice()->exists()) {
return redirect()->to(InvoiceResource::getUrl('edit', ['record' => $record->invoice->id]));
}
$invoice = Invoice::create([
'customer_id' => $record->customer_id,
'date' => today(),
'status' => InvoiceStatus::UNPAID->value,
]);
$invoice->orders()->save($record);
return Notification::make()
->title('Invoice '.$invoice->internal_id.' created successfully')
->body('Click the button again to go to the invoice')
->success()
->send();
}),
Action::make('print')
->icon(IconEnum::PRINT->value)
->url(fn (Order $record) => route('orders.pdf', $record))
->openUrlInNewTab(),
Actions\DeleteAction::make()
->icon(IconEnum::TRASH->value),
];
}
}

View File

@ -1,83 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\OrderResource\Pages;
use App\Enums\IconEnum;
use App\Enums\OrderAttributes;
use App\Enums\OrderStatus;
use App\Filament\Admin\Resources\OrderResource;
use App\Models\Order;
use Filament\Actions;
use Filament\Resources\Components\Tab;
use Filament\Resources\Pages\ListRecords;
class ListOrders extends ListRecords
{
protected static string $resource = OrderResource::class;
private function excludeStatuses($query): mixed
{
return $query
->whereNot('status', OrderStatus::DRAFT)
->whereNot('status', OrderStatus::READY_FOR_INVOICE)
->whereNot('status', OrderStatus::INVOICED)
->whereNot('status', OrderStatus::SHIPPED);
}
private function getBadgeCount(callable $queryCallback): ?int
{
$count = Order::query()->when(true, $queryCallback)
->whereNot('status', OrderStatus::DRAFT)
->whereNot('status', OrderStatus::READY_FOR_INVOICE)
->whereNot('status', OrderStatus::INVOICED)
->whereNot('status', OrderStatus::SHIPPED)
->count();
return $count > 0 ? $count : null;
}
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->icon(IconEnum::NEW->value),
];
}
public function getTabs(): array
{
return [
'all' => Tab::make('All')
->icon(IconEnum::TAB_ALL->value),
'active' => Tab::make()
->query(fn ($query) => $this->excludeStatuses($query))
->icon(OrderStatus::PRODUCTION->getIcon())
->badge(fn () => $this->getBadgeCount(fn ($query) => $this->excludeStatuses($query))),
'unprinted' => Tab::make()
->query(fn ($query) => $this->excludeStatuses($query)->where('printed', false))
->icon(IconEnum::PRINT->value)
->badge(fn () => $this->getBadgeCount(fn ($query) => $query->where('printed', false)))
->badgeColor('success'),
'overdue' => Tab::make()
->query(fn ($query) => $this->excludeStatuses($query)->whereDate('due_date', '<=', today()))
->icon(IconEnum::TAB_OVERDUE->value)
->badge(fn () => $this->getBadgeCount(fn ($query) => $query->whereDate('due_date', '<=', today())))
->badgeColor('danger'),
'rush' => Tab::make()
->query(fn ($query) => $this->excludeStatuses($query)->where('rush', true))
->icon(OrderAttributes::rush->getIcon())
->badge(fn () => $this->getBadgeCount(fn ($query) => $query->where('rush', true)))
->badgeColor('warning'),
'ready_for_invoice' => Tab::make()
->query(fn ($query) => $query->where('status', OrderStatus::READY_FOR_INVOICE))
->icon(OrderStatus::READY_FOR_INVOICE->getIcon())
->badge(fn () => ($count = Order::query()->where('status', OrderStatus::READY_FOR_INVOICE)->count()) > 0 ? $count : null)
->badgeColor(OrderStatus::READY_FOR_INVOICE->getColor()),
];
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\OrderResource\Pages;
use App\Filament\Admin\Resources\OrderResource;
use Filament\Resources\Pages\ViewRecord;
class ViewOrder extends ViewRecord
{
protected static string $resource = OrderResource::class;
}

View File

@ -1,54 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\OrderResource\RelationManagers;
use App\Enums\IconEnum;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Table;
class OrderProductsRelationManager extends RelationManager
{
protected static string $relationship = 'orderProducts';
public function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('id')
->required()
->maxLength(255),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('id')
->columns([
Tables\Columns\TextColumn::make('sku'),
Tables\Columns\TextColumn::make('product_name'),
Tables\Columns\TextColumn::make('color'),
])
->filters([
//
])
->headerActions([
Tables\Actions\CreateAction::make()
->icon(IconEnum::NEW->value),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make()
->icon(IconEnum::TRASH->value),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make()
->icon(IconEnum::TRASH->value),
]),
]);
}
}

View File

@ -1,119 +0,0 @@
<?php
namespace App\Filament\Admin\Resources;
use App\Enums\IconEnum;
use App\Models\Customer;
use App\Models\Order;
use App\Models\PackingSlip;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Split;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class PackingSlipResource extends Resource
{
protected static ?string $model = PackingSlip::class;
protected static ?string $navigationIcon = IconEnum::PACKING_SLIP->value;
protected static ?string $navigationGroup = 'Production';
protected static ?int $navigationSort = 2;
public static function form(Form $form): Form
{
return $form
->schema([
Section::make([
Split::make([
Grid::make(1)
->schema([
DatePicker::make('date_received')
->default(today())
->required(),
Select::make('customer_id')
->label('Customer')
->options(Customer::all()->pluck('company_name', 'id'))
->reactive()
->searchable(),
Select::make('order_id')
->label('Order')
->options(fn ($get): array => Order::where('customer_id', $get('customer_id') ?? null)
->get()
->pluck('customer_po', 'id')
->toArray())
->searchable(),
])
->columnSpan(1),
Grid::make(1)
->schema([
TextArea::make('contents')
->rows(9),
])
->columnSpan(1),
]),
]),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('date_received')
->sortable()
->searchable(),
TextColumn::make('order.customer_po')
// ->url(fn ($record) => $record->to)
->url(fn ($record) => OrderResource::getUrl('edit', ['record' => $record->id]))
->weight('bold')
->color('code')
->sortable()
->searchable(),
TextColumn::make('contents')
->extraHeaderAttributes(['class' => 'w-full']),
// TextColumn::make('amount')
// ->label('Quantity'),
TextColumn::make('order.customer.company_name')
->sortable()
->searchable(),
])
->defaultSort('date_received', 'desc')
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => \App\Filament\Admin\Resources\PackingSlipResource\Pages\ListPackingSlips::route('/'),
// 'create' => \App\Filament\Admin\Resources\PackingSlipResource\Pages\CreatePackingSlip::route('/create'),
// 'edit' => \App\Filament\Admin\Resources\PackingSlipResource\Pages\EditPackingSlip::route('/{record}/edit'),
];
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\PackingSlipResource\Pages;
use App\Filament\Admin\Resources\PackingSlipResource;
use Filament\Resources\Pages\CreateRecord;
class CreatePackingSlip extends CreateRecord
{
protected static string $resource = PackingSlipResource::class;
}

View File

@ -1,21 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\PackingSlipResource\Pages;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\PackingSlipResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditPackingSlip extends EditRecord
{
protected static string $resource = PackingSlipResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make()
->icon(IconEnum::TRASH->value),
];
}
}

View File

@ -1,21 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\PackingSlipResource\Pages;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\PackingSlipResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListPackingSlips extends ListRecords
{
protected static string $resource = PackingSlipResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->icon(IconEnum::NEW->value),
];
}
}

View File

@ -1,149 +0,0 @@
<?php
namespace App\Filament\Admin\Resources;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\InvoiceResource\Pages\ListInvoices;
use App\Filament\Admin\Resources\PaymentResource\Pages;
use App\Filament\Admin\Resources\PaymentResource\RelationManagers\InvoicesRelationManager;
use App\Models\Payment;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Group;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class PaymentResource extends Resource
{
protected static ?string $model = Payment::class;
protected static ?string $navigationIcon = IconEnum::PAYMENTS->value;
protected static ?string $navigationGroup = 'Financial';
protected static ?int $navigationSort = 2;
public static function form(Form $form): Form
{
return $form
->schema([
Section::make([
Group::make([
Select::make('customer_id')
->relationship('customer', 'company_name')
->required()
->searchable()
->hidden(fn ($livewire) => $livewire::class === ListInvoices::class)
->preload()
->columnSpanFull(),
TextInput::make('amount')
->required()
->prefix('$')
->rules('numeric')
->minValue(0)
->maxValue(99999999)
->columnSpan(3),
TextInput::make('check_number')
->columnSpan(6),
DatePicker::make('date')
->default(today())
->columnSpan(4),
Placeholder::make('break_2')->columnSpan(3)->hiddenLabel(),
Textarea::make('notes')
->columnSpanFull(),
])->columnSpan(fn (?Payment $record) => $record === null ? 9 : 3)
->columns(9),
])->columns(9),
]);
}
public static function table(Table $table, ?bool $showSearchable = true): Table
{
return $table
->columns([
TextColumn::make('created_at')
->label('Date')
->date('Y-m-d')
->searchable($showSearchable),
TextColumn::make('customer.company_name')
->hidden(fn ($livewire) => $livewire::class !== Pages\ListPayments::class)
->searchable($showSearchable),
TextColumn::make('check_number')
->searchable($showSearchable)
->extraHeaderAttributes(['class' => 'w-full']),
TextColumn::make('amount')
->searchable($showSearchable)
->alignRight()
->money(),
TextColumn::make('unapplied_amount')
->searchable($showSearchable)
->label('Balance')
->alignRight()
->money(),
])
->actions([
\Filament\Tables\Actions\EditAction::make(),
]);
}
public static function paymentRelationManagerTable(Table $table): Table
{
return $table
->columns([
TextColumn::make('id')
->color('primary'),
TextColumn::make('created_at')
->label('Date')
->date('Y-m-d'),
TextColumn::make('check_number')
->extraHeaderAttributes(['class' => 'w-full']),
TextColumn::make('amount')
->label('Total amount')
->alignRight()
->money(),
TextColumn::make('applied_amount')
->alignRight()
->money(),
]);
}
public static function canAccess(): bool
{
return auth()->user()->is_admin ?? false;
}
public static function getRelations(): array
{
return [
InvoicesRelationManager::class,
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListPayments::route('/'),
'edit' => Pages\EditPayment::route('/{record}/edit'),
];
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\PaymentResource\Pages;
use App\Filament\Admin\Resources\PaymentResource;
use Filament\Resources\Pages\CreateRecord;
class CreatePayment extends CreateRecord
{
protected static string $resource = PaymentResource::class;
}

View File

@ -1,19 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\PaymentResource\Pages;
use App\Filament\Admin\Resources\PaymentResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditPayment extends EditRecord
{
protected static string $resource = PaymentResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}

View File

@ -1,37 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\PaymentResource\Pages;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\PaymentResource;
use App\Services\PaymentService;
use Filament\Actions;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\ListRecords;
class ListPayments extends ListRecords
{
protected static string $resource = PaymentResource::class;
protected function getHeaderActions(): array
{
return [
/* Actions\Action::make('distributePayments')
->icon(IconEnum::DISTRIBUTE_PAYMENTS->value)
->action(function (PaymentService $paymentService) {
$paymentService->distributePayments();
Notification::make()
->title('Success!')
->body('Payments have been distributed')
->success()
->send();
}),*/
Actions\CreateAction::make()
->modalWidth('lg')
->icon(IconEnum::NEW->value)
->successRedirectUrl(fn ($record) => PaymentResource::getUrl('edit', ['record' => $record->id])),
];
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\PaymentResource\Pages;
use App\Filament\Admin\Resources\PaymentResource;
use Filament\Resources\Pages\ViewRecord;
class ViewPayment extends ViewRecord
{
protected static string $resource = PaymentResource::class;
}

View File

@ -1,36 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\PaymentResource\RelationManagers;
use App\Filament\Admin\Resources\InvoiceResource;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class InvoicesRelationManager extends RelationManager
{
protected static string $relationship = 'invoices';
public function form(Form $form): Form
{
return $form
->schema([
TextInput::make('internal_id')
->required()
->maxLength(255),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('internal_id')
->columns([
TextColumn::make('internal_id')
->color('primary')
->url(fn ($record) => InvoiceResource::getUrl('edit', ['record' => $record->id])),
]);
}
}

View File

@ -1,272 +0,0 @@
<?php
namespace App\Filament\Admin\Resources;
use App\Enums\IconEnum;
use App\Models\Customer;
use App\Models\Quote;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Group;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Icetalker\FilamentTableRepeater\Forms\Components\TableRepeater;
class QuoteResource extends Resource
{
protected static ?string $model = Quote::class;
protected static ?string $navigationIcon = IconEnum::QUOTE->value;
protected static ?string $navigationGroup = 'Production';
protected static ?int $navigationSort = 1;
public static function form(Form $form): Form
{
return $form
->schema([
Grid::make(3)
->schema([
Section::make([
Group::make([
Select::make('customer_id')
->required()
->label('Customer')
->options(Customer::all()->pluck('company_name', 'id'))
->reactive()
->searchable()
->columnSpan(1),
DatePicker::make('date')
->default(today())
->required(),
TextArea::make('notes')
->rows(3)
->columnSpan(2),
]),
])
->columns(2)
->columnSpan(fn (?Quote $record) => $record === null ? 3 : 2)
->extraAttributes(['class' => 'h-full']),
Section::make()
->schema([
Placeholder::make('Id')
->label('ID')
->content(fn (Quote $record): ?string => $record->id),
Placeholder::make('created_at')
->content(fn (Quote $record): ?string => $record->created_at?->diffForHumans().' at '.$record->created_at->format('Y-m-d')),
Placeholder::make('updated_at')
->content(fn (Quote $record): ?string => $record->updated_at?->diffForHumans().' at '.$record->updated_at->format('Y-m-d')),
])
->columnSpan(1)
->hidden(fn (?Quote $record) => $record === null)
->extraAttributes(['class' => 'h-full']),
]),
TableRepeater::make('embroideryEntries')
->relationship('embroideryEntries')
->schema([
TextInput::make('logo')
->label('Logo name'),
TextInput::make('placement'),
TextInput::make('quantity')
->prefix('#')
->rules('numeric'),
TextInput::make('width')
->suffix('inch')
->rules('numeric'),
TextInput::make('height')
->suffix('inch')
->rules('numeric'),
TextInput::make('stitch_count'),
TextInput::make('digitizing_cost')
->prefix('$')
->rules('numeric'),
TextInput::make('run_charge')
->prefix('$')
->rules('numeric'),
])
->addActionLabel('Add Embroidery Entry')
->reorderable()
->defaultItems(0)
->colStyles([
'logo' => 'width: 15%',
'placement' => 'width: 15%',
'quantity' => 'width: 10%',
'width' => 'width: 11%',
'height' => 'width: 11%',
'stitch_count' => 'width: 16%',
'digitizing_cost' => 'width: 11%',
'run_charge' => 'width: 11%',
]),
TableRepeater::make('screenPrintEntries')
->relationship('screenPrintEntries')
->schema([
TextInput::make('logo')
->label('Logo name')
->columnSpan(2),
TextInput::make('placement'),
TextInput::make('quantity')
->rules(['numeric'])
->label('Qty'),
TextInput::make('width')
->rules('numeric'),
TextInput::make('height')
->rules('numeric'),
TextInput::make('setup_amount')
->label('Setup qty')
->rules('numeric'),
TextInput::make('color_amount')
->label('Color qty')
->rules('numeric'),
TextInput::make('color_match')
->rules('numeric'),
TextInput::make('color_change')
->rules('numeric'),
TextInput::make('flash')
->rules(['numeric']),
TextInput::make('fleece')
->rules('numeric'),
TextInput::make('poly_ink')
->rules('numeric'),
TextInput::make('run_charge')
->rules('numeric'),
TextInput::make('artwork_fee')
->label('Artwork fee')
->rules('numeric'),
TextInput::make('repacking_fee')
->label('Repack. fee')
->rules('numeric'),
])
->addActionLabel('Add Screen Print Entry')
->defaultItems(0)
->reorderable()
->colStyles([
'logo' => 'width: 11%',
'placement' => 'width: 11%',
'quantity' => 'width: 5%',
'width' => 'width: 6%',
'height' => 'width: 6%',
'setup_amount' => 'width: 5%',
'color_amount' => 'width: 5%',
'color_match' => 'width: 6%',
'color_change' => 'width: 5%',
'flash' => 'width: 6%',
'fleece' => 'width: 6%',
'poly_ink' => 'width: 6%',
'run_charge' => 'width: 6%',
'artwork_fee' => 'width: 6%',
'repacking_fee' => 'width: 6%',
]),
TableRepeater::make('heatTransferEntries')
->relationship('heatTransferEntries')
->schema([
TextInput::make('logo')
->label('Logo name'),
TextInput::make('placement'),
TextInput::make('quantity')
->prefix('#')
->rules('numeric'),
TextInput::make('width')
->suffix('inch')
->rules('numeric'),
TextInput::make('height')
->rules('numeric')
->suffix('inch'),
TextInput::make('price')
->rules('numeric')
->prefix('$'),
])
->addActionLabel('Add Heat Transfer Entry')
->defaultItems(0)
->reorderable()
->colStyles([
'logo' => 'width: 25%',
'placement' => 'width: 25%',
'quantity' => 'width: 10%',
'width' => 'width: 11%',
'height' => 'width: 11%',
'price' => 'width: 15%',
]),
])->columns(1);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('id')
->color('primary')
->searchable(),
TextColumn::make('internal_id'),
TextColumn::make('date')
->date('Y-m-d')
->sortable()
->searchable(),
TextColumn::make('customer.company_name')
->sortable()
->searchable(),
TextColumn::make('notes')
->searchable()
->extraHeaderAttributes(['class' => 'w-full']),
TextColumn::make('total')
->money(),
])
->defaultSort('created_at', 'desc')
->groups([
'customer.company_name',
])
->filters([
])
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
]);
}
public static function canAccess(): bool
{
return auth()->user()->is_admin ?? false;
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => \App\Filament\Admin\Resources\QuoteResource\Pages\ListQuotes::route('/'),
'create' => \App\Filament\Admin\Resources\QuoteResource\Pages\CreateQuote::route('/create'),
'edit' => \App\Filament\Admin\Resources\QuoteResource\Pages\EditQuote::route('/{record}/edit'),
];
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\QuoteResource\Pages;
use App\Filament\Admin\Resources\QuoteResource;
use Filament\Resources\Pages\CreateRecord;
class CreateQuote extends CreateRecord
{
protected static string $resource = QuoteResource::class;
}

View File

@ -1,38 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\QuoteResource\Pages;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\QuoteResource;
use App\Models\Quote;
use Filament\Actions;
use Filament\Actions\Action;
use Filament\Resources\Pages\EditRecord;
class EditQuote extends EditRecord
{
protected static string $resource = QuoteResource::class;
public function getTitle(): string|\Illuminate\Contracts\Support\Htmlable
{
return parent::getTitle().' '.$this->record->getKey();
}
protected function getHeaderActions(): array
{
return [
Action::make('save')
->label('Save changes')
->action('save')
->icon(IconEnum::SAVE->value),
Action::make('print')
->icon(IconEnum::PRINT->value)
->url(fn (Quote $record) => route('pdf.quote', $record))
->openUrlInNewTab(),
Actions\DeleteAction::make()
->icon(IconEnum::TRASH->value),
];
}
}

View File

@ -1,21 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\QuoteResource\Pages;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\QuoteResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListQuotes extends ListRecords
{
protected static string $resource = QuoteResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->icon(IconEnum::NEW->value),
];
}
}

View File

@ -1,158 +0,0 @@
<?php
namespace App\Filament\Admin\Resources;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\ServiceTypeResource\Widgets\ServiceTypeOverview;
use App\Models\ServiceType;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Columns\Summarizers\Sum;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
class ServiceTypeResource extends Resource
{
protected static ?string $model = ServiceType::class;
protected static ?string $navigationIcon = IconEnum::DEFAULT->value;
protected static ?string $navigationGroup = 'Reports';
protected static ?string $label = 'Product Services';
protected static ?int $navigationSort = 2;
public static function getWidgets(): array
{
return [
ServiceTypeOverview::class,
];
}
public static function form(Form $form): Form
{
return $form
->schema([
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('name')
->label('Code'),
TextColumn::make('value')
->label('Long Name')
->extraHeaderAttributes(['class' => 'w-full']),
TextColumn::make('productServices.amount')
->label('Quantity')
->alignRight()
->getStateUsing(function (Table $table, Model $record) {
return $record->getQuantityAttribute(
$table->getFilter('created_at')->getState()['created_at'],
$table->getFilter('created_until')->getState()['created_until']
);
})
->summarize(Sum::make('amount')
->label('')
->using(fn (Table $table, $query) => $query
->when($createdAt = $table->getFilter('created_at')->getState()['created_at'] ?? null,
fn ($q) => $q->whereDate('created_at', '>=', $createdAt))
->when($createdUntil = $table->getFilter('created_until')->getState()['created_until'] ?? null,
fn ($q) => $q->whereDate('created_at', '<=', $createdUntil))
->sum('amount')
)
),
TextColumn::make('productServices.amount_price')
->label('Amount')
->alignRight()
->money()
->getStateUsing(function (Table $table, Model $record) {
return $record->getAmountAttribute(
$table->getFilter('created_at')->getState()['created_at'],
$table->getFilter('created_until')->getState()['created_until']
);
})
->summarize(Sum::make('amount_price')
->label('')
->money()
->using(fn (Table $table, $query) => $query
->when($createdAt = $table->getFilter('created_at')->getState()['created_at'] ?? null,
fn ($q) => $q->whereDate('created_at', '>=', $createdAt))
->when($createdUntil = $table->getFilter('created_until')->getState()['created_until'] ?? null,
fn ($q) => $q->whereDate('created_at', '<=', $createdUntil))
->sum('amount_price')
)
),
TextColumn::make('salesPercentage')
->alignRight()
->suffix('%')
->label('% sales')
->getStateUsing(function (Table $table, Model $record) {
return $record->getSalesPercentageAttribute(
$table->getFilter('created_at')->getState()['created_at'],
$table->getFilter('created_until')->getState()['created_until']
);
}),
TextColumn::make('averagePrice')
->getStateUsing(function (Table $table, Model $record) {
return $record->getAveragePriceAttribute(
$table->getFilter('created_at')->getState()['created_at'],
$table->getFilter('created_until')->getState()['created_until']
);
})
->alignRight()
->label('Average')
->prefix('$'),
])
->filters([
Tables\Filters\Filter::make('created_at')
->form([
DatePicker::make('created_at')
->label('From date'),
]),
Tables\Filters\Filter::make('created_until')
->form([
DatePicker::make('created_until')
->label('Until date'),
]),
], layout: Tables\Enums\FiltersLayout::AboveContent)
// ])
->actions([
])
->bulkActions([
]);
}
public static function canAccess(): bool
{
return auth()->user()->is_admin ?? false;
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => \App\Filament\Admin\Resources\ServiceTypeResource\Pages\ListServiceTypes::route('/'),
'create' => \App\Filament\Admin\Resources\ServiceTypeResource\Pages\CreateServiceType::route('/create'),
];
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\ServiceTypeResource\Pages;
use App\Filament\Admin\Resources\ServiceTypeResource;
use Filament\Resources\Pages\CreateRecord;
class CreateServiceType extends CreateRecord
{
protected static string $resource = ServiceTypeResource::class;
}

View File

@ -1,19 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\ServiceTypeResource\Pages;
use App\Filament\Admin\Resources\ServiceTypeResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditServiceType extends EditRecord
{
protected static string $resource = ServiceTypeResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}

View File

@ -1,25 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\ServiceTypeResource\Pages;
use App\Filament\Admin\Resources\ServiceTypeResource;
use Filament\Resources\Pages\ListRecords;
class ListServiceTypes extends ListRecords
{
protected static string $resource = ServiceTypeResource::class;
protected function getHeaderWidgets(): array
{
return [
// ServiceTypeResource\Widgets\ServiceTypeOverview::class,
];
}
protected function getHeaderActions(): array
{
return [
// Actions\CreateAction::make(),
];
}
}

View File

@ -1,35 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\ServiceTypeResource\Widgets;
use Filament\Widgets\ChartWidget;
class ServiceTypeOverview extends ChartWidget
{
protected static ?string $heading = 'Services';
protected static ?string $maxHeight = '200px';
protected function getData(): array
{
return [
'datasets' => [
[
'label' => 'Test Label',
'data' => [30, 15, 25, 30],
],
],
'labels' => [
'Test 1',
'Test 2',
'Test 3',
'Test 4',
],
];
}
protected function getType(): string
{
return 'pie';
}
}

View File

@ -1,155 +0,0 @@
<?php
namespace App\Filament\Admin\Resources;
use App\Enums\IconEnum;
use App\Enums\ShippingType;
use App\Models\ShippingEntry;
use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Split;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Support\Enums\IconPosition;
use Filament\Tables;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Grouping\Group;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
class ShippingEntryResource extends Resource
{
protected static ?string $model = ShippingEntry::class;
protected static ?string $navigationIcon = IconEnum::SHIPPING_ENTRY->value;
protected static ?string $navigationGroup = 'Management';
protected static ?int $navigationSort = 3;
public static function form(Form $form): Form
{
return $form
->schema([
Section::make([
Fieldset::make('Primary information')
->schema([
Select::make('customer')
->relationship('customer', 'company_name')
->searchable()
->required(),
ToggleButtons::make('shipping_type')
->options(ShippingType::class)
->inline()
->required(),
TextInput::make('courier')
->placeholder('UPS, Purolator...'),
]),
Split::make([
Fieldset::make('Account Details')
->schema([
TextInput::make('account_title')
->label('Title')
->prefixIcon('lucide-folder-pen')
->placeholder('What is this account used for?')
->columnSpan(2),
TextInput::make('account_url')
->label('URL')
->prefixIcon('lucide-globe')
->placeholder('Shipping website')
->url()
->columnSpan(2),
TextInput::make('account_username')
->label('Username')
->prefixIcon('lucide-circle-user')
->placeholder('...'),
TextInput::make('account_password')
->label('Password')
->prefixIcon('lucide-key-round')
->placeholder('...'),
])->columnSpan(1),
Fieldset::make('Shipping Instructions')
->schema([
TextInput::make('info_needed')
->label('Instructions')
->prefixIcon('lucide-pencil')
->placeholder('Example: put PO on box')
->columnSpan(2),
TextInput::make('notify')
->placeholder('Who to email and CC?')
->prefixIcon('lucide-users-round')
->columnSpan(2),
TextArea::make('notes')
->placeholder('Any additional information...')
->rows(2)
->columnSpan(2),
]),
])->columnSpan(2),
])->columns(2),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('shipping_type')
->label('Type')
->sortable(),
TextColumn::make('courier')
->url(fn ($record) => $record->account_url ?? null, shouldOpenInNewTab: true)
->icon(fn ($record) => $record->account_url ? 'lucide-external-link' : null)
->iconPosition(IconPosition::After)
->searchable(query: function (Builder $query, $search) {
return $query
->where('courier', 'like', "%{$search}%")
->orWhereHas('customer', function (Builder $query) use ($search) {
return $query->where('company_name', 'like', "%{$search}%");
});
}),
TextColumn::make('account_title'),
TextColumn::make('info_needed'),
TextColumn::make('notify'),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
// Tables\Actions\BulkActionGroup::make([
// Tables\Actions\DeleteBulkAction::make(),
// ]),
])
->defaultGroup(
Group::make('customer.company_name')
->titlePrefixedWithLabel(false)
);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => \App\Filament\Admin\Resources\ShippingEntryResource\Pages\ListShippingEntries::route('/'),
'create' => \App\Filament\Admin\Resources\ShippingEntryResource\Pages\CreateShippingEntry::route('/create'),
'edit' => \App\Filament\Admin\Resources\ShippingEntryResource\Pages\EditShippingEntry::route('/{record}/edit'),
];
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\ShippingEntryResource\Pages;
use App\Filament\Admin\Resources\ShippingEntryResource;
use Filament\Resources\Pages\CreateRecord;
class CreateShippingEntry extends CreateRecord
{
protected static string $resource = ShippingEntryResource::class;
}

View File

@ -1,19 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\ShippingEntryResource\Pages;
use App\Filament\Admin\Resources\ShippingEntryResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditShippingEntry extends EditRecord
{
protected static string $resource = ShippingEntryResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}

View File

@ -1,21 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\ShippingEntryResource\Pages;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\ShippingEntryResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListShippingEntries extends ListRecords
{
protected static string $resource = ShippingEntryResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->icon(IconEnum::NEW->value),
];
}
}

View File

@ -1,86 +0,0 @@
<?php
namespace App\Filament\Admin\Resources;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\TaxRateResource\Pages;
use App\Models\TaxRate;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Support\Enums\FontWeight;
use Filament\Tables;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class TaxRateResource extends Resource
{
protected static ?string $model = TaxRate::class;
protected static ?string $navigationIcon = IconEnum::TAX_RATE->value;
protected static ?string $navigationGroup = 'Settings';
protected static ?int $navigationSort = 11;
public static function form(Form $form): Form
{
return $form
->schema([
Section::make([
TextInput::make('name')
->disabledOn('edit'),
TextInput::make('value')
->label('Value in percentage')
->numeric()
->prefix('%'),
]),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('name')
->weight(FontWeight::Bold),
TextColumn::make('value')
->extraHeaderAttributes(['class' => 'w-full'])
->suffix(' %'),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make()
->modalWidth('xs'),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function canAccess(): bool
{
return auth()->user()->is_admin ?? false;
}
public static function getPages(): array
{
return [
'index' => Pages\ListTaxRates::route('/'),
// 'create' => Pages\CreateTaxRate::route('/create'),
// 'edit' => Pages\EditTaxRate::route('/{record}/edit'),
];
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\TaxRateResource\Pages;
use App\Filament\Admin\Resources\TaxRateResource;
use Filament\Resources\Pages\CreateRecord;
class CreateTaxRate extends CreateRecord
{
protected static string $resource = TaxRateResource::class;
}

View File

@ -1,19 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\TaxRateResource\Pages;
use App\Filament\Admin\Resources\TaxRateResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditTaxRate extends EditRecord
{
protected static string $resource = TaxRateResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}

View File

@ -1,23 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\TaxRateResource\Pages;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\TaxRateResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListTaxRates extends ListRecords
{
protected static string $resource = TaxRateResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->modalWidth('xs')
->icon(IconEnum::NEW->value)
->createAnother(false),
];
}
}

View File

@ -1,128 +0,0 @@
<?php
namespace App\Filament\Admin\Resources;
use App\Enums\IconEnum;
use App\Models\User;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class UserResource extends Resource
{
protected static ?int $navigationSort = 10;
protected static ?string $model = User::class;
protected static ?string $navigationIcon = IconEnum::USER->value;
protected static ?string $navigationGroup = 'Settings';
public static function form(Form $form): Form
{
return $form
->schema([
Section::make('Login details')
->description(fn (string $operation) => $operation == 'edit' ? 'To leave the password unchanged, leave both fields empty,' : false)
->schema([
TextInput::make('username')
->autocomplete('new-username')
->unique()
->required()
->columnSpanFull(),
TextInput::make('password')
->password()
->autocomplete('new-password')
->revealable()
->dehydrated(fn ($state) => ! empty($state))
->required(fn (string $operation): bool => $operation === 'create'),
TextInput::make('password_verify')
->label('Verify password')
->password()
->revealable()
->same('password')
->dehydrated(false)
->required(fn (string $operation) => $operation === 'create'),
]),
Section::make('Permissions')
->description('Administrators can access invoices and settings')
->schema([
Toggle::make('is_admin')
->columnSpanFull()
->label('User is an administrator')
->reactive()
->afterStateUpdated(fn ($state, callable $set) => $set('customer_id', null))
->disabled(fn (?User $record, $operation) => $operation !== 'create' && auth()->user()->id === $record->id),
])
->columns(2),
Section::make('Customer Login')
->description('If this account is for a customer, select them here')
->schema([
Select::make('customer_id')
->relationship('customer', 'company_name')
->disabled(fn ($get) => $get('is_admin'))
->afterStateUpdated(fn ($state, callable $set) => $set('is_admin', false))
->nullable()
->searchable()
->preload(),
]),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('username')
->extraHeaderAttributes(['class' => 'w-full']),
TextColumn::make('customer.company_name')
->sortable()
->placeholder('Internal'),
Tables\Columns\IconColumn::make('is_admin')
->label('Admin')
->boolean()
->alignRight(),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make()
->modalWidth('md')
->modal(),
])
->defaultSort('customer_id', 'asc');
}
public static function canAccess(): bool
{
return auth()->user()->is_admin ?? false;
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => \App\Filament\Admin\Resources\UserResource\Pages\ListUsers::route('/'),
];
}
private static function Grid() {}
}

View File

@ -1,11 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\UserResource\Pages;
use App\Filament\Admin\Resources\UserResource;
use Filament\Resources\Pages\CreateRecord;
class CreateUser extends CreateRecord
{
protected static string $resource = UserResource::class;
}

View File

@ -1,21 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\UserResource\Pages;
use App\Filament\Admin\Resources\UserResource;
use App\Models\User;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditUser extends EditRecord
{
protected static string $resource = UserResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make()
->disabled(fn (User $record) => $record->id == auth()->user()->id),
];
}
}

View File

@ -1,23 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\UserResource\Pages;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\UserResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListUsers extends ListRecords
{
protected static string $resource = UserResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->modalWidth('md')
->icon(IconEnum::NEW->value)
->modal(),
];
}
}

View File

@ -1,92 +0,0 @@
<?php
namespace App\Filament\Admin\Widgets;
use App\Enums\OrderStatus;
use App\Models\Order;
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;
class OrderStats extends BaseWidget
{
// protected int|string|array $columnSpan = '2';
protected function getStats(): array
{
return [
Stat::make('This Month', $this->getOrdersPast30Days())
->icon('heroicon-s-calendar')
->chartColor('success')
->chart($this->getOrdersInPast30DaysChart())
->description('New orders in the past 30 days'),
Stat::make('Active Orders', $this->getActiveOrders())
->icon('heroicon-o-arrow-path')
->description('Orders that have yet to be completed'),
Stat::make('Due Today', $this->getDueOrders())
->icon('heroicon-o-clock')
->chartColor('info')
->chart($this->getDueOrdersChart())
->description('Orders that are scheduled to be due today'),
];
}
private function getActiveOrders(): string
{
return Order::all()
->where('order_status', '!=', OrderStatus::DRAFT)
->where('order_status', '!=', OrderStatus::SHIPPED)
->where('order_status', '!=', OrderStatus::READY_FOR_INVOICE)
->where('order_status', '!=', OrderStatus::INVOICED)
->count();
}
private function getOrdersPast30Days(): string
{
return Order::all()
->where('order_status', '!=', OrderStatus::DRAFT)
->where('order_status', '!=', OrderStatus::READY_FOR_INVOICE)
->where('order_status', '!=', OrderStatus::SHIPPED)
->where('order_status', '!=', OrderStatus::INVOICED)
->whereBetween('created_at', [now()->startOfMonth(), now()->endOfMonth()])
->count();
}
private function getOrdersInPast30DaysChart(): array
{
$chart = [];
$points = 30;
$startDate = today()->subDays(31);
for ($i = 0; $i < $points; $i++) {
$chart[$i] = Order::where('order_date', $startDate->addDay())->count();
}
return $chart;
}
private function getDueOrders(): string
{
return Order::all()
->where('order_status', '!=', OrderStatus::DRAFT)
->where('order_status', '!=', OrderStatus::READY_FOR_INVOICE)
->where('order_status', '!=', OrderStatus::SHIPPED)
->where('order_status', '!=', OrderStatus::INVOICED)
->where('due_date', '<=', now())
->count();
}
private function getDueOrdersChart(): array
{
$chart = [];
$points = 30;
$startDate = today()->subDays(31);
for ($i = 0; $i < $points; $i++) {
$chart[$i] = Order::where('due_date', $startDate->addDay())->count();
}
return $chart;
}
}

View File

@ -1,87 +0,0 @@
<?php
namespace App\Filament\Customer\Pages;
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
use Filament\Facades\Filament;
use Filament\Forms\Components\Component;
use Filament\Forms\Components\TextInput;
use Filament\Http\Responses\Auth\Contracts\LoginResponse;
use Filament\Models\Contracts\FilamentUser;
use Filament\Pages\Auth\Login;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Validation\ValidationException;
class CustomerLogin extends Login
{
protected function getEmailFormComponent(): Component
{
return TextInput::make('username')
->label('Username')
->required()
->autofocus()
->extraInputAttributes(['tabindex' => 1])
->autocomplete();
}
protected function getCredentialsFromFormData(array $data): array
{
return [
'username' => $data['username'],
'password' => $data['password'],
];
}
protected function throwFailureValidationException(): never
{
throw ValidationException::withMessages([
'data.username' => __('filament-panels::pages/auth/login.messages.failed'),
]);
}
public function authenticate(): ?LoginResponse
{
try {
$this->rateLimit(5);
} catch (TooManyRequestsException $exception) {
$this->getRateLimitedNotification($exception)?->send();
return null;
}
$data = $this->form->getState();
if (! Filament::auth()->attempt($this->getCredentialsFromFormData($data), $data['remember'] ?? false)) {
$this->throwFailureValidationException();
}
$user = Filament::auth()->user();
if (($user instanceof FilamentUser) && (! $user->canAccessPanel(Filament::getCurrentPanel()))) {
Filament::auth()->logout();
$this->throwFailureValidationException();
} elseif ($user->customer_id === null) {
Filament::auth()->logout();
throw ValidationException::withMessages([
'data.username' => 'Incorrect username or password.',
]);
}
session()->regenerate();
return app(LoginResponse::class);
}
public function getTitle(): Htmlable|string
{
return __('Login');
}
public function getHeading(): Htmlable|string
{
return __('Login');
}
}

View File

@ -1,58 +0,0 @@
<?php
namespace App\Filament\Customer\Resources;
use App\Enums\IconEnum;
use App\Filament\Customer\Resources\OrderResource\Pages;
use App\Models\Order;
use Filament\Infolists\Components\TextEntry;
use Filament\Infolists\Infolist;
use Filament\Resources\Resource;
use Filament\Tables\Actions\ViewAction;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
class OrderResource extends Resource
{
protected static ?string $model = Order::class;
protected static ?string $navigationIcon = IconEnum::ORDER->value;
public static function infolist(Infolist $infolist): Infolist
{
return $infolist
->schema([
TextEntry::make('internal_po'),
TextEntry::make('customer_po'),
TextEntry::make('order_date'),
TextEntry::make('due_date'),
TextEntry::make('status'),
]);
}
public static function table(Table $table): Table
{
return \App\Filament\Admin\Resources\OrderResource::table($table)
->modifyQueryUsing(function (Builder $query) {
return $query->where('customer_id', auth()->user()->customer_id);
})
->actions([
ViewAction::make(),
])
->bulKActions([]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListOrders::route('/'),
];
}
}

View File

@ -1,17 +0,0 @@
<?php
namespace App\Filament\Customer\Resources\OrderResource\Pages;
use App\Filament\Customer\Resources\OrderResource;
use Filament\Resources\Pages\ListRecords;
class ListOrders extends ListRecords
{
protected static string $resource = OrderResource::class;
protected function getHeaderActions(): array
{
return [
];
}
}

View File

@ -1,46 +0,0 @@
<?php
namespace App\Filament\Guest\Pages;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section;
use Filament\Pages\Page;
use Illuminate\Support\HtmlString;
class ContactUs extends Page
{
protected static ?string $navigationIcon = 'lucide-contact';
protected static ?int $navigationSort = 2;
protected static string $view = 'filament.guest.pages.contact-us';
protected function getFormSchema(): array
{
return [
Section::make()
->schema([
Placeholder::make('digitizingContent')
->hiddenLabel()
->content(new HtmlString('<p class="w-3/4">
<b>Address</b>
<br>
<ul class="list-none list-inside">
<li>108 - 618 East Kent Ave. South,</li>
<li> Vancouver BC, V5X 0B1</li>
</ul>
<br>
<b>Contact Numbers</b>
<br>
<ul class="list-none list-inside">
<li>Tel: (604)871-9991</li>
<li>Fax: (604)871-9980</li>
<li>E-mail: info@sewtopnotch.com</li>
</ul>
</p>')),
])
->columns(2)
->columnSpan(1),
];
}
}

View File

@ -1,43 +0,0 @@
<?php
namespace App\Filament\Guest\Pages;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section;
use Filament\Pages\Page;
use Illuminate\Support\HtmlString;
class Digitizing extends Page
{
protected static ?string $navigationIcon = 'heroicon-o-document-text';
protected static ?int $navigationSort = -1;
protected static string $view = 'filament.guest.pages.digitizing';
protected function getFormSchema(): array
{
return [
Section::make()
->schema([
Placeholder::make('digitizingContent')
->hiddenLabel()
->content(new HtmlString('<p class="w-3/4">
<b>Overview</b>
<br>
<ul class="list-disc">
<li>Digitizing is the process of converting an artwork/picture into instructions an embroidery machine can understand.</li>
<br>
<li>The quality of digitizing plays a very important role in the final embroidery quality.</li>
<br>
<li>We have our own digitizing expert working in the shop, so digitizing files can be adjusted according to fabric, placement, and colors.</li>
<br>
<li>We always digitize by ourselves.</li>
</ul>
</p>')),
])
->columns(2)
->columnSpan(1),
];
}
}

View File

@ -1,51 +0,0 @@
<?php
namespace App\Filament\Guest\Pages;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section;
use Filament\Pages\Page;
use Illuminate\Support\HtmlString;
class Embroidery extends Page
{
protected static ?string $navigationIcon = 'heroicon-o-document-text';
protected static ?int $navigationSort = 0;
protected static string $view = 'filament.guest.pages.embroidery';
protected function getFormSchema(): array
{
return [
Section::make()
->schema([
Placeholder::make('embContent')
->hiddenLabel()
->content(new HtmlString('<p class="w-3/4">
<b>Overview</b>
<br>
<ul class="list-disc list-inside">
<li>We can do regular embroidery, appliqué, patches, puff (3D). They can apply on apparels, hats, bags, towels, blankets, etc. </li>
<br>
<li> Comparing with screen printing, embroidery means high end. To make sure it is really high end, we strictly apply 3 steps: </li>
</ul>
<br>
<ul class="list-inside list-decimal">
<li>1. Good digitizing</li>
<li>2. Best quality materials (such as threads and backings)</li>
<li>3. Detailed QC (Quality Control) procedures</li>
</ul>
<br>
<ul class="list-disc list-inside">
<li>As for the material, there are every kind of threads, backings, and other materials that embroidery needs to use. We always use the best quality.</li>
<br>
<li>QC is the last step before our embroidery products leave the shop. We consider QC as important as digitizing and embroidery. The time we spent on QC is twice as others.</li>
</ul>
</p>')),
])
->columns(2)
->columnSpan(1),
];
}
}

View File

@ -1,42 +0,0 @@
<?php
namespace App\Filament\Guest\Pages;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section;
use Filament\Pages\Page;
use Illuminate\Support\HtmlString;
class Home extends Page
{
protected static ?string $navigationIcon = 'lucide-house';
protected static ?int $navigationSort = -2;
protected static string $view = 'filament.guest.pages.home';
protected function getFormSchema(): array
{
return [
Section::make()
->schema([
Placeholder::make('homeContent')
->hiddenLabel()
->content(new HtmlString('<p class="w-3/4">
<b>Welcome to Top-Notch Embroidery and Digitizing Ltd.</b>
<br>
<ul class="list-disc list-inside">
<li>We specialize in digitizing, embroidery, vinyl and screen printing.</li>
<br>
<li>Our digitizing is done by our experienced digitizer right in our shop.</li>
<br>
<li>We can embellish on jackets, shirts, pants, sweaters, hoodies, sports jerseys,
team wears, bags, towels, hats, blankets, wedding gawns, gloves, head bands, wrist bands, etc.</li>
</ul>
</p>')),
])
->columns(2)
->columnSpan(1),
];
}
}

View File

@ -1,38 +0,0 @@
<?php
namespace App\Filament\Guest\Pages;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section;
use Filament\Pages\Page;
use Illuminate\Support\HtmlString;
class Vinyl extends Page
{
protected static ?string $navigationIcon = 'heroicon-o-document-text';
protected static ?int $navigationSort = 1;
protected static string $view = 'filament.guest.pages.vinyl';
protected function getFormSchema(): array
{
return [
Section::make()
->schema([
Placeholder::make('digitizingContent')
->hiddenLabel()
->content(new HtmlString('<p class="w-3/4">
<b>Overview</b>
<br>
<ul class="list-disc">
<li>Vinyl is widely used in jerseys and other apparels. </li>
<li>We mostly use 3M material.</li>
</ul>
</p>')),
])
->columns(2)
->columnSpan(1),
];
}
}

View File

@ -5,15 +5,13 @@
use App\Http\Requests\ContactRequest;
use App\Models\Contact;
use App\Models\Customer;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class ContactController extends Controller
{
public function index(): void {}
public function index() {}
public function create(Request $request): View
public function create(Request $request)
{
return view('contacts.create', [
'customers' => Customer::all(),
@ -21,20 +19,20 @@ public function create(Request $request): View
]);
}
public function store(ContactRequest $request): RedirectResponse
public function store(ContactRequest $request)
{
$contact = Contact::create($request->validated());
return redirect()->route('customers.show', [$contact->customer, 'contacts'])->with('status', 'Contact created successfully');
}
public function show(int $id): void {}
public function show($id) {}
public function edit(int $id): void {}
public function edit($id) {}
public function update(Request $request, int $id): void {}
public function update(Request $request, $id) {}
public function requestDestroy(Request $request): RedirectResponse
public function requestDestroy(Request $request)
{
$contact = Contact::find($request->get('contact'));
$contact->delete();
@ -42,5 +40,5 @@ public function requestDestroy(Request $request): RedirectResponse
return redirect()->route('customers.show', [$contact->customer->id, 'contacts'])->with('status', 'Contact deleted successfully');
}
public function destroy(int $id): void {}
public function destroy($id) {}
}

View File

@ -5,16 +5,12 @@
use App\Http\Requests\CustomerRequest;
use App\Models\Customer;
use App\Models\PackingSlip;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View;
use Illuminate\Foundation\Application;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
class CustomerController extends Controller
{
public function index(): void {}
public function index() {}
public function store(CustomerRequest $request)
{
@ -23,12 +19,12 @@ public function store(CustomerRequest $request)
return redirect()->route('management.index')->with('status', 'Customer created successfully.');
}
public function create(): Factory|View|Application|\Illuminate\View\View
public function create()
{
return view('customers.create');
}
public function show(Customer $customer, ?string $tab = null): RedirectResponse|View
public function show(Customer $customer, ?string $tab = null)
{
if (! $tab) {
return redirect()->route('customers.show', [$customer, 'tab' => 'details']);
@ -44,14 +40,14 @@ public function show(Customer $customer, ?string $tab = null): RedirectResponse|
]);
}
public function update(CustomerRequest $request, Customer $customer): RedirectResponse
public function update(CustomerRequest $request, Customer $customer)
{
$customer->update($request->validated());
return redirect()->route('customers.show', $customer)->with('status', 'Customer updated successfully.');
}
public function requestDestroy(Request $request): RedirectResponse
public function requestDestroy(Request $request)
{
$customer = Customer::find($request->id);
$customer->delete();
@ -59,15 +55,10 @@ public function requestDestroy(Request $request): RedirectResponse
return redirect()->route('management.index')->with('status', 'Customer deleted successfully.');
}
public function destroy(Customer $customer): RedirectResponse
public function destroy(Customer $customer)
{
$customer->delete();
return redirect()->route('management.index')->with('status', 'Customer deleted successfully.');
}
public function pdf(Customer $customer, ?bool $paid = false, ?string $created_from = null, ?string $created_until = null): RedirectResponse
{
dd($customer, $paid, $created_from, $created_until);
}
}

View File

@ -26,9 +26,9 @@ public function index(Request $request)
return redirect()->route('dashboard', ['tab' => 'active_orders']);
}
// return view('dashboard', [
// 'today' => Carbon::today(),
// 'tab' => $request->get('tab'),
// ]);
return view('dashboard', [
'today' => Carbon::today(),
'tab' => $request->get('tab'),
]);
}
}

View File

@ -1,26 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Invoice;
use Spatie\Browsershot\Browsershot;
use Spatie\LaravelPdf\Facades\Pdf;
class InvoiceController extends Controller
{
public function pdf(int $id)
{
$invoice = Invoice::find($id);
$url = strtolower('invoice-'.$invoice->internal_id.'.pdf');
Pdf::view('pdf.invoice', ['invoice' => $invoice])
->withBrowsershot(function (Browsershot $browsershot) {
$browsershot->noSandbox();
})
->margins(8, 8, 15, 8)
->footerView('pdf.invoice-footer', ['invoice' => $invoice])
->save($url);
return redirect($url);
}
}

View File

@ -3,7 +3,6 @@
namespace App\Http\Controllers;
use App\Models\Customer;
use App\Models\ServiceFile;
class ManagementController extends Controller
{
@ -16,9 +15,8 @@ public function index(?string $tab = null)
}
return view('management.index', [
'customers' => Customer::all(),
'serviceFiles' => ServiceFile::paginate(15),
'tab' => $tab,
'customers' => Customer::all(),
'tab' => $tab,
]);
}
}

View File

@ -11,13 +11,8 @@
use App\Models\ProductService;
use App\Models\ProductSize;
use App\Models\ServiceFile;
use Illuminate\Contracts\View\Factory;
use Illuminate\Foundation\Application;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\View\View;
use Spatie\Browsershot\Browsershot;
use Spatie\LaravelPdf\Facades\Pdf;
class OrderController extends Controller
{
@ -85,29 +80,30 @@ public function store(OrderRequest $request)
// Create productServices
for ($i = 0; $i < count($request->get('serviceInputCount')) - 1; $i++) {
$serviceFile = ServiceFile::create([
'code' => $request->get('service_file_name')[$i],
'name' => $request->get('logo_name')[$i],
'width' => $request->get('service_width')[$i],
'height' => $request->get('service_height')[$i],
'setup_number' => $request->get('setup_amount')[$i],
]);
ProductService::create([
'order_id' => $order->id,
'service_file_id' => $serviceFile->id,
'service_type' => $request->get('service_type')[$i],
'placement' => $request->get('placement')[$i],
'amount' => $request->get('amount')[$i],
'amount_price' => $request->get('amount_price')[$i],
'notes' => $request->get('service_notes')[$i],
$productService = ProductService::create([
'order_id' => $order->id,
'service_type' => $request->get('service_type')[$i],
'placement' => $request->get('placement')[$i],
'setup_amount' => $request->get('setup_amount')[$i],
'amount' => $request->get('amount')[$i],
'amount_price' => $request->get('amount_price')[$i],
]);
ServiceFile::create([
'product_service_id' => $productService,
'code' => $request->get('service_file_name')[$i],
'name' => $request->get('logo_name')[$i],
'width' => $request->get('service_width')[$i],
'height' => $request->get('service_height')[$i],
'unit' => $request->get('service_setup_unit')[$i],
'setup_number' => $request->get('setup_number')[$i],
]);
}
return redirect()->route('orders.show', $order);
return redirect()->route('order-products.create', ['order' => $order->id]);
}
public function show(int $id): Factory|\Illuminate\Contracts\View\View|Application|View
public function show($id)
{
return view('orders.show', [
'order' => Order::find($id),
@ -115,25 +111,9 @@ public function show(int $id): Factory|\Illuminate\Contracts\View\View|Applicati
]);
}
public function edit(int $id) {}
public function edit($id) {}
public function update(Request $request, $id) {}
public function destroy(int $id): void {}
public function pdf(int $id)
{
$order = Order::find($id);
$url = strtolower('order-'.$order->internal_po.'.pdf');
Pdf::view('pdf.order', ['order' => $order])
->withBrowsershot(function (Browsershot $browsershot) {
$browsershot->noSandbox();
})
->margins(8, 8, 15, 8)
->footerView('pdf.order-footer', ['order' => $order])
->save($url);
return redirect($url);
}
public function destroy($id) {}
}

View File

@ -2,27 +2,24 @@
namespace App\Http\Controllers;
use Illuminate\Contracts\View\Factory;
use Illuminate\Foundation\Application;
use Illuminate\Http\Request;
use Illuminate\View\View;
class OrderProductController extends Controller
{
public function index(): void {}
public function index() {}
public function create(): Factory|\Illuminate\Contracts\View\View|Application|View
public function create()
{
return view('order-products.create');
}
public function store(Request $request): void {}
public function store(Request $request) {}
public function show($id): void {}
public function show($id) {}
public function edit($id): void {}
public function edit($id) {}
public function update(Request $request, $id): void {}
public function update(Request $request, $id) {}
public function destroy($id): void {}
public function destroy($id) {}
}

View File

@ -5,16 +5,15 @@
use App\Http\Requests\PackingSlipRequest;
use App\Models\Customer;
use App\Models\PackingSlip;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class PackingSlipController extends Controller
{
public function index(): void {}
public function index() {}
public function create(): void {}
public function create() {}
public function store(PackingSlipRequest $request): RedirectResponse
public function store(PackingSlipRequest $request)
{
PackingSlip::create($request->validated());
@ -25,14 +24,14 @@ public function store(PackingSlipRequest $request): RedirectResponse
]);
}
return redirect()->back(); // todo: change to packing slips page
return redirect()->back(); //todo: change to packing slips page
}
public function show($id): void {}
public function show($id) {}
public function edit($id): void {}
public function edit($id) {}
public function update(Request $request, $id): void {}
public function update(Request $request, $id) {}
public function destroy($id): void {}
public function destroy($id) {}
}

View File

@ -1,45 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\InvoiceReport;
use App\Models\Quote;
use Spatie\Browsershot\Browsershot;
use Spatie\LaravelPdf\Facades\Pdf;
class PdfController extends Controller
{
public function invoiceReport(int $id)
{
$invoiceReport = InvoiceReport::find($id);
$url = strtolower('invoicereport-'.$invoiceReport->internal_id.'.pdf');
Pdf::view('pdf.invoice-report', ['invoiceReport' => $invoiceReport])
->withBrowsershot(function (Browsershot $browsershot) {
$browsershot->noSandbox();
})
->margins(8, 8, 15, 8)
->footerView('pdf.invoice-report-footer', ['invoiceReport' => $invoiceReport])
->save($url);
return redirect($url);
}
public function quote(int $id)
{
$quote = Quote::find($id);
$company_name = $quote->customer->company_name ?? '';
$url = strtolower('TN-quote-'.$quote->id.'.pdf');
Pdf::view('pdf.quote', ['quote' => $quote])
->withBrowsershot(function (Browsershot $browsershot) {
$browsershot->noSandbox();
})
->margins(8, 8, 15, 8)
->footerView('pdf.quote-footer', ['quote' => $quote])
->save($url);
return redirect($url);
}
}

View File

@ -1,8 +0,0 @@
<?php
namespace App\Http\Controllers;
class QuoteController extends Controller
{
//
}

Some files were not shown because too many files have changed in this diff Show More