mirror of
https://github.com/nihonbuzz/nihonbuzz-academy.git
synced 2026-01-26 05:25:37 +07:00
feat: update seeder with real YouTube video URLs and update technical blueprint
This commit is contained in:
193
README.md
193
README.md
@@ -1,66 +1,169 @@
|
||||
<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>
|
||||
# NihonBuzz Ecosystem Blueprint 🇯🇵
|
||||
|
||||
<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>
|
||||
Dokumen ini merupakan cetak biru (blueprint) teknis mutakhir untuk seluruh ekosistem **NihonBuzz**, mencakup arsitektur sistem, infrastruktur, dan model data yang telah dikonsolidasikan.
|
||||
|
||||
## 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:
|
||||
## 1. High-Level Architecture
|
||||
|
||||
- [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).
|
||||
Eksosistem NihonBuzz terbagi menjadi dua entitas utama yang saling terintegrasi namun berjalan di infrastruktur yang dioptimalkan untuk kebutuhan masing-masing.
|
||||
|
||||
Laravel is accessible, powerful, and provides tools required for large, robust applications.
|
||||
```mermaid
|
||||
graph TD
|
||||
User((User/Student))
|
||||
|
||||
subgraph "Public Interface (nihonbuzz-org)"
|
||||
NextJS["Next.js 16 (React)"]
|
||||
Tailwind["Tailwind CSS"]
|
||||
CF_Pages["Cloudflare Pages + OpenNext"]
|
||||
end
|
||||
|
||||
subgraph "Learning Engine (nihonbuzz-academy)"
|
||||
Laravel["Laravel 12 (PHP 8.3+)"]
|
||||
Inertia["Inertia.js (React Protocol)"]
|
||||
Shadcn["Shadcn UI (Design System)"]
|
||||
Filament["Filament v5 (Jimusho Admin)"]
|
||||
end
|
||||
|
||||
subgraph "Shared Infrastructure"
|
||||
MySQL[("MySQL (UUID Based)")]
|
||||
R2[("Cloudflare R2 (Media Storage)")]
|
||||
OAuth["Google OAuth 2.0"]
|
||||
end
|
||||
|
||||
User --> NextJS
|
||||
User --> Laravel
|
||||
NextJS -.-> OAuth
|
||||
Laravel -.-> OAuth
|
||||
Laravel --> MySQL
|
||||
Laravel --> R2
|
||||
```
|
||||
|
||||
## 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.
|
||||
## 2. Technical Stack Detail
|
||||
|
||||
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
|
||||
### A. nihonbuzz-org (Marketing & Landing)
|
||||
- **Framework**: Next.js 16.1.4.
|
||||
- **Styling**: Tailwind CSS dengan standar Modern Japanese Aesthetics.
|
||||
- **Infrastructure**: Cloudflare Pages menggunakan adapter **OpenNext** untuk mendukung fitur Server-Side Rendering (SSR) dan API Routes di lingkungan Serverless/Workers.
|
||||
|
||||
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.
|
||||
### B. nihonbuzz-academy (Core LMS)
|
||||
- **Backend Core**: Laravel 12.
|
||||
- **Frontend Bridge**: Inertia.js (menghilangkan kebutuhan API terpisah untuk web, menjaga state tetap sinkron).
|
||||
- **UI Architecture**: Shadcn UI yang distandarisasi untuk mencapai kualitas premium dan konsistensi antara Dashboard Siswa dan Panel Manajemen.
|
||||
- **Admin Identity**: Jalur kustom `/jimusho` menggunakan Filament v5 dengan plugin tema Shadcn.
|
||||
|
||||
## 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).
|
||||
## 3. Security & Access Model (RBAC)
|
||||
|
||||
### Premium Partners
|
||||
Sistem keamanan menggunakan UUID global untuk seluruh entitas dan manajemen peran berbasis **Spatie Permission**.
|
||||
|
||||
- **[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)**
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph "Management Level (Jimusho Accessibility)"
|
||||
SA[Super Admin] --> A[Admin]
|
||||
A --> B[Bendahara]
|
||||
A --> E[Editor]
|
||||
A --> S[Sensei]
|
||||
end
|
||||
|
||||
subgraph "User Level"
|
||||
ST[Student] --> KK[Ketua Kelas]
|
||||
ST --> AL[Alumni]
|
||||
end
|
||||
|
||||
OAuth((OAuth Integration)) --> ST
|
||||
SA -- "Full Access" --> Laravel
|
||||
```
|
||||
|
||||
## 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).
|
||||
## 4. Database Schema Blueprint
|
||||
|
||||
## Code of Conduct
|
||||
Arsitektur database dirancang untuk mendukung "Data Atomik" guna kebutuhan pembelajaran bahasa yang dinamis.
|
||||
|
||||
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).
|
||||
```mermaid
|
||||
erDiagram
|
||||
Users ||--o{ Enrollments : has
|
||||
Users ||--o{ SRSReviews : tracks
|
||||
Courses ||--o{ Modules : contains
|
||||
Modules ||--o{ Lessons : contains
|
||||
Levels ||--o{ Courses : categorizes
|
||||
Levels ||--o{ Vocabularies : categorizes
|
||||
Vocabularies ||--o{ SRSReviews : "review_data"
|
||||
|
||||
Users {
|
||||
uuid id PK
|
||||
string name
|
||||
string email
|
||||
timestamp last_activity_at
|
||||
}
|
||||
|
||||
Vocabularies {
|
||||
uuid id PK
|
||||
string word "Kanji/Kana"
|
||||
string reading "Kana only"
|
||||
string meaning_id "Indonesian"
|
||||
string meaning_en "English"
|
||||
string audio_url "Stored in R2"
|
||||
json stroke_order "SVG Logic"
|
||||
}
|
||||
|
||||
## Security Vulnerabilities
|
||||
SRSReviews {
|
||||
uuid id PK
|
||||
timestamp next_review_at
|
||||
int interval
|
||||
float ease_factor
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
## 5. Learning Core Engine
|
||||
|
||||
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
|
||||
### A. Japanese Support System
|
||||
- **Furigana Engine**: Implementasi tag HTML `<ruby>` di seluruh komponen React untuk manajemen cara baca Kanji otomatis.
|
||||
- **Multi-lingual Context**: Setiap data kosa kata wajib memiliki konteks makna dalam Bahasa Indonesia dan Inggris.
|
||||
- **Media Delivery**: Seluruh aset audio (pronunciation) dan video materi dialirkan (streaming) melalui Cloudflare R2 untuk latensi rendah.
|
||||
|
||||
### B. SRS (Spaced Repetition System)
|
||||
- Menggunakan algoritma pengulangan berjarak (SM-2 based) untuk menghitung `next_review_at` secara individual bagi setiap siswa pada setiap unit kosa kata.
|
||||
- Dashboard siswa menampilkan statistik "Ready to Review" yang diambil secara real-time dari mesin SRS.
|
||||
|
||||
---
|
||||
|
||||
## 6. Advanced Technical Systems
|
||||
|
||||
### A. Gamification Mechanics (Engagement Engine)
|
||||
Sistem dirancang untuk mempertahankan retensi pengguna menggunakan tiga pilar utama:
|
||||
- **XP Ecosystem**: Setiap penyelesaian pelajaran, kuis, atau sesi SRS memberikan unit XP yang meningkatkan level profil siswa.
|
||||
- **Streak System**: Logika harian yang melacak aktivitas beruntun. Absensi aktivitas selama 24 jam akan mereset streak (disertai fitur *Streak Freeze* via shop/metadata).
|
||||
- **Milestone Badges**: Pencapaian otomatis (misal: "N5 Master", "100 Days Warrior") yang disimpan dalam tabel metadata user.
|
||||
|
||||
### B. Media & Asset Pipeline
|
||||
Pengelolaan aset media premium dilakukan secara otomatis menggunakan **Spatie MediaLibrary**:
|
||||
- **Storage**: Integrasi native dengan **Cloudflare R2** via driver S3.
|
||||
- **Processing**: Konversi otomatis format audio ke `.mp3` atau `.ogg` untuk kompatibilitas browser lintas perangkat.
|
||||
- **Security**: Folder `private/pdfs` di R2 digunakan khusus untuk materi berbayar (Paid Content) yang hanya bisa diakses via *Signed URLs* dengan validitas waktu terbatas.
|
||||
|
||||
### C. Monetization & Enrollment Flow
|
||||
Sistem mendukung pembelian kursus secara individual atau berbasis langganan:
|
||||
- **Price Engine**: Mendukung mata uang IDR dengan format desimal yang dioptimalkan untuk gateway pembayaran lokal.
|
||||
- **Access Control**: Relasi `enrollments` menentukan masa aktif akses siswa ke `modules` dan `lessons`. Kursus gratis (Free Preview) dapat diakses tanpa entri di tabel enrollment.
|
||||
|
||||
---
|
||||
|
||||
## 7. Future-Proof & Cross-Platform Strategy
|
||||
|
||||
Arsitektur dikembangkan dengan prinsip **API-Ready** untuk mendukung ekspansi ke aplikasi mobile asli (Bukan Capacitor):
|
||||
|
||||
- **Headless Compatibility**: Meskipun saat ini menggunakan Inertia.js untuk web, seluruh Controller didesain agar mudah di-porting menjadi REST API atau GraphQL untuk konsumsi **React Native**.
|
||||
- **Shared Logic (The Brain)**: Algoritma SRS dan logika validasi kosa kata dipisahkan ke dalam *Service Classes* di Laravel agar bisa melayani permintaan dari Web (Inertia) maupun Mobile App (API) dengan kode sumber yang sama.
|
||||
- **Universal Aesthetics**: Desain sistem Shadcn di Web menjadi referensi utama (tokens) untuk implementasi **NativeWind** di aplikasi mobile guna menjaga visual branding yang identik.
|
||||
|
||||
---
|
||||
|
||||
> **Blueprint Status**: Phase 2 Finalized (Comprehensive).
|
||||
> **Objective**: Membangun ekosistem belajar Bahasa Jepang yang tak tertandingi secara teknis dan estetika.
|
||||
|
||||
@@ -21,7 +21,7 @@ class CourseForm
|
||||
->required(),
|
||||
TextInput::make('slug')
|
||||
->required(),
|
||||
Textarea::make('description')
|
||||
\Filament\Forms\Components\RichEditor::make('description')
|
||||
->columnSpanFull(),
|
||||
Select::make('level_id')
|
||||
->relationship('level', 'name'),
|
||||
@@ -29,9 +29,13 @@ class CourseForm
|
||||
->required()
|
||||
->numeric()
|
||||
->default(0)
|
||||
->prefix('$'),
|
||||
TextInput::make('thumbnail_url')
|
||||
->url(),
|
||||
->prefix('IDR'),
|
||||
\Filament\Forms\Components\FileUpload::make('thumbnail_url')
|
||||
->disk('r2')
|
||||
->directory('thumbnails')
|
||||
->visibility('public')
|
||||
->image()
|
||||
->imageEditor(),
|
||||
Toggle::make('is_published')
|
||||
->required(),
|
||||
Textarea::make('metadata')
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\Vocabularies\Schemas;
|
||||
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class VocabularyForm
|
||||
@@ -12,20 +15,63 @@ class VocabularyForm
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('word')
|
||||
->required(),
|
||||
TextInput::make('reading'),
|
||||
TextInput::make('romaji'),
|
||||
TextInput::make('meaning_en'),
|
||||
TextInput::make('meaning_id'),
|
||||
TextInput::make('level_id'),
|
||||
TextInput::make('audio_url')
|
||||
->url(),
|
||||
TextInput::make('type'),
|
||||
Textarea::make('stroke_order_svg')
|
||||
->columnSpanFull(),
|
||||
Textarea::make('example_sentences')
|
||||
->columnSpanFull(),
|
||||
Section::make('Core Info')
|
||||
->schema([
|
||||
TextInput::make('word')
|
||||
->label('Word (Kanji/Kana)')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
TextInput::make('reading')
|
||||
->label('Reading (Kana)')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
TextInput::make('romaji')
|
||||
->maxLength(255),
|
||||
TextInput::make('meaning_id')
|
||||
->label('Meaning (Bahasa Indonesia)')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
TextInput::make('meaning_en')
|
||||
->label('Meaning (English)')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
])->columns(2),
|
||||
|
||||
Section::make('Metadata & Media')
|
||||
->schema([
|
||||
Select::make('level_id')
|
||||
->label('JLPT Level')
|
||||
->relationship('level', 'code')
|
||||
->searchable()
|
||||
->preload()
|
||||
->required(),
|
||||
Select::make('type')
|
||||
->options([
|
||||
'noun' => 'Noun',
|
||||
'verb' => 'Verb',
|
||||
'adjective' => 'Adjective',
|
||||
'adverb' => 'Adverb',
|
||||
'particle' => 'Particle',
|
||||
'expression' => 'Expression',
|
||||
])
|
||||
->required(),
|
||||
FileUpload::make('audio_url')
|
||||
->label('Pronunciation Audio')
|
||||
->disk('r2')
|
||||
->directory('audio/vocab')
|
||||
->visibility('public')
|
||||
->acceptedFileTypes(['audio/mpeg', 'audio/wav', 'audio/ogg']),
|
||||
])->columns(2),
|
||||
|
||||
Section::make('Advanced')
|
||||
->schema([
|
||||
Textarea::make('stroke_order_svg')
|
||||
->label('Stroke Order SVG (JSON)')
|
||||
->columnSpanFull(),
|
||||
Textarea::make('example_sentences')
|
||||
->label('Example Sentences (JSON)')
|
||||
->columnSpanFull(),
|
||||
])->collapsed(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Filament\Admin\Resources\Vocabularies\Tables;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class VocabulariesTable
|
||||
@@ -14,30 +14,33 @@ class VocabulariesTable
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('id')
|
||||
->label('ID')
|
||||
Tables\Columns\TextColumn::make('word')
|
||||
->label('Word')
|
||||
->searchable()
|
||||
->description(fn (\App\Models\Vocabulary $record): string => $record->reading),
|
||||
Tables\Columns\TextColumn::make('meaning_id')
|
||||
->label('Meaning')
|
||||
->searchable(),
|
||||
TextColumn::make('word')
|
||||
->searchable(),
|
||||
TextColumn::make('reading')
|
||||
->searchable(),
|
||||
TextColumn::make('romaji')
|
||||
->searchable(),
|
||||
TextColumn::make('meaning_en')
|
||||
->searchable(),
|
||||
TextColumn::make('meaning_id')
|
||||
->searchable(),
|
||||
TextColumn::make('level_id')
|
||||
->searchable(),
|
||||
TextColumn::make('audio_url')
|
||||
->searchable(),
|
||||
TextColumn::make('type')
|
||||
->searchable(),
|
||||
TextColumn::make('created_at')
|
||||
Tables\Columns\TextColumn::make('level.code')
|
||||
->label('Level')
|
||||
->badge()
|
||||
->color(fn (?string $state): string => match ($state) {
|
||||
'N1' => 'danger',
|
||||
'N2' => 'warning',
|
||||
'N3' => 'info',
|
||||
default => 'success',
|
||||
}),
|
||||
Tables\Columns\TextColumn::make('type')
|
||||
->badge(),
|
||||
Tables\Columns\IconColumn::make('audio_url')
|
||||
->label('Audio')
|
||||
->icon('heroicon-o-speaker-wave')
|
||||
->color(fn ($state) => $state ? 'success' : 'gray'),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
Tables\Columns\TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Courses;
|
||||
|
||||
use App\Filament\Resources\Courses\Pages\CreateCourse;
|
||||
use App\Filament\Resources\Courses\Pages\EditCourse;
|
||||
use App\Filament\Resources\Courses\Pages\ListCourses;
|
||||
use App\Filament\Resources\Courses\Schemas\CourseForm;
|
||||
use App\Filament\Resources\Courses\Tables\CoursesTable;
|
||||
use App\Models\Course;
|
||||
use BackedEnum;
|
||||
use Filament\Forms;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
|
||||
class CourseResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Course::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
|
||||
|
||||
protected static ?int $navigationSort = 1;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'title';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('title')
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->live(onBlur: true)
|
||||
->afterStateUpdated(fn ($state, $set) => $set('slug', \Illuminate\Support\Str::slug($state))),
|
||||
|
||||
Forms\Components\TextInput::make('slug')
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->unique(ignoreRecord: true),
|
||||
|
||||
Forms\Components\RichEditor::make('description')
|
||||
->columnSpanFull(),
|
||||
|
||||
Forms\Components\Select::make('level_id')
|
||||
->label('JLPT Level')
|
||||
->relationship('level', 'code')
|
||||
->searchable()
|
||||
->preload()
|
||||
->required(),
|
||||
|
||||
Forms\Components\TextInput::make('price')
|
||||
->numeric()
|
||||
->prefix('IDR')
|
||||
->maxValue(42949672.95),
|
||||
|
||||
Forms\Components\FileUpload::make('thumbnail_url')
|
||||
->label('Thumbnail Image')
|
||||
->image()
|
||||
->disk('r2')
|
||||
->directory('thumbnails')
|
||||
->visibility('public')
|
||||
->imageEditor(),
|
||||
|
||||
Forms\Components\Select::make('teacher_id')
|
||||
->relationship('teacher', 'name')
|
||||
->searchable()
|
||||
->preload()
|
||||
->required(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\ImageColumn::make('thumbnail_url')
|
||||
->disk('r2')
|
||||
->visibility('public'),
|
||||
Tables\Columns\TextColumn::make('title')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('level.code')
|
||||
->label('Level')
|
||||
->badge(),
|
||||
Tables\Columns\TextColumn::make('price')
|
||||
->money('IDR'),
|
||||
Tables\Columns\TextColumn::make('teacher.name')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListCourses::route('/'),
|
||||
'create' => CreateCourse::route('/create'),
|
||||
'edit' => EditCourse::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getRecordRouteBindingEloquentQuery(): Builder
|
||||
{
|
||||
return parent::getRecordRouteBindingEloquentQuery()
|
||||
->withoutGlobalScopes([
|
||||
SoftDeletingScope::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Courses\Pages;
|
||||
|
||||
use App\Filament\Resources\Courses\CourseResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateCourse extends CreateRecord
|
||||
{
|
||||
protected static string $resource = CourseResource::class;
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Courses\Pages;
|
||||
|
||||
use App\Filament\Resources\Courses\CourseResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\ForceDeleteAction;
|
||||
use Filament\Actions\RestoreAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditCourse extends EditRecord
|
||||
{
|
||||
protected static string $resource = CourseResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
ForceDeleteAction::make(),
|
||||
RestoreAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Courses\Pages;
|
||||
|
||||
use App\Filament\Resources\Courses\CourseResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListCourses extends ListRecords
|
||||
{
|
||||
protected static string $resource = CourseResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Courses\Schemas;
|
||||
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class CourseForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Select::make('teacher_id')
|
||||
->relationship('teacher', 'name')
|
||||
->required(),
|
||||
TextInput::make('title')
|
||||
->required(),
|
||||
TextInput::make('slug')
|
||||
->required(),
|
||||
Textarea::make('description')
|
||||
->columnSpanFull(),
|
||||
TextInput::make('level')
|
||||
->required()
|
||||
->default('N5'),
|
||||
TextInput::make('price')
|
||||
->required()
|
||||
->numeric()
|
||||
->default(0)
|
||||
->prefix('$'),
|
||||
TextInput::make('thumbnail_url')
|
||||
->url(),
|
||||
Toggle::make('is_published')
|
||||
->required(),
|
||||
Textarea::make('metadata')
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Courses\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ForceDeleteBulkAction;
|
||||
use Filament\Actions\RestoreBulkAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\TrashedFilter;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class CoursesTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('id')
|
||||
->label('ID')
|
||||
->searchable(),
|
||||
TextColumn::make('teacher.name')
|
||||
->searchable(),
|
||||
TextColumn::make('title')
|
||||
->searchable(),
|
||||
TextColumn::make('slug')
|
||||
->searchable(),
|
||||
TextColumn::make('level')
|
||||
->searchable(),
|
||||
TextColumn::make('price')
|
||||
->money()
|
||||
->sortable(),
|
||||
TextColumn::make('thumbnail_url')
|
||||
->searchable(),
|
||||
IconColumn::make('is_published')
|
||||
->boolean(),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('deleted_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
TrashedFilter::make(),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
ForceDeleteBulkAction::make(),
|
||||
RestoreBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Lessons;
|
||||
|
||||
use App\Filament\Resources\Lessons\Pages\CreateLesson;
|
||||
use App\Filament\Resources\Lessons\Pages\EditLesson;
|
||||
use App\Filament\Resources\Lessons\Pages\ListLessons;
|
||||
use App\Filament\Resources\Lessons\Schemas\LessonForm;
|
||||
use App\Filament\Resources\Lessons\Tables\LessonsTable;
|
||||
use App\Models\Lesson;
|
||||
use BackedEnum;
|
||||
use Filament\Forms;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
|
||||
class LessonResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Lesson::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-play-circle';
|
||||
|
||||
protected static ?int $navigationSort = 3;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'title';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->schema([
|
||||
Forms\Components\Select::make('module_id')
|
||||
->relationship('module', 'title')
|
||||
->searchable()
|
||||
->preload()
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('title')
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->live(onBlur: true)
|
||||
->afterStateUpdated(fn ($state, $set) => $set('slug', \Illuminate\Support\Str::slug($state))),
|
||||
Forms\Components\TextInput::make('slug')
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->unique(ignoreRecord: true),
|
||||
Forms\Components\Select::make('type')
|
||||
->options([
|
||||
'video' => 'Video',
|
||||
'text' => 'Text',
|
||||
'quiz' => 'Quiz',
|
||||
'vocab_list' => 'Vocabulary List',
|
||||
])
|
||||
->default('text')
|
||||
->required(),
|
||||
Forms\Components\FileUpload::make('content_pdf')
|
||||
->label('Lesson PDF (Paid Content)')
|
||||
->disk('r2_private')
|
||||
->directory('pdfs')
|
||||
->visibility('private')
|
||||
->acceptedFileTypes(['application/pdf'])
|
||||
->downloadable()
|
||||
->columnSpanFull(),
|
||||
Forms\Components\RichEditor::make('content')
|
||||
->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('video_url')
|
||||
->label('Video URL (YouTube/Vimeo)')
|
||||
->url(),
|
||||
Forms\Components\Toggle::make('is_free_preview')
|
||||
->label('Free Preview?')
|
||||
->default(false),
|
||||
Forms\Components\TextInput::make('order_index')
|
||||
->numeric()
|
||||
->default(0),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('title')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('module.title')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('type')
|
||||
->badge(),
|
||||
Tables\Columns\IconColumn::make('is_free_preview')
|
||||
->boolean(),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->defaultSort('order_index', 'asc');
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListLessons::route('/'),
|
||||
'create' => CreateLesson::route('/create'),
|
||||
'edit' => EditLesson::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getRecordRouteBindingEloquentQuery(): Builder
|
||||
{
|
||||
return parent::getRecordRouteBindingEloquentQuery()
|
||||
->withoutGlobalScopes([
|
||||
SoftDeletingScope::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Lessons\Pages;
|
||||
|
||||
use App\Filament\Resources\Lessons\LessonResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateLesson extends CreateRecord
|
||||
{
|
||||
protected static string $resource = LessonResource::class;
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Lessons\Pages;
|
||||
|
||||
use App\Filament\Resources\Lessons\LessonResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\ForceDeleteAction;
|
||||
use Filament\Actions\RestoreAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditLesson extends EditRecord
|
||||
{
|
||||
protected static string $resource = LessonResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
ForceDeleteAction::make(),
|
||||
RestoreAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Lessons\Pages;
|
||||
|
||||
use App\Filament\Resources\Lessons\LessonResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListLessons extends ListRecords
|
||||
{
|
||||
protected static string $resource = LessonResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Lessons\Schemas;
|
||||
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class LessonForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Select::make('module_id')
|
||||
->relationship('module', 'title')
|
||||
->required(),
|
||||
TextInput::make('title')
|
||||
->required(),
|
||||
TextInput::make('slug')
|
||||
->required(),
|
||||
TextInput::make('type')
|
||||
->required()
|
||||
->default('text'),
|
||||
Textarea::make('content')
|
||||
->columnSpanFull(),
|
||||
TextInput::make('video_url')
|
||||
->url(),
|
||||
TextInput::make('duration_seconds')
|
||||
->required()
|
||||
->numeric()
|
||||
->default(0),
|
||||
Toggle::make('is_free_preview')
|
||||
->required(),
|
||||
TextInput::make('order_index')
|
||||
->required()
|
||||
->numeric()
|
||||
->default(0),
|
||||
Textarea::make('metadata')
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Lessons\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ForceDeleteBulkAction;
|
||||
use Filament\Actions\RestoreBulkAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\TrashedFilter;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class LessonsTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('id')
|
||||
->label('ID')
|
||||
->searchable(),
|
||||
TextColumn::make('module.title')
|
||||
->searchable(),
|
||||
TextColumn::make('title')
|
||||
->searchable(),
|
||||
TextColumn::make('slug')
|
||||
->searchable(),
|
||||
TextColumn::make('type')
|
||||
->searchable(),
|
||||
TextColumn::make('video_url')
|
||||
->searchable(),
|
||||
TextColumn::make('duration_seconds')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
IconColumn::make('is_free_preview')
|
||||
->boolean(),
|
||||
TextColumn::make('order_index')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('deleted_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
TrashedFilter::make(),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
ForceDeleteBulkAction::make(),
|
||||
RestoreBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Levels;
|
||||
|
||||
use App\Filament\Resources\Levels\Pages\CreateLevel;
|
||||
use App\Filament\Resources\Levels\Pages\EditLevel;
|
||||
use App\Filament\Resources\Levels\Pages\ListLevels;
|
||||
use App\Filament\Resources\Levels\Schemas\LevelForm;
|
||||
use App\Filament\Resources\Levels\Tables\LevelsTable;
|
||||
use App\Models\Level;
|
||||
use BackedEnum;
|
||||
use Filament\Forms;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class LevelResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Level::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-signal';
|
||||
|
||||
protected static ?int $navigationSort = 10;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'code';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('code')
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->unique(ignoreRecord: true),
|
||||
Forms\Components\TextInput::make('name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\Textarea::make('description')
|
||||
->maxLength(65535)
|
||||
->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('order_index')
|
||||
->numeric()
|
||||
->default(0),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('code')
|
||||
->badge()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('order_index')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->defaultSort('order_index', 'asc');
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListLevels::route('/'),
|
||||
'create' => CreateLevel::route('/create'),
|
||||
'edit' => EditLevel::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Levels\Pages;
|
||||
|
||||
use App\Filament\Resources\Levels\LevelResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateLevel extends CreateRecord
|
||||
{
|
||||
protected static string $resource = LevelResource::class;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Levels\Pages;
|
||||
|
||||
use App\Filament\Resources\Levels\LevelResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditLevel extends EditRecord
|
||||
{
|
||||
protected static string $resource = LevelResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Levels\Pages;
|
||||
|
||||
use App\Filament\Resources\Levels\LevelResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListLevels extends ListRecords
|
||||
{
|
||||
protected static string $resource = LevelResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Levels\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class LevelForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->required(),
|
||||
TextInput::make('code')
|
||||
->required(),
|
||||
Textarea::make('description')
|
||||
->columnSpanFull(),
|
||||
TextInput::make('order_index')
|
||||
->required()
|
||||
->numeric()
|
||||
->default(0),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Levels\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class LevelsTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('id')
|
||||
->label('ID')
|
||||
->searchable(),
|
||||
TextColumn::make('name')
|
||||
->searchable(),
|
||||
TextColumn::make('code')
|
||||
->searchable(),
|
||||
TextColumn::make('order_index')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Modules;
|
||||
|
||||
use App\Filament\Resources\Modules\Pages\CreateModule;
|
||||
use App\Filament\Resources\Modules\Pages\EditModule;
|
||||
use App\Filament\Resources\Modules\Pages\ListModules;
|
||||
use App\Filament\Resources\Modules\Schemas\ModuleForm;
|
||||
use App\Filament\Resources\Modules\Tables\ModulesTable;
|
||||
use App\Models\Module;
|
||||
use BackedEnum;
|
||||
use Filament\Forms;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
|
||||
class ModuleResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Module::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-rectangle-group';
|
||||
|
||||
protected static ?int $navigationSort = 2;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'title';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->schema([
|
||||
Forms\Components\Select::make('course_id')
|
||||
->relationship('course', 'title')
|
||||
->searchable()
|
||||
->preload()
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('title')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\RichEditor::make('description')
|
||||
->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('order_index')
|
||||
->numeric()
|
||||
->default(0),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('title')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('course.title')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('order_index')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->defaultSort('order_index', 'asc');
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListModules::route('/'),
|
||||
'create' => CreateModule::route('/create'),
|
||||
'edit' => EditModule::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getRecordRouteBindingEloquentQuery(): Builder
|
||||
{
|
||||
return parent::getRecordRouteBindingEloquentQuery()
|
||||
->withoutGlobalScopes([
|
||||
SoftDeletingScope::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Modules\Pages;
|
||||
|
||||
use App\Filament\Resources\Modules\ModuleResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateModule extends CreateRecord
|
||||
{
|
||||
protected static string $resource = ModuleResource::class;
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Modules\Pages;
|
||||
|
||||
use App\Filament\Resources\Modules\ModuleResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\ForceDeleteAction;
|
||||
use Filament\Actions\RestoreAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditModule extends EditRecord
|
||||
{
|
||||
protected static string $resource = ModuleResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
ForceDeleteAction::make(),
|
||||
RestoreAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Modules\Pages;
|
||||
|
||||
use App\Filament\Resources\Modules\ModuleResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListModules extends ListRecords
|
||||
{
|
||||
protected static string $resource = ModuleResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Modules\Schemas;
|
||||
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class ModuleForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Select::make('course_id')
|
||||
->relationship('course', 'title')
|
||||
->required(),
|
||||
TextInput::make('title')
|
||||
->required(),
|
||||
Textarea::make('description')
|
||||
->columnSpanFull(),
|
||||
TextInput::make('order_index')
|
||||
->required()
|
||||
->numeric()
|
||||
->default(0),
|
||||
Toggle::make('is_free_preview')
|
||||
->required(),
|
||||
Textarea::make('metadata')
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Modules\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ForceDeleteBulkAction;
|
||||
use Filament\Actions\RestoreBulkAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\TrashedFilter;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ModulesTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('id')
|
||||
->label('ID')
|
||||
->searchable(),
|
||||
TextColumn::make('course.title')
|
||||
->searchable(),
|
||||
TextColumn::make('title')
|
||||
->searchable(),
|
||||
TextColumn::make('order_index')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
IconColumn::make('is_free_preview')
|
||||
->boolean(),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('deleted_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
TrashedFilter::make(),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
ForceDeleteBulkAction::make(),
|
||||
RestoreBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Roles\Pages;
|
||||
|
||||
use App\Filament\Resources\Roles\RoleResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateRole extends CreateRecord
|
||||
{
|
||||
protected static string $resource = RoleResource::class;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Roles\Pages;
|
||||
|
||||
use App\Filament\Resources\Roles\RoleResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditRole extends EditRecord
|
||||
{
|
||||
protected static string $resource = RoleResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Roles\Pages;
|
||||
|
||||
use App\Filament\Resources\Roles\RoleResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListRoles extends ListRecords
|
||||
{
|
||||
protected static string $resource = RoleResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Roles;
|
||||
|
||||
use App\Filament\Resources\Roles\Pages\CreateRole;
|
||||
use App\Filament\Resources\Roles\Pages\EditRole;
|
||||
use App\Filament\Resources\Roles\Pages\ListRoles;
|
||||
use App\Filament\Resources\Roles\Schemas\RoleForm;
|
||||
use App\Filament\Resources\Roles\Tables\RolesTable;
|
||||
use App\Models\Role;
|
||||
use BackedEnum;
|
||||
use Filament\Forms;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class RoleResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Role::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-rectangle-stack';
|
||||
|
||||
protected static ?int $navigationSort = 91;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('guard_name')
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->default('web'),
|
||||
Forms\Components\Select::make('permissions')
|
||||
->relationship('permissions', 'name')
|
||||
->multiple()
|
||||
->preload(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('guard_name')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('permissions_count')
|
||||
->counts('permissions')
|
||||
->label('Perms'),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListRoles::route('/'),
|
||||
'create' => CreateRole::route('/create'),
|
||||
'edit' => EditRole::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Roles\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class RoleForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->required(),
|
||||
TextInput::make('guard_name')
|
||||
->required(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Roles\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class RolesTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('id')
|
||||
->label('ID')
|
||||
->searchable(),
|
||||
TextColumn::make('name')
|
||||
->searchable(),
|
||||
TextColumn::make('guard_name')
|
||||
->searchable(),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Users\Pages;
|
||||
|
||||
use App\Filament\Resources\Users\UserResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateUser extends CreateRecord
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Users\Pages;
|
||||
|
||||
use App\Filament\Resources\Users\UserResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditUser extends EditRecord
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Users\Pages;
|
||||
|
||||
use App\Filament\Resources\Users\UserResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListUsers extends ListRecords
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Users\Schemas;
|
||||
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class UserForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->required(),
|
||||
TextInput::make('email')
|
||||
->label('Email address')
|
||||
->email()
|
||||
->required(),
|
||||
DateTimePicker::make('email_verified_at'),
|
||||
TextInput::make('password')
|
||||
->password(),
|
||||
TextInput::make('avatar_url')
|
||||
->url(),
|
||||
TextInput::make('xp_points')
|
||||
->required()
|
||||
->numeric()
|
||||
->default(0),
|
||||
TextInput::make('current_streak')
|
||||
->required()
|
||||
->numeric()
|
||||
->default(0),
|
||||
Textarea::make('metadata')
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Users\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class UsersTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('id')
|
||||
->label('ID')
|
||||
->searchable(),
|
||||
TextColumn::make('name')
|
||||
->searchable(),
|
||||
TextColumn::make('email')
|
||||
->label('Email address')
|
||||
->searchable(),
|
||||
TextColumn::make('email_verified_at')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
TextColumn::make('avatar_url')
|
||||
->searchable(),
|
||||
TextColumn::make('xp_points')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
TextColumn::make('current_streak')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Users;
|
||||
|
||||
use App\Filament\Resources\Users\Pages\CreateUser;
|
||||
use App\Filament\Resources\Users\Pages\EditUser;
|
||||
use App\Filament\Resources\Users\Pages\ListUsers;
|
||||
use App\Filament\Resources\Users\Schemas\UserForm;
|
||||
use App\Filament\Resources\Users\Tables\UsersTable;
|
||||
use App\Models\User;
|
||||
use BackedEnum;
|
||||
use Filament\Forms;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class UserResource extends Resource
|
||||
{
|
||||
protected static ?string $model = User::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-rectangle-stack';
|
||||
|
||||
protected static ?int $navigationSort = 90;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('email')
|
||||
->email()
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\DateTimePicker::make('email_verified_at'),
|
||||
Forms\Components\TextInput::make('password')
|
||||
->password()
|
||||
->dehydrateStateUsing(fn ($state) => filled($state) ? bcrypt($state) : null)
|
||||
->required(fn (string $context): bool => $context === 'create')
|
||||
->maxLength(255),
|
||||
Forms\Components\Select::make('roles')
|
||||
->relationship('roles', 'name')
|
||||
->multiple()
|
||||
->preload(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('email')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('roles.name')
|
||||
->badge(),
|
||||
Tables\Columns\TextColumn::make('email_verified_at')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListUsers::route('/'),
|
||||
'create' => CreateUser::route('/create'),
|
||||
'edit' => EditUser::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Vocabularies\Pages;
|
||||
|
||||
use App\Filament\Resources\Vocabularies\VocabularyResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateVocabulary extends CreateRecord
|
||||
{
|
||||
protected static string $resource = VocabularyResource::class;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Vocabularies\Pages;
|
||||
|
||||
use App\Filament\Resources\Vocabularies\VocabularyResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditVocabulary extends EditRecord
|
||||
{
|
||||
protected static string $resource = VocabularyResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Vocabularies\Pages;
|
||||
|
||||
use App\Filament\Resources\Vocabularies\VocabularyResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListVocabularies extends ListRecords
|
||||
{
|
||||
protected static string $resource = VocabularyResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Vocabularies\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class VocabularyForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('word')
|
||||
->required(),
|
||||
TextInput::make('reading'),
|
||||
TextInput::make('romaji'),
|
||||
TextInput::make('meaning_en'),
|
||||
TextInput::make('meaning_id'),
|
||||
TextInput::make('level'),
|
||||
TextInput::make('audio_url')
|
||||
->url(),
|
||||
TextInput::make('type'),
|
||||
Textarea::make('stroke_order_svg')
|
||||
->columnSpanFull(),
|
||||
Textarea::make('example_sentences')
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Vocabularies\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class VocabulariesTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('id')
|
||||
->label('ID')
|
||||
->searchable(),
|
||||
TextColumn::make('word')
|
||||
->searchable(),
|
||||
TextColumn::make('reading')
|
||||
->searchable(),
|
||||
TextColumn::make('romaji')
|
||||
->searchable(),
|
||||
TextColumn::make('meaning_en')
|
||||
->searchable(),
|
||||
TextColumn::make('meaning_id')
|
||||
->searchable(),
|
||||
TextColumn::make('level')
|
||||
->searchable(),
|
||||
TextColumn::make('audio_url')
|
||||
->searchable(),
|
||||
TextColumn::make('type')
|
||||
->searchable(),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Vocabularies;
|
||||
|
||||
use App\Filament\Resources\Vocabularies\Pages\CreateVocabulary;
|
||||
use App\Filament\Resources\Vocabularies\Pages\EditVocabulary;
|
||||
use App\Filament\Resources\Vocabularies\Pages\ListVocabularies;
|
||||
use App\Filament\Resources\Vocabularies\Schemas\VocabularyForm;
|
||||
use App\Filament\Resources\Vocabularies\Tables\VocabulariesTable;
|
||||
use App\Models\Vocabulary;
|
||||
use BackedEnum;
|
||||
use Filament\Forms;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components as Schemas;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class VocabularyResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Vocabulary::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-language';
|
||||
|
||||
protected static ?int $navigationSort = 4;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'word';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->schema([
|
||||
Schemas\Section::make('Core Info')
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('word')
|
||||
->label('Word (Kanji/Kana)')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('reading')
|
||||
->label('Reading (Kana)')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('romaji')
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('meaning_id')
|
||||
->label('Meaning (Bahasa Indonesia)')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('meaning_en')
|
||||
->label('Meaning (English)')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
])->columns(2),
|
||||
|
||||
Schemas\Section::make('Metadata & Media')
|
||||
->schema([
|
||||
Forms\Components\Select::make('level_id')
|
||||
->label('JLPT Level')
|
||||
->relationship('level', 'code')
|
||||
->searchable()
|
||||
->preload()
|
||||
->required(),
|
||||
Forms\Components\Select::make('type')
|
||||
->options([
|
||||
'noun' => 'Noun',
|
||||
'verb' => 'Verb',
|
||||
'adjective' => 'Adjective',
|
||||
'adverb' => 'Adverb',
|
||||
'particle' => 'Particle',
|
||||
'expression' => 'Expression',
|
||||
])
|
||||
->required(),
|
||||
Forms\Components\FileUpload::make('audio_url')
|
||||
->label('Pronunciation Audio')
|
||||
->disk('r2')
|
||||
->directory('audio/vocab')
|
||||
->visibility('public')
|
||||
->acceptedFileTypes(['audio/mpeg', 'audio/wav', 'audio/ogg']),
|
||||
])->columns(2),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('word')
|
||||
->label('Word')
|
||||
->searchable()
|
||||
->description(fn (Vocabulary $record): string => $record->reading),
|
||||
Tables\Columns\TextColumn::make('meaning_id')
|
||||
->label('Meaning')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('level.code')
|
||||
->label('Level')
|
||||
->badge()
|
||||
->color(fn (?string $state): string => match ($state) {
|
||||
'N1' => 'danger',
|
||||
'N2' => 'warning',
|
||||
'N3' => 'info',
|
||||
default => 'success',
|
||||
}),
|
||||
Tables\Columns\TextColumn::make('type')
|
||||
->badge(),
|
||||
Tables\Columns\IconColumn::make('audio_url')
|
||||
->label('Audio')
|
||||
->icon('heroicon-o-speaker-wave')
|
||||
->color(fn ($state) => $state ? 'success' : 'gray'),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('level_id')
|
||||
->label('Level')
|
||||
->relationship('level', 'code'),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListVocabularies::route('/'),
|
||||
'create' => CreateVocabulary::route('/create'),
|
||||
'edit' => EditVocabulary::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -42,15 +42,20 @@ class TestDataSeeder extends Seeder
|
||||
);
|
||||
|
||||
$lessons1 = [
|
||||
['title' => 'Selamat Datang di N5', 'slug' => 'welcome-n5', 'type' => 'video'],
|
||||
['title' => 'Struktur Menulis Jepang', 'slug' => 'writing-structure', 'type' => 'text'],
|
||||
['title' => 'Kata Ganti Orang', 'slug' => 'pronouns', 'type' => 'video'],
|
||||
['title' => 'Selamat Datang di N5', 'slug' => 'welcome-n5', 'type' => 'video', 'video_url' => 'https://www.youtube.com/watch?v=e8mQAY6HQQc'],
|
||||
['title' => 'Struktur Menulis Jepang', 'slug' => 'writing-structure', 'type' => 'text', 'video_url' => null],
|
||||
['title' => 'Kata Ganti Orang', 'slug' => 'pronouns', 'type' => 'video', 'video_url' => 'https://www.youtube.com/watch?v=e8mQAY6HQQc'],
|
||||
];
|
||||
|
||||
foreach ($lessons1 as $index => $lessonData) {
|
||||
Lesson::updateOrCreate(
|
||||
['module_id' => $module1->id, 'slug' => $lessonData['slug']],
|
||||
['title' => $lessonData['title'], 'type' => $lessonData['type'], 'order_index' => $index + 1]
|
||||
[
|
||||
'title' => $lessonData['title'],
|
||||
'type' => $lessonData['type'],
|
||||
'video_url' => $lessonData['video_url'],
|
||||
'order_index' => $index + 1
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -73,14 +78,19 @@ class TestDataSeeder extends Seeder
|
||||
);
|
||||
|
||||
$lessons2 = [
|
||||
['title' => 'Baris A-I-U-E-O', 'slug' => 'vowels-kana', 'type' => 'video'],
|
||||
['title' => 'Baris KA-KI-KU-KE-KO', 'slug' => 'ka-line', 'type' => 'video'],
|
||||
['title' => 'Baris A-I-U-E-O', 'slug' => 'vowels-kana', 'type' => 'video', 'video_url' => 'https://www.youtube.com/watch?v=icK6kVTegDA'],
|
||||
['title' => 'Baris KA-KI-KU-KE-KO', 'slug' => 'ka-line', 'type' => 'video', 'video_url' => 'https://www.youtube.com/watch?v=icK6kVTegDA'],
|
||||
];
|
||||
|
||||
foreach ($lessons2 as $index => $lessonData) {
|
||||
Lesson::updateOrCreate(
|
||||
['module_id' => $module2->id, 'slug' => $lessonData['slug']],
|
||||
['title' => $lessonData['title'], 'type' => $lessonData['type'], 'order_index' => $index + 1]
|
||||
[
|
||||
'title' => $lessonData['title'],
|
||||
'type' => $lessonData['type'],
|
||||
'video_url' => $lessonData['video_url'],
|
||||
'order_index' => $index + 1
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user