Compare commits
103 Commits
Author | SHA1 | Date | |
---|---|---|---|
43eebd9528 | |||
ea4f46f96d | |||
3defbd8253 | |||
77b9c558af | |||
6814821100 | |||
47669851ff | |||
0385f614d6 | |||
ef5acd3d06 | |||
8a34981742 | |||
|
f3e7819908 | ||
44ec2068c3 | |||
195722c570 | |||
e6fc32c369 | |||
aa340852e7 | |||
|
1192ca681b | ||
d025570d8d | |||
a75296f997 | |||
2a831578a9 | |||
6433bb8485 | |||
435224ff7c | |||
a4e4c30118 | |||
8f8b3a9943 | |||
076dcccbb8 | |||
899377d594 | |||
629b3276b5 | |||
ec6ae88888 | |||
dddbbb8f9b | |||
779d46d708 | |||
6fc753fc19 | |||
4955780f67 | |||
cab2b2c478 | |||
e45041566a | |||
16e17de6fb | |||
4fbb62353a | |||
6b5a758dbe | |||
686cda21bf | |||
aa60cf18ee | |||
2b377f72aa | |||
5bec1fc3d8 | |||
20dd032b40 | |||
0d1501362c | |||
09cc6b249e | |||
eac70f0f53 | |||
f0f10a4907 | |||
66168d0919 | |||
0b8fd00f67 | |||
d3a9f183ca | |||
f08bff9684 | |||
9ae273fda0 | |||
b2c8ce7405 | |||
f937302159 | |||
b320915fc1 | |||
efe78bb49f | |||
9efde6fa34 | |||
0d68062055 | |||
306afd630b | |||
056462f511 | |||
f122f2925a | |||
90ef3c9c29 | |||
e4899de42a | |||
0b359b7255 | |||
0528506dfa | |||
9e8eb05e88 | |||
548e56335f | |||
74a5a43c85 | |||
487ea48c14 | |||
2c0fbfde5b | |||
fcb1cef6fd | |||
f945ad2f71 | |||
b47dd597e1 | |||
cf4a56ee84 | |||
2aaf7ab8a2 | |||
73df66d0eb | |||
eceaf3e676 | |||
bdbc65cedb | |||
f051f20ad9 | |||
1ffd38fd53 | |||
1f1f783aa9 | |||
7740160b4f | |||
fcb1eda56f | |||
fdbc2653d4 | |||
162c8839e2 | |||
2de11bcaba | |||
1b891f8350 | |||
7e2a22e016 | |||
562e499d12 | |||
256cc1f7ed | |||
a12e0b29d3 | |||
d950955371 | |||
3c98de929e | |||
913a4477a7 | |||
b3868e8b0a | |||
e79d1839fe | |||
2361ec0b88 | |||
b9346c4466 | |||
3e2c5d5fac | |||
c0053a8969 | |||
a5c40ea161 | |||
542e1346f4 | |||
08f0a99551 | |||
8e80ba9480 | |||
7074596cb7 | |||
61831b1940 |
5
.directory
Normal file
5
.directory
Normal file
@ -0,0 +1,5 @@
|
||||
[Dolphin]
|
||||
Timestamp=2024,11,17,16,40,18.95
|
||||
Version=4
|
||||
ViewMode=1
|
||||
VisibleRoles=Icons_text,Icons_size
|
25
.dockerignore
Normal file
25
.dockerignore
Normal file
@ -0,0 +1,25 @@
|
||||
# .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
|
69
.env.testing
Normal file
69
.env.testing
Normal file
@ -0,0 +1,69 @@
|
||||
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
|
27
.gitea/workflows/deploy.yaml
Normal file
27
.gitea/workflows/deploy.yaml
Normal file
@ -0,0 +1,27 @@
|
||||
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
6
.gitignore
vendored
@ -103,3 +103,9 @@ fabric.properties
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
.idea
|
||||
.directory
|
||||
.directory
|
||||
.directory
|
||||
public
|
||||
_ide_helper.php
|
||||
public/build
|
||||
|
9
.puppeteer.cjs
Normal file
9
.puppeteer.cjs
Normal file
@ -0,0 +1,9 @@
|
||||
const {join} = require('path');
|
||||
|
||||
/**
|
||||
* @type {import("puppeteer").Configuration}
|
||||
*/
|
||||
module.exports = {
|
||||
// Changes the cache location for Puppeteer.
|
||||
cacheDirectory: join(__dirname, '.cache', 'puppeteer'),
|
||||
};
|
32
.scripts/deploy.sh
Normal file
32
.scripts/deploy.sh
Normal file
@ -0,0 +1,32 @@
|
||||
#!/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!"
|
72
README.md
72
README.md
@ -1,66 +1,8 @@
|
||||
<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>
|
||||
# Changelog
|
||||
|
||||
<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).
|
||||
**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
|
||||
|
62
app/Enums/IconEnum.php
Normal file
62
app/Enums/IconEnum.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?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';
|
||||
}
|
45
app/Enums/InvoiceStatus.php
Normal file
45
app/Enums/InvoiceStatus.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?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,
|
||||
};
|
||||
}
|
||||
}
|
35
app/Enums/OrderAttributes.php
Normal file
35
app/Enums/OrderAttributes.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?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,
|
||||
};
|
||||
}
|
||||
}
|
@ -2,10 +2,52 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum OrderStatus: string
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum OrderStatus: string implements HasColor, HasIcon, HasLabel
|
||||
{
|
||||
case APPROVED = 'Approved';
|
||||
case PRODUCTION = 'Production';
|
||||
case SHIPPED = 'Shipped';
|
||||
case INVOICED = 'Invoiced';
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,24 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum OrderType: string
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum OrderType: string implements HasLabel
|
||||
{
|
||||
case EMBROIDERY = 'Embroidery';
|
||||
case SCREEN = 'Screen printing';
|
||||
case DTG = 'Direct-to-garment';
|
||||
case VINYL = 'Vinyl';
|
||||
case MISC = 'Misc';
|
||||
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',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,28 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ShippingType: string
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum ShippingType: string implements HasIcon, HasLabel
|
||||
{
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
33
app/Events/InvoiceCreated.php
Normal file
33
app/Events/InvoiceCreated.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?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'),
|
||||
];
|
||||
}
|
||||
}
|
87
app/Filament/Admin/Pages/UsernameLogin.php
Normal file
87
app/Filament/Admin/Pages/UsernameLogin.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?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;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?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(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?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),
|
||||
];
|
||||
}
|
||||
}
|
146
app/Filament/Admin/Resources/CustomerReportResource.php
Normal file
146
app/Filament/Admin/Resources/CustomerReportResource.php
Normal file
@ -0,0 +1,146 @@
|
||||
<?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('/'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?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;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?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(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<?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(),
|
||||
];
|
||||
}
|
||||
}
|
118
app/Filament/Admin/Resources/CustomerResource.php
Normal file
118
app/Filament/Admin/Resources/CustomerResource.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?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'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?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;
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?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(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<?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])),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
<?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(),
|
||||
// ]),
|
||||
]);
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
<?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(),
|
||||
]);
|
||||
}
|
||||
}
|
130
app/Filament/Admin/Resources/InvoiceReportResource.php
Normal file
130
app/Filament/Admin/Resources/InvoiceReportResource.php
Normal file
@ -0,0 +1,130 @@
|
||||
<?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}'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\InvoiceReportResource\Pages;
|
||||
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateInvoiceReport extends CreateRecord {}
|
@ -0,0 +1,20 @@
|
||||
<?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(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
<?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])),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
<?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(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
<?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([
|
||||
]);
|
||||
}
|
||||
}
|
330
app/Filament/Admin/Resources/InvoiceResource.php
Normal file
330
app/Filament/Admin/Resources/InvoiceResource.php
Normal file
@ -0,0 +1,330 @@
|
||||
<?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'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?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;
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
<?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'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
<?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])),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
471
app/Filament/Admin/Resources/OrderResource.php
Normal file
471
app/Filament/Admin/Resources/OrderResource.php
Normal file
@ -0,0 +1,471 @@
|
||||
<?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'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
241
app/Filament/Admin/Resources/OrderResource/Pages/EditOrder.php
Normal file
241
app/Filament/Admin/Resources/OrderResource/Pages/EditOrder.php
Normal file
@ -0,0 +1,241 @@
|
||||
<?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),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
<?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()),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?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;
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
<?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),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
119
app/Filament/Admin/Resources/PackingSlipResource.php
Normal file
119
app/Filament/Admin/Resources/PackingSlipResource.php
Normal file
@ -0,0 +1,119 @@
|
||||
<?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'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?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;
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?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),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?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),
|
||||
];
|
||||
}
|
||||
}
|
149
app/Filament/Admin/Resources/PaymentResource.php
Normal file
149
app/Filament/Admin/Resources/PaymentResource.php
Normal file
@ -0,0 +1,149 @@
|
||||
<?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'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?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;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?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(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?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])),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?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;
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
<?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])),
|
||||
]);
|
||||
}
|
||||
}
|
272
app/Filament/Admin/Resources/QuoteResource.php
Normal file
272
app/Filament/Admin/Resources/QuoteResource.php
Normal file
@ -0,0 +1,272 @@
|
||||
<?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'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?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;
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
<?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),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?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),
|
||||
];
|
||||
}
|
||||
}
|
158
app/Filament/Admin/Resources/ServiceTypeResource.php
Normal file
158
app/Filament/Admin/Resources/ServiceTypeResource.php
Normal file
@ -0,0 +1,158 @@
|
||||
<?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'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?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;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?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(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<?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(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
<?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';
|
||||
}
|
||||
}
|
155
app/Filament/Admin/Resources/ShippingEntryResource.php
Normal file
155
app/Filament/Admin/Resources/ShippingEntryResource.php
Normal file
@ -0,0 +1,155 @@
|
||||
<?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'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?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;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?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(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?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),
|
||||
];
|
||||
}
|
||||
}
|
86
app/Filament/Admin/Resources/TaxRateResource.php
Normal file
86
app/Filament/Admin/Resources/TaxRateResource.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?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'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?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;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?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(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
<?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),
|
||||
];
|
||||
}
|
||||
}
|
128
app/Filament/Admin/Resources/UserResource.php
Normal file
128
app/Filament/Admin/Resources/UserResource.php
Normal file
@ -0,0 +1,128 @@
|
||||
<?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() {}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?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;
|
||||
}
|
21
app/Filament/Admin/Resources/UserResource/Pages/EditUser.php
Normal file
21
app/Filament/Admin/Resources/UserResource/Pages/EditUser.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?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),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
<?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(),
|
||||
];
|
||||
}
|
||||
}
|
92
app/Filament/Admin/Widgets/OrderStats.php
Normal file
92
app/Filament/Admin/Widgets/OrderStats.php
Normal file
@ -0,0 +1,92 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
87
app/Filament/Customer/Pages/CustomerLogin.php
Normal file
87
app/Filament/Customer/Pages/CustomerLogin.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
58
app/Filament/Customer/Resources/OrderResource.php
Normal file
58
app/Filament/Customer/Resources/OrderResource.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?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('/'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
<?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 [
|
||||
];
|
||||
}
|
||||
}
|
46
app/Filament/Guest/Pages/ContactUs.php
Normal file
46
app/Filament/Guest/Pages/ContactUs.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?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),
|
||||
];
|
||||
}
|
||||
}
|
43
app/Filament/Guest/Pages/Digitizing.php
Normal file
43
app/Filament/Guest/Pages/Digitizing.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?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),
|
||||
];
|
||||
}
|
||||
}
|
51
app/Filament/Guest/Pages/Embroidery.php
Normal file
51
app/Filament/Guest/Pages/Embroidery.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?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),
|
||||
];
|
||||
}
|
||||
}
|
42
app/Filament/Guest/Pages/Home.php
Normal file
42
app/Filament/Guest/Pages/Home.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?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),
|
||||
];
|
||||
}
|
||||
}
|
38
app/Filament/Guest/Pages/Vinyl.php
Normal file
38
app/Filament/Guest/Pages/Vinyl.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?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),
|
||||
];
|
||||
}
|
||||
}
|
@ -5,13 +5,15 @@
|
||||
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() {}
|
||||
public function index(): void {}
|
||||
|
||||
public function create(Request $request)
|
||||
public function create(Request $request): View
|
||||
{
|
||||
return view('contacts.create', [
|
||||
'customers' => Customer::all(),
|
||||
@ -19,20 +21,20 @@ public function create(Request $request)
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(ContactRequest $request)
|
||||
public function store(ContactRequest $request): RedirectResponse
|
||||
{
|
||||
$contact = Contact::create($request->validated());
|
||||
|
||||
return redirect()->route('customers.show', [$contact->customer, 'contacts'])->with('status', 'Contact created successfully');
|
||||
}
|
||||
|
||||
public function show($id) {}
|
||||
public function show(int $id): void {}
|
||||
|
||||
public function edit($id) {}
|
||||
public function edit(int $id): void {}
|
||||
|
||||
public function update(Request $request, $id) {}
|
||||
public function update(Request $request, int $id): void {}
|
||||
|
||||
public function requestDestroy(Request $request)
|
||||
public function requestDestroy(Request $request): RedirectResponse
|
||||
{
|
||||
$contact = Contact::find($request->get('contact'));
|
||||
$contact->delete();
|
||||
@ -40,5 +42,5 @@ public function requestDestroy(Request $request)
|
||||
return redirect()->route('customers.show', [$contact->customer->id, 'contacts'])->with('status', 'Contact deleted successfully');
|
||||
}
|
||||
|
||||
public function destroy($id) {}
|
||||
public function destroy(int $id): void {}
|
||||
}
|
||||
|
@ -5,12 +5,16 @@
|
||||
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() {}
|
||||
public function index(): void {}
|
||||
|
||||
public function store(CustomerRequest $request)
|
||||
{
|
||||
@ -19,12 +23,12 @@ public function store(CustomerRequest $request)
|
||||
return redirect()->route('management.index')->with('status', 'Customer created successfully.');
|
||||
}
|
||||
|
||||
public function create()
|
||||
public function create(): Factory|View|Application|\Illuminate\View\View
|
||||
{
|
||||
return view('customers.create');
|
||||
}
|
||||
|
||||
public function show(Customer $customer, ?string $tab = null)
|
||||
public function show(Customer $customer, ?string $tab = null): RedirectResponse|View
|
||||
{
|
||||
if (! $tab) {
|
||||
return redirect()->route('customers.show', [$customer, 'tab' => 'details']);
|
||||
@ -40,14 +44,14 @@ public function show(Customer $customer, ?string $tab = null)
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(CustomerRequest $request, Customer $customer)
|
||||
public function update(CustomerRequest $request, Customer $customer): RedirectResponse
|
||||
{
|
||||
$customer->update($request->validated());
|
||||
|
||||
return redirect()->route('customers.show', $customer)->with('status', 'Customer updated successfully.');
|
||||
}
|
||||
|
||||
public function requestDestroy(Request $request)
|
||||
public function requestDestroy(Request $request): RedirectResponse
|
||||
{
|
||||
$customer = Customer::find($request->id);
|
||||
$customer->delete();
|
||||
@ -55,10 +59,15 @@ public function requestDestroy(Request $request)
|
||||
return redirect()->route('management.index')->with('status', 'Customer deleted successfully.');
|
||||
}
|
||||
|
||||
public function destroy(Customer $customer)
|
||||
public function destroy(Customer $customer): RedirectResponse
|
||||
{
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
@ -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'),
|
||||
// ]);
|
||||
}
|
||||
}
|
||||
|
26
app/Http/Controllers/InvoiceController.php
Normal file
26
app/Http/Controllers/InvoiceController.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Customer;
|
||||
use App\Models\ServiceFile;
|
||||
|
||||
class ManagementController extends Controller
|
||||
{
|
||||
@ -15,8 +16,9 @@ public function index(?string $tab = null)
|
||||
}
|
||||
|
||||
return view('management.index', [
|
||||
'customers' => Customer::all(),
|
||||
'tab' => $tab,
|
||||
'customers' => Customer::all(),
|
||||
'serviceFiles' => ServiceFile::paginate(15),
|
||||
'tab' => $tab,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -11,8 +11,13 @@
|
||||
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
|
||||
{
|
||||
@ -80,30 +85,29 @@ public function store(OrderRequest $request)
|
||||
|
||||
// Create productServices
|
||||
for ($i = 0; $i < count($request->get('serviceInputCount')) - 1; $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 = 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],
|
||||
]);
|
||||
|
||||
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('order-products.create', ['order' => $order->id]);
|
||||
return redirect()->route('orders.show', $order);
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
public function show(int $id): Factory|\Illuminate\Contracts\View\View|Application|View
|
||||
{
|
||||
return view('orders.show', [
|
||||
'order' => Order::find($id),
|
||||
@ -111,9 +115,25 @@ public function show($id)
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit($id) {}
|
||||
public function edit(int $id) {}
|
||||
|
||||
public function update(Request $request, $id) {}
|
||||
|
||||
public function destroy($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);
|
||||
}
|
||||
}
|
||||
|
@ -2,24 +2,27 @@
|
||||
|
||||
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() {}
|
||||
public function index(): void {}
|
||||
|
||||
public function create()
|
||||
public function create(): Factory|\Illuminate\Contracts\View\View|Application|View
|
||||
{
|
||||
return view('order-products.create');
|
||||
}
|
||||
|
||||
public function store(Request $request) {}
|
||||
public function store(Request $request): void {}
|
||||
|
||||
public function show($id) {}
|
||||
public function show($id): void {}
|
||||
|
||||
public function edit($id) {}
|
||||
public function edit($id): void {}
|
||||
|
||||
public function update(Request $request, $id) {}
|
||||
public function update(Request $request, $id): void {}
|
||||
|
||||
public function destroy($id) {}
|
||||
public function destroy($id): void {}
|
||||
}
|
||||
|
@ -5,15 +5,16 @@
|
||||
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() {}
|
||||
public function index(): void {}
|
||||
|
||||
public function create() {}
|
||||
public function create(): void {}
|
||||
|
||||
public function store(PackingSlipRequest $request)
|
||||
public function store(PackingSlipRequest $request): RedirectResponse
|
||||
{
|
||||
PackingSlip::create($request->validated());
|
||||
|
||||
@ -24,14 +25,14 @@ public function store(PackingSlipRequest $request)
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->back(); //todo: change to packing slips page
|
||||
return redirect()->back(); // todo: change to packing slips page
|
||||
}
|
||||
|
||||
public function show($id) {}
|
||||
public function show($id): void {}
|
||||
|
||||
public function edit($id) {}
|
||||
public function edit($id): void {}
|
||||
|
||||
public function update(Request $request, $id) {}
|
||||
public function update(Request $request, $id): void {}
|
||||
|
||||
public function destroy($id) {}
|
||||
public function destroy($id): void {}
|
||||
}
|
||||
|
45
app/Http/Controllers/PdfController.php
Normal file
45
app/Http/Controllers/PdfController.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
8
app/Http/Controllers/QuoteController.php
Normal file
8
app/Http/Controllers/QuoteController.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?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
Loading…
x
Reference in New Issue
Block a user