Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature(Admin): Shop/Store #34

Merged
merged 80 commits into from
Jan 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
85bf5b4
✨ feat(Filament): Add shop settings page and reorganize navigation.
MarJose123 Dec 16, 2024
bb90cda
✨ feat(Filament/Redeem): Add redeem resource to filament.
MarJose123 Dec 16, 2024
011952e
✨ feat(`app/Filament/Resources/ProductsResource.php`): Add status sel…
MarJose123 Dec 16, 2024
062bdac
🚀 feat(Filament/Pages/ShopGroup.php): Redirect ShopGroup to ShopResou…
MarJose123 Dec 16, 2024
8227786
Merge branch 'master' into admin-shop
MarJose123 Dec 28, 2024
01a5fc4
Merge branch 'master' into admin-shop
MarJose123 Jan 8, 2025
b769067
✨ feat(ShopResource): Implement view action and display product detai…
MarJose123 Jan 8, 2025
a1eb5f0
✨ feat(app/Http/Controllers/Api/ProductController.php): Update cloudi…
MarJose123 Jan 8, 2025
c37e571
✨ feat(Filament/ProductsResource): Add image preview to the edit form…
MarJose123 Jan 8, 2025
25118a9
✨ feat(ProductsResource): Integrate Cloudinary file uploads for produ…
MarJose123 Jan 9, 2025
7b746d7
✨ feat(ProductsResource): Improve product creation and edition forms.
MarJose123 Jan 9, 2025
c8cbf81
✨ feat(Products): Add cloudinary integration for product images. Thi…
MarJose123 Jan 9, 2025
79dac1b
✨ feat(ProductController): Add cloudinary image upload and delete fun…
MarJose123 Jan 9, 2025
0c43513
✨ feat(ProductsResource): Add cloudinary integration for product images.
MarJose123 Jan 9, 2025
6ad50d2
♻️ refactor(Products): Remove soft deletes from products.
MarJose123 Jan 9, 2025
3818b80
✨ feat(ProductsResource): Add maxSize validation to product image upl…
MarJose123 Jan 9, 2025
931cce3
✨ feat(app/Http/Requests/ProductRequest.php): Increase maximum image …
MarJose123 Jan 9, 2025
b5d3063
✨ feat(ShopResource): Implement image upload and enhance table display.
MarJose123 Jan 9, 2025
4834ef7
🐛 fix(ProductController): Handle null cloudinary IDs when updating/de…
MarJose123 Jan 9, 2025
e53edc8
✨ feat(`Filament/Resources/ProductsResource.php`): Format price colum…
MarJose123 Jan 10, 2025
664085f
✨ feat(RedeemController.php): Implement payment processing using user…
MarJose123 Jan 10, 2025
2d1036b
✨ feat(ShopController): Order shops by creation date in descending or…
MarJose123 Jan 10, 2025
89ca174
🔥 refactor(app/Http/Resources/ProductsResource.php): Remove creator a…
MarJose123 Jan 10, 2025
a4304e8
✨ feat(RedeemStatusEnum): Add icons to redeem statuses.
MarJose123 Jan 10, 2025
cd020b6
✨ feat(RedeemResource): Implement status-based tabs for redeem manage…
MarJose123 Jan 10, 2025
972b452
✨ feat(`config/app.php`): Set default timezone to Asia/Manila.
MarJose123 Jan 10, 2025
436a955
✨ feat(Filament/RedeemResource): Improve redeem management page with …
MarJose123 Jan 10, 2025
f8fe854
✨ feat(Filament/RedeemResource): Implement Redeem resource table with…
MarJose123 Jan 10, 2025
548236f
✨ feat(RedeemResource): Add badges to Redeem management tabs displayi…
MarJose123 Jan 10, 2025
e412f90
✨ feat(database): Add `decline_reason` and `decline_date` columns to …
MarJose123 Jan 10, 2025
cf9a9be
✨ feat(database/migrations): Add `decline_reason` and `decline_date` …
MarJose123 Jan 10, 2025
ef1a65a
✨ feat(Redeem): Add `decline_reason` and `decline_date` fields.
MarJose123 Jan 10, 2025
5d575df
✨ feat(migrations): Add `decline_reason_category` to the `redeems` ta…
MarJose123 Jan 10, 2025
54a271f
✨ feat(Redeem): Add `decline_reason_category` to the Redeem model.
MarJose123 Jan 10, 2025
83da2bb
✨ feat(RedeemDeclineReasonEnum.php): Add RedeemDeclineReasonEnum.
MarJose123 Jan 10, 2025
080ffae
✨ feat(database/migrations): Rename `decline_date` column to `decline…
MarJose123 Jan 10, 2025
535ada6
♻️ refactor(`app/Models/Redeem.php`): Rename `decline_date` column to…
MarJose123 Jan 10, 2025
60589b3
✨ feat(config/mail.php): Update mail configuration to use SMTP.
MarJose123 Jan 10, 2025
7529395
✨ feat(`config/mail.php`): Update mail configuration to remove local …
MarJose123 Jan 10, 2025
db5ca18
✨ feat(`Mail`, `emails`): Add redeem declined email notification.
MarJose123 Jan 10, 2025
1772074
✨ feat(RedeemResource): Implement redeem approval, processing, and de…
MarJose123 Jan 10, 2025
baff919
✨ feat(`Filament/Actions/RedeemDeclined.php`): Add action to decline …
MarJose123 Jan 10, 2025
26110b1
✨ feat(Filament/Actions/RedeemDeclined.php): Refund the user when a r…
MarJose123 Jan 10, 2025
2a23919
✅ fix(RedeemController): Correctly refund user and update redeem status.
MarJose123 Jan 10, 2025
083b441
✨ feat(Products): Use ProductStatusEnum for product status values.
MarJose123 Jan 10, 2025
4c7e3b8
✅ feat(RedeemResource.php): Implement stock decrement on redeem appro…
MarJose123 Jan 10, 2025
7f5b690
🌱 feat(database/seeders): Adjust seeder counts for Redeem, Product, a…
MarJose123 Jan 10, 2025
93be220
✨ feat(database/factories/ProductsFactory.php): Implement status enum…
MarJose123 Jan 10, 2025
baa9633
✨ feat(app/Models/Scopes/ProductAvailableScope.php): Use ProductStatu…
MarJose123 Jan 10, 2025
bb53ec3
✨ feat(Filament/Resources/ProductsResource.php): Remove product statu…
MarJose123 Jan 10, 2025
151609b
✨ feat(`Filament/Resources/ProductsResource.php`): Allow viewing unav…
MarJose123 Jan 10, 2025
c2d59ad
🔥 refactor(`app/Filament/Resources/ProductsResource.php`): Remove bul…
MarJose123 Jan 10, 2025
4dc0d3c
✨ feat(`Models/Products.php`): Add relationship between Products and …
MarJose123 Jan 10, 2025
3046614
✨ feat(app/Providers/AppServiceProvider.php): Align Filament notifica…
MarJose123 Jan 10, 2025
a1185c8
✅ test(`phpstan.neon.dist`): Add ignores for Eloquent Models and reso…
MarJose123 Jan 10, 2025
3bf6e8e
✨ feat(ProductsResource): Implement product deletion with Cloudinary …
MarJose123 Jan 10, 2025
a7d3bc8
🛡️ fix(ShopResource): Prevent deletion of shops with associated redeems.
MarJose123 Jan 10, 2025
35cd096
✨ feat(Filament/Resources/ShopResource.php): Refine delete action to …
MarJose123 Jan 10, 2025
9bb9582
✅ test(`.github/workflows/phpstan.yml`): Fix PHPStan workflow to corr…
MarJose123 Jan 10, 2025
5d9e13f
♻️ refactor(ProductsResource): Inject CloudinaryEngine dependency ins…
MarJose123 Jan 10, 2025
1842c3f
♻️ refactor(ProductController): Inject CloudinaryEngine dependency in…
MarJose123 Jan 10, 2025
fba4a9f
✅ fix(ProductsResource): Fix product deletion to include Cloudinary r…
MarJose123 Jan 10, 2025
54c7ba9
chore(code-style): Fix Code style
MarJose123 Jan 10, 2025
a392cff
✅ test(`ProductControllerTest.php`): Fix product status value in tests.
MarJose123 Jan 10, 2025
c2e0cd4
✅ test(`ProductControllerTest.php`): Fix product status update test.
MarJose123 Jan 10, 2025
e0a2471
Merge remote-tracking branch 'origin/admin-shop' into admin-shop
MarJose123 Jan 10, 2025
4b27a3e
composer update
MarJose123 Jan 10, 2025
f697cad
✅ test(`ProductControllerTest.php`): Refactor product status update a…
MarJose123 Jan 10, 2025
1ea1a5e
✅ test(`ProductControllerTest.php`): Add test for deleting a product.
MarJose123 Jan 10, 2025
43f282c
✅ test(`RedeemControllerTest.php`): Refactor redeem tests to improve …
MarJose123 Jan 10, 2025
37d4328
♻️ refactor(`app/Http/Controllers/Api/ProductController.php`): Simpli…
MarJose123 Jan 10, 2025
523cca6
✅ test(`ProductControllerTest.php`): Add tests to ensure unused produ…
MarJose123 Jan 10, 2025
7195159
✅ test(`run-tests.yml`): Update CI workflow to trigger on PHP-related…
MarJose123 Jan 11, 2025
07cf47f
⬆️ refactor(`composer.json`): Upgrade Laravel framework and PestPHP t…
MarJose123 Jan 11, 2025
b5dc03f
🔥 refactor(tests): Remove `CreatesApplication.php` test helper.
MarJose123 Jan 11, 2025
0fc2fc9
✅ test(`TestCase.php`): Add database seeding before each test execution.
MarJose123 Jan 11, 2025
8ca42c9
🔒 fix(tests): refactor all test cases to fix issue on random failing …
MarJose123 Jan 11, 2025
eb883bf
✨ feat(Filament/Resources/UserResource.php): Add labels to User resou…
MarJose123 Jan 11, 2025
b57f2b1
✨ feat(Filament/DepartmentsResource): Add department head relationshi…
MarJose123 Jan 11, 2025
7375929
✅ test(`ShopControllerTest.php`): Add test to check adding a product …
MarJose123 Jan 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/phpstan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ jobs:
uses: ramsey/composer-install@v3

- name: Run PHPStan
run: ./vendor/bin/phpstan --error-format=github
run: ./vendor/bin/phpstan analyse --error-format=github
15 changes: 7 additions & 8 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ name: run-tests

on:
push:
branches: [master]
pull_request:
branches: [master]
paths:
- '**.php'
- '.github/workflows/run-tests.yml'
- 'phpunit.xml.dist'
- 'composer.json'
- 'composer.lock'

jobs:
test:
Expand All @@ -17,10 +20,6 @@ jobs:
php: [8.3]
laravel: [11.*]
stability: [prefer-lowest, prefer-stable]
include:
- laravel: 11.*
testbench: 9.*
carbon: ^2.63

name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}

Expand All @@ -32,7 +31,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
coverage: none

- name: Setup problem matchers
Expand Down
44 changes: 44 additions & 0 deletions app/Filament/Actions/RedeemDeclined.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

/*
* Copyright (c) 2025.
*
* Filename: RedeemDecline.php
* Project Name: ninshiki-backend
* Project Repository: https://github.com/ninshiki-project/Ninshiki-backend
* License: MIT
* GitHub: https://github.com/MarJose123
* Written By: Marjose123
*/

namespace App\Filament\Actions;

use App\Http\Controllers\Api\Enum\RedeemStatusEnum;
use App\Mail\RedeemDeclinedMail;
use App\Models\Redeem;
use Bavix\Wallet\Internal\Exceptions\ExceptionInterface;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Mail;

class RedeemDeclined
{
/**
* @throws ExceptionInterface
*/
public function handle(Redeem $record, array $data): void
{
// update the record status and other column
$record->status = RedeemStatusEnum::DECLINED;
$record->decline_reason_category = $data['category'];
$record->decline_reason = $data['description'];
$record->declined_at = Carbon::now();
$record->save();
$record->refresh();
// refund the user
$userWallet = $record->user->getWallet('ninshiki-wallet');
$userWallet->refund($record->product);
// send notification to the user who redeem the item
Mail::to($record->user->email)
->send(new RedeemDeclinedMail($record));
}
}
2 changes: 1 addition & 1 deletion app/Filament/Pages/SettingsGroup.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ class SettingsGroup extends Page

protected static string $view = 'filament.pages.settings-group';

protected static ?int $navigationSort = 4;
protected static ?int $navigationSort = 5;
}
25 changes: 25 additions & 0 deletions app/Filament/Pages/ShopGroup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace App\Filament\Pages;

use App\Filament\Resources\ShopResource;
use Filament\Pages\Page;

class ShopGroup extends Page
{
/**
* This page will be used only to group other settings pages/resources
*/
protected static ?string $title = 'Store';

protected static ?string $navigationIcon = 'heroicon-o-building-storefront';

protected static string $view = 'filament.pages.settings-group';

protected static ?int $navigationSort = 4;

public function mount(): void
{
$this->redirect(ShopResource::getUrl('index'));
}
}
16 changes: 12 additions & 4 deletions app/Filament/Resources/DepartmentsResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Support\Enums\Alignment;
use Filament\Support\Enums\MaxWidth;
use Filament\Tables;
use Filament\Tables\Table;

Expand All @@ -24,12 +26,14 @@ public static function form(Form $form): Form
->columns(1)
->schema([
Forms\Components\TextInput::make('name')
->unique(ignoreRecord: true)
->required(),
Forms\Components\Select::make('department_head')
Forms\Components\Select::make('head')
->label('Department Head')
->native(false)
->searchable()
->preload()
->relationship('departmentHead', 'name'),
->relationship('head', 'name'),
]);
}

Expand All @@ -38,8 +42,10 @@ public static function table(Table $table): Table
return $table
->columns([
Tables\Columns\TextColumn::make('name')
->label('Department Name')
->searchable(),
Tables\Columns\TextColumn::make('department_head')
Tables\Columns\TextColumn::make('head.name')
->label('Department Head')
->searchable(),
Tables\Columns\TextColumn::make('deleted_at')
->dateTime()
Expand All @@ -58,7 +64,9 @@ public static function table(Table $table): Table
//
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\EditAction::make()
->modalWidth(MaxWidth::Medium)
->modalFooterActionsAlignment(Alignment::Right),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Expand Down
190 changes: 190 additions & 0 deletions app/Filament/Resources/ProductsResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
<?php

namespace App\Filament\Resources;

use App\Filament\Resources\ProductsResource\Pages;
use App\Models\Products;
use App\Models\Scopes\ProductAvailableScope;
use CloudinaryLabs\CloudinaryLaravel\CloudinaryEngine;
use Filament\Forms;
use Filament\Forms\Components\BaseFileUpload;
use Filament\Forms\Form;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
use Filament\Support\Enums\Alignment;
use Filament\Support\Enums\MaxWidth;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use League\Flysystem\UnableToCheckFileExistence;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;

class ProductsResource extends Resource
{
protected static ?string $model = Products::class;

protected static ?string $navigationParentItem = 'Store';

protected static ?int $navigationSort = 2;

public static ?string $cloudinaryPublicId = null;

public static ?string $oldCloudinaryPublicId = null;

public static function form(Form $form): Form
{
return $form
->columns(2)
->schema([
Forms\Components\FileUpload::make('image')
->image()
->disk('cloudinary')
->directory('products')
->visibility('private')
->maxSize(10240) // 10MB
->afterStateHydrated(static function (BaseFileUpload $component, string|array|null $state) {
if (blank($state)) {
$component->state([]);

return;
}
$component->state([((string) Str::uuid()) => $state]);
})
->afterStateUpdated(static function (BaseFileUpload $component, $state) {
$component->state([(string) Str::uuid() => $state]);
})
->getUploadedFileUsing(static function (BaseFileUpload $component, string $file): array {
return [
'name' => basename($file),
'size' => 0,
'type' => null,
'url' => $file,
];
})
->saveUploadedFileUsing((static function (BaseFileUpload $component, TemporaryUploadedFile $file, Forms\Set $set): ?string {
try {
if (! $file->exists()) {
return null;
}
} catch (UnableToCheckFileExistence $exception) {
return null;
}

$uploadedFile = $file->storeOnCloudinaryAs($component->getDirectory(), $component->getUploadedFileNameForStorage($file));
self::$cloudinaryPublicId = $uploadedFile->getPublicId();

return $uploadedFile->getSecurePath();
}))
->reactive()
->columnSpanFull()
->required(),
Forms\Components\TextInput::make('name')
->required(),
Forms\Components\TextInput::make('price')
->required()
->numeric(),
Forms\Components\TextInput::make('stock')
->required()
->numeric()
->default(1),
Forms\Components\Textarea::make('description')
->columnSpanFull(),
]);
}

public static function table(Table $table): Table
{
return $table
->modifyQueryUsing(fn (Builder $query): Builder => $query->withoutGlobalScope(new ProductAvailableScope)->orderBy('created_at', 'desc'))
->columns([
Tables\Columns\TextColumn::make('id')
->label('ID')
->toggleable(isToggledHiddenByDefault: true)
->searchable(),
Tables\Columns\ImageColumn::make('image'),
Tables\Columns\TextColumn::make('name')
->searchable(),
Tables\Columns\TextColumn::make('price')
->money()
->formatStateUsing(fn ($state) => number_format($state, 2))
->sortable(),
Tables\Columns\TextColumn::make('stock')
->numeric()
->sortable(),
Tables\Columns\TextColumn::make('status')
->searchable(),
Tables\Columns\TextColumn::make('deleted_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('updated_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make()
->mutateFormDataUsing(function (array $data): array {
$data['cloudinary_id'] = self::$cloudinaryPublicId;

return $data;
})
->before(function (Products $record) {
self::$oldCloudinaryPublicId = $record->cloudinary_id;
})
->after(function (CloudinaryEngine $cloudinary) {
// delete cloudinary id
$cloudinary->destroy(self::$oldCloudinaryPublicId);
})
->modalAlignment(Alignment::Center)
->modalWidth(MaxWidth::FitContent)
->modalFooterActionsAlignment(Alignment::Right),
Tables\Actions\DeleteAction::make()
->action(function (Products $record, Tables\Actions\DeleteAction $action, CloudinaryEngine $cloudinary) {
// prevent deleting if the record is being used in other model
if ($record->shop()->exists() || $record->redeems()->exists()) {
Notification::make('stop')
->title('Unable to Delete')
->body('Product has existing record in Shop or Redeem')
->warning()
->send();

return;
}
if ($record->cloudinary_id) {
$cloudinary->destroy($record->cloudinary_id);
$record->delete();
$action->success();
}
}),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make()
->action(fn (Collection $records) => $records->each(function (Model $record, CloudinaryEngine $cloudinary) {
if ($record->cloudinary_id && (! $record->shop()->exists() || ! $record->redeems()->exists())) {
$cloudinary->destroy($record->cloudinary_id);
$record->delete();
}
})),
]),
]);
}

public static function getPages(): array
{
return [
'index' => Pages\ManageProducts::route('/'),
];
}
}
31 changes: 31 additions & 0 deletions app/Filament/Resources/ProductsResource/Pages/ManageProducts.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace App\Filament\Resources\ProductsResource\Pages;

use App\Filament\Resources\ProductsResource;
use Filament\Actions;
use Filament\Resources\Pages\ManageRecords;
use Filament\Support\Enums\Alignment;
use Filament\Support\Enums\MaxWidth;

class ManageProducts extends ManageRecords
{
protected static string $resource = ProductsResource::class;

protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->mutateFormDataUsing(function (array $data): array {
$resource = static::getResource();
$data['cloudinary_id'] = $resource::$cloudinaryPublicId;

return $data;
})
->modalAlignment(Alignment::Center)
->modalWidth(MaxWidth::FitContent)
->modalFooterActionsAlignment(Alignment::Right)
->createAnother(false),
];
}
}
Loading
Loading