first commit

This commit is contained in:
Mohamad Al-Kahfi 2025-06-03 10:52:02 +07:00
commit 43cc02b39e
334 changed files with 63412 additions and 0 deletions

18
.editorconfig Normal file
View File

@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[docker-compose.yml]
indent_size = 4

59
.env.example Normal file
View File

@ -0,0 +1,59 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=ternakq_animal_husbandry_education
DB_USERNAME=root
DB_PASSWORD=
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
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
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1
VITE_APP_NAME="${APP_NAME}"
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

11
.gitattributes vendored Normal file
View File

@ -0,0 +1,11 @@
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore

19
.gitignore vendored Normal file
View File

@ -0,0 +1,19 @@
/.phpunit.cache
/node_modules
/public/build
/public/hot
/public/storage
/storage/*.key
/vendor
.env
.env.backup
.env.production
.phpunit.result.cache
Homestead.json
Homestead.yaml
auth.json
npm-debug.log
yarn-error.log
/.fleet
/.idea
/.vscode

66
README.md Normal file
View File

@ -0,0 +1,66 @@
<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>
<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).

27
app/Console/Kernel.php Normal file
View File

@ -0,0 +1,27 @@
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* Define the application's command schedule.
*/
protected function schedule(Schedule $schedule): void
{
// $schedule->command('inspire')->hourly();
}
/**
* Register the commands for the application.
*/
protected function commands(): void
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* The list of the inputs that are never flashed to the session on validation exceptions.
*
* @var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*/
public function register(): void
{
$this->reportable(function (Throwable $e) {
//
});
}
}

View File

@ -0,0 +1,83 @@
<?php
namespace App\Http\Controllers;
use App\Models\Article;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class AdminArticleAccController extends Controller
{
public function indexAdminArticle(Request $request)
{
try {
$articlePage = $request->get('article_page', 1);
$articles = Article::latest()->paginate(10, ['*'], 'article_page', $articlePage);
return view('admin.article-management', compact('articles'));
} catch (\Exception $e) {
Log::error('Gagal memuat data artikel: ' . $e->getMessage());
return redirect()->route('admin.article-management')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memuat data artikel.',
]);
}
}
public function editArticle($id)
{
try {
$article = Article::with(['subArticles' => function ($query) {
$query->orderBy('order_number', 'asc');
}])->findOrFail($id);
return view('admin.article-management-edit', compact('article'));
} catch (\Exception $e) {
Log::error('Gagal memuat artikel untuk edit: ' . $e->getMessage());
return redirect()->route('admin.article-management-edit')
->with('status', 'error')
->with('message', 'Terjadi kesalahan saat memuat artikel.');
}
}
public function updateArticle(Request $request, $id)
{
try {
$validated = $request->validate([
'catatan' => 'nullable|string',
'status' => 'required|in:Tertunda,Disetujui,Ditolak',
]);
$article = Article::findOrFail($id);
$article->update($validated);
return redirect()->route('admin.article-management')->with([
'status' => 'success',
'message' => 'Status artikel berhasil diperbarui!',
]);
} catch (\Exception $e) {
Log::error('Gagal memperbarui artikel: ' . $e->getMessage());
return redirect()->route('admin.article-management')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memperbarui artikel.',
]);
}
}
public function deleteArticle($id)
{
try {
$article = Article::findOrFail($id);
$article->tags()->detach();
if ($article->image && Storage::disk('public')->exists($article->image)) {
Storage::disk('public')->delete($article->image);
}
$article->delete();
return response()->json(['success' => true, 'message' => 'Artikel berhasil dihapus.']);
} catch (\Exception $e) {
Log::error('Gagal menghapus artikel: ' . $e->getMessage());
return response()->json(['success' => false, 'message' => 'Gagal menghapus artikel: ' . $e->getMessage()], 500);
}
}
}

View File

@ -0,0 +1,201 @@
<?php
namespace App\Http\Controllers;
use App\Models\CardArticle;
use App\Models\Article;
use App\Models\Tag;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\ValidationException;
class AdminArticleController extends Controller
{
public function indexAdminArtikel(Request $request)
{
try {
$articlePage = $request->get('article_page', 1);
$articles = Article::with('cardArticle')
->where('user_id', Auth::id())
->latest()
->paginate(4, ['*'], 'article_page', $articlePage);
$cardArticles = CardArticle::where('user_id', Auth::id())->get();
$tags = Tag::all();
$articles->appends(['article_page' => $articlePage]);
$totalArticles = Article::where('user_id', Auth::id())->count();
$todayArticles = Article::where('user_id', Auth::id())
->whereDate('created_at', now())
->count();
return view('admin.add-article-detail', compact('articles', 'cardArticles', 'tags', 'totalArticles', 'todayArticles'));
} catch (\Exception $e) {
Log::error('Gagal memuat data artikel: ' . $e->getMessage());
return redirect()->route('admin.add-article-detail')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memuat data artikel.',
]);
}
}
public function storeAdminArtikel(Request $request)
{
try {
$validated = $request->validate([
'card_id' => 'required|exists:card_articles,id',
'title' => 'required|string|max:255',
'description' => 'required|string',
'status' => 'required|string|in:Tertunda,Disetujui,Ditolak',
'tags' => 'required|array|min:1|max:3',
'tags.*' => 'exists:tags,id',
'image' => 'required|image|mimes:jpeg,png,jpg,gif|max:2048',
], [
'card_id.required' => 'Artikel grup harus dipilih',
'card_id.exists' => 'Artikel grup yang dipilih tidak valid',
'title.required' => 'Judul artikel harus diisi',
'title.max' => 'Judul artikel maksimal 255 karakter',
'description.required' => 'Deskripsi artikel harus diisi',
'tags.required' => 'Minimal satu tag harus dipilih',
'tags.array' => 'Format tag tidak valid',
'tags.min' => 'Minimal satu tag harus dipilih',
'tags.max' => 'Maksimal tiga tag yang dapat dipilih',
'tags.*.exists' => 'Tag yang dipilih tidak valid',
'image.required' => 'Gambar artikel harus diunggah',
'image.image' => 'File harus berupa gambar',
'image.mimes' => 'Format gambar harus jpeg, png, jpg, atau gif',
'image.max' => 'Ukuran gambar maksimal 2MB',
]);
$imagePath = null;
if ($request->hasFile('image')) {
$imagePath = $request->file('image')->store('articles', 'public');
}
$article = Article::create([
'card_id' => $validated['card_id'],
'title' => $validated['title'],
'description' => $validated['description'],
'status' => $validated['status'],
'image' => $imagePath,
'user_id' => Auth::id(),
]);
if (!empty($validated['tags'])) {
$article->tags()->attach($validated['tags']);
}
return redirect()->route('admin.add-article-detail')->with([
'status' => 'success',
'message' => 'Artikel berhasil dibuat!',
]);
} catch (ValidationException $e) {
return redirect()->back()
->withErrors($e->validator)
->withInput();
} catch (\Exception $e) {
Log::error('Gagal menyimpan artikel: ' . $e->getMessage());
return redirect()->back()->withInput()->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat menyimpan artikel: ' . $e->getMessage(),
]);
}
}
public function updateAdminArtikel(Request $request, $id)
{
try {
$validated = $request->validate([
'card_id' => 'required|exists:card_articles,id',
'title' => 'required|string|max:255',
'description' => 'required|string',
'status' => 'required|string|in:Tertunda,Disetujui,Ditolak',
'tags' => 'required|array|min:1|max:3',
'tags.*' => 'exists:tags,id',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
], [
'card_id.required' => 'Artikel grup harus dipilih',
'card_id.exists' => 'Artikel grup yang dipilih tidak valid',
'title.required' => 'Judul artikel harus diisi',
'title.max' => 'Judul artikel maksimal 255 karakter',
'description.required' => 'Deskripsi artikel harus diisi',
'tags.required' => 'Minimal satu tag harus dipilih',
'tags.array' => 'Format tag tidak valid',
'tags.min' => 'Minimal satu tag harus dipilih',
'tags.max' => 'Maksimal tiga tag yang dapat dipilih',
'tags.*.exists' => 'Tag yang dipilih tidak valid',
'image.image' => 'File harus berupa gambar',
'image.mimes' => 'Format gambar harus jpeg, png, jpg, atau gif',
'image.max' => 'Ukuran gambar maksimal 2MB',
]);
$article = Article::where('id', $id)
->where('user_id', Auth::id())
->firstOrFail();
$imagePath = $article->image;
if ($request->hasFile('image')) {
if ($article->image && Storage::disk('public')->exists($article->image)) {
Storage::disk('public')->delete($article->image);
}
$imagePath = $request->file('image')->store('articles', 'public');
}
$article->update([
'card_id' => $validated['card_id'],
'title' => $validated['title'],
'description' => $validated['description'],
'status' => $validated['status'],
'image' => $imagePath,
]);
if (!empty($validated['tags'])) {
$article->tags()->sync($validated['tags']);
} else {
$article->tags()->detach();
}
return redirect()->route('admin.add-article-detail')->with([
'status' => 'success',
'message' => 'Artikel berhasil diperbarui!',
]);
} catch (ValidationException $e) {
return redirect()->back()
->withErrors($e->validator)
->withInput();
} catch (\Exception $e) {
Log::error('Gagal memperbarui artikel: ' . $e->getMessage());
return redirect()->back()->withInput()->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memperbarui artikel: ' . $e->getMessage(),
]);
}
}
public function deleteAdminArtikel($id)
{
try {
$article = Article::where('id', $id)
->where('user_id', Auth::id())
->firstOrFail();
$article->tags()->detach();
if ($article->image) {
Storage::disk('public')->delete($article->image);
}
$article->delete();
return response()->json(['success' => true, 'message' => 'Artikel berhasil dihapus.']);
} catch (\Exception $e) {
Log::error('Gagal menghapus artikel: ' . $e->getMessage());
return response()->json(['success' => false, 'message' => 'Gagal menghapus artikel.'], 500);
}
}
}

View File

@ -0,0 +1,165 @@
<?php
namespace App\Http\Controllers;
use App\Models\CardArticle;
use App\Models\Article;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\ValidationException;
class AdminCardArticleController extends Controller
{
public function indexAdminArtikel(Request $request)
{
try {
$artikelPage = $request->get('artikel_page', 1);
$articles = CardArticle::where('user_id', Auth::id())
->withCount('articles')
->latest()
->paginate(5, ['*'], 'artikel_page', $artikelPage);
$approvedCount = Article::where('status', 'Disetujui')
->where('user_id', Auth::id())
->count();
return view('admin.add-card-article', compact('articles', 'approvedCount'));
} catch (\Exception $e) {
Log::error('Gagal memuat data artikel: ' . $e->getMessage());
return redirect()->route('admin.add-article')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memuat data artikel.',
]);
}
}
public function storeAdminArtikel(Request $request)
{
try {
$validated = $request->validate([
'title' => 'required|string|max:255',
'description' => 'required|string',
'image' => 'required|image|mimes:jpeg,png,jpg,gif|max:2048',
], [
'title.required' => 'Judul grup artikel harus diisi.',
'title.max' => 'Judul grup artikel maksimal 255 karakter.',
'description.required' => 'Deskripsi grup artikel harus diisi.',
'image.required' => 'Gambar grup artikel harus diunggah.',
'image.image' => 'File harus berupa gambar.',
'image.mimes' => 'Format gambar harus jpeg, png, jpg, atau gif.',
'image.max' => 'Ukuran gambar maksimal 2MB.'
]);
$imagePath = null;
if ($request->hasFile('image')) {
$imagePath = $request->file('image')->store('card_articles', 'public');
}
$cardArticle = CardArticle::create([
'title' => $validated['title'],
'description' => $validated['description'],
'image' => $imagePath,
'user_id' => Auth::id(),
]);
return redirect()->route('admin.add-article')->with([
'status' => 'success',
'message' => 'Artikel grup berhasil ditambahkan!',
]);
} catch (ValidationException $e) {
return redirect()->back()
->withErrors($e->validator)
->withInput();
} catch (\Exception $e) {
Log::error('Gagal menyimpan artikel: ' . $e->getMessage());
return redirect()->route('admin.add-article')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat menyimpan artikel.',
]);
}
}
public function updateAdminArtikel(Request $request, $id)
{
try {
$validated = $request->validate([
'title' => 'required|string|max:255',
'description' => 'required|string',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
], [
'title.required' => 'Judul grup artikel harus diisi.',
'title.max' => 'Judul grup artikel maksimal 255 karakter.',
'description.required' => 'Deskripsi grup artikel harus diisi.',
'image.image' => 'File harus berupa gambar.',
'image.mimes' => 'Format gambar harus jpeg, png, jpg, atau gif.',
'image.max' => 'Ukuran gambar maksimal 2MB.'
]);
$card = CardArticle::where('id', $id)
->where('user_id', Auth::id())
->firstOrFail();
$imagePath = $card->image;
if ($request->hasFile('image')) {
if ($imagePath && Storage::disk('public')->exists($imagePath)) {
Storage::disk('public')->delete($imagePath);
Log::info('Gambar lama berhasil dihapus: ' . $imagePath);
}
$newImagePath = $request->file('image')->store('card_articles', 'public');
Log::info('Gambar baru berhasil disimpan: ' . $newImagePath);
$imagePath = $newImagePath;
}
$card->update([
'title' => $validated['title'],
'description' => $validated['description'],
'image' => $imagePath,
]);
return redirect()->route('admin.add-article')->with([
'status' => 'success',
'message' => 'Artikel grup berhasil diperbarui!',
]);
} catch (ValidationException $e) {
return redirect()->back()
->withErrors($e->validator)
->withInput();
} catch (ModelNotFoundException $e) {
return redirect()->route('admin.add-article')->with([
'status' => 'error',
'message' => 'Artikel grup tidak ditemukan.',
]);
} catch (\Exception $e) {
Log::error('Gagal memperbarui artikel grup: ' . $e->getMessage());
return redirect()->route('admin.add-article')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memperbarui artikel grup.',
]);
}
}
public function deleteAdminArtikel($id)
{
try {
$cardArticle = CardArticle::where('id', $id)
->where('user_id', Auth::id())
->firstOrFail();
if ($cardArticle->image) {
Storage::disk('public')->delete($cardArticle->image);
}
$cardArticle->delete();
return response()->json(['success' => true, 'message' => 'Grup artikel berhasil dihapus.']);
} catch (\Exception $e) {
Log::error('Gagal menghapus artikel: ' . $e->getMessage());
return response()->json(['success' => false, 'message' => 'Gagal menghapus artikel.'], 500);
}
}
}

View File

@ -0,0 +1,261 @@
<?php
namespace App\Http\Controllers;
use App\Models\SubArticle;
use App\Models\Article;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\ValidationException;
class AdminSubArticleController extends Controller
{
public function indexAdminArtikel(Request $request)
{
try {
$subArticle = null;
$articles = Article::where('user_id', Auth::id())->get();
$selectedArticle = null;
$subArticles = collect();
$articleId = $request->query('article_id');
if ($articleId) {
$selectedArticle = Article::with(['subArticles' => function ($query) {
$query->orderBy('order_number', 'asc')
->orderBy('id', 'desc');
}])->where('user_id', Auth::id())->find($articleId);
if ($selectedArticle) {
$subArticles = $selectedArticle->subArticles;
}
}
$editId = $request->query('edit_id');
if ($editId) {
$subArticle = SubArticle::where('id', $editId)
->whereHas('article', function ($query) {
$query->where('user_id', Auth::id());
})
->first();
}
return view('admin.add-article-sub', compact('subArticles', 'articles', 'selectedArticle', 'subArticle'));
} catch (\Exception $e) {
Log::error('Gagal memuat sub-artikel: ' . $e->getMessage());
return redirect()->route('admin.add-article-sub')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memuat data sub-artikel.',
]);
}
}
public function storeMultipleSubArticles(Request $request)
{
try {
$validated = $request->validate([
'article_id' => 'required|exists:articles,id',
'sub_articles' => 'required|array|min:1',
'sub_articles.*.title' => 'required|string|max:255',
'sub_articles.*.content' => 'required|string',
'sub_articles.*.order_number' => 'required|integer|min:1',
'sub_articles.*.image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
'sub_articles.*.remove_image' => 'nullable|in:0,1',
], [
'article_id.required' => 'Artikel induk harus dipilih.',
'article_id.exists' => 'Artikel induk tidak ditemukan.',
'sub_articles.required' => 'Minimal satu sub artikel harus diisi.',
'sub_articles.min' => 'Minimal satu sub artikel harus diisi.',
'sub_articles.*.title.required' => 'Judul sub artikel harus diisi.',
'sub_articles.*.title.max' => 'Judul sub artikel maksimal 255 karakter.',
'sub_articles.*.content.required' => 'Konten sub artikel harus diisi.',
'sub_articles.*.order_number.required' => 'Urutan sub artikel harus diisi.',
'sub_articles.*.order_number.integer' => 'Urutan sub artikel harus berupa angka.',
'sub_articles.*.order_number.min' => 'Urutan sub artikel minimal 1.',
'sub_articles.*.image.image' => 'File harus berupa gambar.',
'sub_articles.*.image.mimes' => 'Format gambar harus jpeg, png, jpg, atau gif.',
'sub_articles.*.image.max' => 'Ukuran gambar maksimal 2MB.',
]);
$article = Article::where('id', $validated['article_id'])
->where('user_id', Auth::id())
->firstOrFail();
$subArticlesData = [];
foreach ($validated['sub_articles'] as $subArticle) {
$imagePath = null;
if (isset($subArticle['image']) && $subArticle['image'] instanceof \Illuminate\Http\UploadedFile) {
$imagePath = $subArticle['image']->store('sub_articles', 'public');
}
$subArticlesData[] = [
'article_id' => $validated['article_id'],
'title' => $subArticle['title'],
'content' => $subArticle['content'],
'order_number' => $subArticle['order_number'],
'image' => $imagePath,
'user_id' => Auth::id(),
'created_at' => now(),
'updated_at' => now(),
];
}
SubArticle::insert($subArticlesData);
return redirect()->route('admin.add-article-sub', ['article_id' => $validated['article_id']])->with([
'status' => 'success',
'message' => 'Semua sub-artikel berhasil disimpan!',
]);
} catch (ValidationException $e) {
return redirect()->back()->withErrors($e->validator)->withInput();
} catch (\Exception $e) {
Log::error('Gagal menyimpan sub-artikel: ' . $e->getMessage());
return redirect()->route('admin.add-article-sub')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat menyimpan sub-artikel: ' . $e->getMessage(),
]);
}
}
public function updateAdminArtikel(Request $request, $id)
{
try {
$subArticle = SubArticle::where('id', $id)
->whereHas('article', function ($query) {
$query->where('user_id', Auth::id());
})
->firstOrFail();
$validated = $request->validate([
'article_id' => 'required|exists:articles,id',
'sub_articles' => 'required|array',
'sub_articles.*.title' => 'required|string|max:255',
'sub_articles.*.content' => 'required|string',
'sub_articles.*.order_number' => 'required|integer|min:1',
'sub_articles.*.image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
'sub_articles.*.remove_image' => 'nullable|in:0,1',
], [
'article_id.required' => 'Artikel induk harus dipilih.',
'article_id.exists' => 'Artikel induk tidak ditemukan.',
'sub_articles.required' => 'Data sub artikel harus diisi.',
'sub_articles.*.title.required' => 'Judul sub artikel harus diisi.',
'sub_articles.*.title.max' => 'Judul sub artikel maksimal 255 karakter.',
'sub_articles.*.content.required' => 'Konten sub artikel harus diisi.',
'sub_articles.*.order_number.required' => 'Urutan sub artikel harus diisi.',
'sub_articles.*.order_number.integer' => 'Urutan sub artikel harus berupa angka.',
'sub_articles.*.order_number.min' => 'Urutan sub artikel minimal 1.',
'sub_articles.*.image.image' => 'File harus berupa gambar.',
'sub_articles.*.image.mimes' => 'Format gambar harus jpeg, png, jpg, atau gif.',
'sub_articles.*.image.max' => 'Ukuran gambar maksimal 2MB.',
]);
$removeImage = $request->input('sub_articles.0.remove_image', '0');
$imagePath = $subArticle->image;
if ($removeImage === '1') {
if ($imagePath && Storage::disk('public')->exists($imagePath)) {
Storage::disk('public')->delete($imagePath);
}
$imagePath = null;
}
elseif ($request->hasFile('sub_articles.0.image')) {
if ($imagePath && Storage::disk('public')->exists($imagePath)) {
Storage::disk('public')->delete($imagePath);
}
$imagePath = $request->file('sub_articles.0.image')->store('sub_articles', 'public');
}
$subArticle->update([
'article_id' => $validated['article_id'],
'title' => $validated['sub_articles'][0]['title'],
'content' => $validated['sub_articles'][0]['content'],
'order_number' => $validated['sub_articles'][0]['order_number'],
'image' => $imagePath,
]);
return redirect()->route('admin.add-article-sub', ['article_id' => $validated['article_id']])->with([
'status' => 'success',
'message' => 'Sub-artikel berhasil diperbarui!',
]);
} catch (ValidationException $e) {
return redirect()->back()->withErrors($e->validator)->withInput();
} catch (\Exception $e) {
Log::error('Gagal memperbarui sub-artikel: ' . $e->getMessage());
return redirect()->route('admin.add-article-sub')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memperbarui sub-artikel: ' . $e->getMessage(),
]);
}
}
public function editAdminArtikel($id)
{
try {
$subArticle = SubArticle::where('id', $id)
->whereHas('article', function ($query) {
$query->where('user_id', Auth::id());
})
->firstOrFail();
$selectedArticle = $subArticle->article;
$articles = Article::where('user_id', Auth::id())->get();
return view('admin.add-article-sub', compact('subArticle', 'selectedArticle', 'articles'));
} catch (\Exception $e) {
return redirect()->route('admin.add-article-sub')->with([
'status' => 'error',
'message' => 'Sub-artikel tidak ditemukan atau tidak dapat diakses.',
]);
}
}
public function deleteAdminArtikel($id)
{
try {
$subArticle = SubArticle::where('id', $id)
->whereHas('article', function ($query) {
$query->where('user_id', Auth::id());
})
->firstOrFail();
$articleId = $subArticle->article_id;
if ($subArticle->image) {
Storage::disk('public')->delete($subArticle->image);
}
$subArticle->delete();
if (request()->ajax() || request()->wantsJson()) {
return response()->json([
'success' => true,
'message' => 'Sub-artikel berhasil dihapus!'
]);
}
return redirect()->route('admin.add-article-sub', ['article_id' => $articleId])->with([
'status' => 'success',
'message' => 'Sub-artikel berhasil dihapus!',
]);
} catch (\Exception $e) {
Log::error('Gagal menghapus sub-artikel: ' . $e->getMessage());
if (request()->ajax() || request()->wantsJson()) {
return response()->json([
'success' => false,
'message' => 'Terjadi kesalahan saat menghapus sub-artikel: ' . $e->getMessage()
], 500);
}
return redirect()->route('admin.add-article-sub')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat menghapus sub-artikel.',
]);
}
}
}

View File

@ -0,0 +1,152 @@
<?php
namespace App\Http\Controllers;
use App\Models\User;
use App\Models\Article;
use App\Models\CardArticle;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class ArticleController extends Controller
{
public function index()
{
$cardArticles = CardArticle::whereHas('articles', function($q) {
$q->where('status', 'Disetujui');
})
->with(['articles' => function($q) {
$q->where('status', 'Disetujui')->with('subArticles');
}])
->withCount(['articles' => function($q) {
$q->where('status', 'Disetujui');
}])
->latest()
->take(8)
->get();
$readingSpeed = 200;
foreach ($cardArticles as $card) {
$totalReadingTime = 0;
foreach ($card->articles as $article) {
$text = $article->title . ' ' . $article->description;
if ($article->subArticles && $article->subArticles->isNotEmpty()) {
$subText = $article->subArticles
->map(function ($sub) {
return $sub->title . ' ' . $sub->content;
})
->implode(' ');
$text .= ' ' . $subText;
}
$wordCount = str_word_count(strip_tags($text));
$articleReadingTime = ceil($wordCount / $readingSpeed);
$totalReadingTime += $articleReadingTime;
}
$card->readingTime = $totalReadingTime;
}
$totalArticles = Article::where('status', 'Disetujui')->count();
$totalUsers = User::count();
return view('home', compact('cardArticles', 'totalArticles', 'totalUsers'));
}
public function showAllCards(Request $request)
{
$search = $request->get('search');
$cardArticlesQuery = CardArticle::whereHas('articles', function ($q) {
$q->where('status', 'Disetujui');
})
->withCount(['articles' => function ($q) {
$q->where('status', 'Disetujui');
}])
->latest();
if ($search) {
$cardArticlesQuery->where('title', 'like', '%' . $search . '%');
}
$cardArticles = $cardArticlesQuery->paginate(8);
$readingSpeed = 200;
foreach ($cardArticles as $card) {
$totalReadingTime = 0;
foreach ($card->articles as $article) {
$text = $article->title . ' ' . $article->description;
if ($article->subArticles && $article->subArticles->isNotEmpty()) {
$subText = $article->subArticles
->map(function ($sub) {
return $sub->title . ' ' . $sub->content;
})
->implode(' ');
$text .= ' ' . $subText;
}
$wordCount = str_word_count(strip_tags($text));
$articleReadingTime = ceil($wordCount / $readingSpeed);
$totalReadingTime += $articleReadingTime;
}
$card->readingTime = $totalReadingTime;
}
return view('livewire.pages.home.all-cards', compact('cardArticles'));
}
public function showArticles(Request $request, $id)
{
try {
$card = CardArticle::whereHas('articles', function($query) {
$query->where('status', 'Disetujui');
})->findOrFail($id);
$articlesQuery = $card->articles()
->with('subArticles')
->where('status', 'Disetujui');
if ($request->has('search')) {
$search = $request->get('search');
$articlesQuery->where('title', 'like', '%' . $search . '%');
}
$articles = $articlesQuery->latest()->paginate(8);
$readingSpeed = 200;
foreach ($articles as $article) {
$text = $article->title . ' ' . $article->description;
if ($article->subArticles && $article->subArticles->isNotEmpty()) {
$subText = $article->subArticles->map(function ($sub) {
return $sub->title . ' ' . $sub->content;
})->implode(' ');
$text .= ' ' . $subText;
}
$wordCount = str_word_count(strip_tags($text));
$article->readingTime = ceil($wordCount / $readingSpeed);
}
$totalReadingTime = $articles->sum('readingTime');
$card->readingTime = $totalReadingTime;
return view('livewire.pages.home.articles', compact('card', 'articles'));
} catch (\Exception $e) {
return redirect('/')->with('error', 'Card artikel tidak ditemukan atau tidak memiliki artikel yang disetujui.');
}
}
public function showArticleDetail($id)
{
$article = Article::with(['subArticles' => function ($query) {
$query->orderBy('order_number', 'asc');
}])->findOrFail($id);
return view('livewire.pages.home.article-detail', compact('article'));
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Providers\RouteServiceProvider;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class AuthenticatedSessionController extends Controller
{
/**
* Protect the controller with the 'auth' middleware.
*/
public function __construct()
{
$this->middleware('auth')->except('store');
}
/**
* Handle login and redirect based on role.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(Request $request)
{
$request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
if (Auth::attempt($request->only('email', 'password'), $request->boolean('remember'))) {
$request->session()->regenerate();
return redirect()->intended(RouteServiceProvider::HOME);
}
return back()->withErrors([
'email' => 'Kredensial yang diberikan salah.',
]);
}
/**
* Logout the authenticated user and invalidate their session.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Request $request)
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Auth\Events\Verified;
use Illuminate\Foundation\Auth\EmailVerificationRequest;
use Illuminate\Http\RedirectResponse;
class VerifyEmailController extends Controller
{
public function __invoke(EmailVerificationRequest $request): RedirectResponse
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(RouteServiceProvider::HOME.'?verified=1');
}
if ($request->user()->markEmailAsVerified()) {
event(new Verified($request->user()));
}
return redirect()->intended(RouteServiceProvider::HOME.'?verified=1');
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests, ValidatesRequests;
}

View File

@ -0,0 +1,107 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Auth;
use App\Models\KandangAyam;
use Carbon\Carbon;
use DB;
class DashboardController extends Controller
{
public function index(Request $request)
{
$userId = Auth::id();
$totalKandangAyams = KandangAyam::where('status_kandang', 'Aktif')
->where('user_id', $userId)
->count();
$totalCapacity = KandangAyam::where('status_kandang', 'Aktif')
->where('user_id', $userId)
->sum('kapasitas');
$totalDeathsThisMonth = DB::table('harian_ayam')
->whereMonth('tanggal_input', Carbon::now()->month)
->whereYear('tanggal_input', Carbon::now()->year)
->whereIn('id_populasi', function ($query) use ($userId) {
$query->select('id')->from('populasi_ayam')->where('user_id', $userId);
})
->sum('jumlah_ayam_mati');
$currentMonthDeaths = DB::table('harian_ayam')
->whereYear('tanggal_input', Carbon::now()->year)
->whereMonth('tanggal_input', Carbon::now()->month)
->whereIn('id_populasi', function ($query) use ($userId) {
$query->select('id')->from('populasi_ayam')->where('user_id', $userId);
})
->sum('jumlah_ayam_mati');
$previousMonthDeaths = DB::table('harian_ayam')
->whereYear('tanggal_input', Carbon::now()->year)
->whereMonth('tanggal_input', Carbon::now()->subMonth()->month)
->whereIn('id_populasi', function ($query) use ($userId) {
$query->select('id')->from('populasi_ayam')->where('user_id', $userId);
})
->sum('jumlah_ayam_mati');
$percentageChange = 0;
if ($previousMonthDeaths > 0) {
$percentageChange = (($currentMonthDeaths - $previousMonthDeaths) / $previousMonthDeaths) * 100;
}
$populasiSub = DB::table('populasi_ayam')
->whereIn('status_ayam', ['Proses', 'Siap Panen'])
->where('user_id', $userId)
->select('kandang_id', DB::raw('SUM(jumlah_ayam_masuk) as total_ayam'))
->groupBy('kandang_id');
$harianSub = DB::table('harian_ayam')
->join('populasi_ayam', 'harian_ayam.id_populasi', '=', 'populasi_ayam.id')
->whereIn('populasi_ayam.status_ayam', ['Proses', 'Siap Panen'])
->where('populasi_ayam.user_id', $userId)
->select('populasi_ayam.kandang_id',
DB::raw('SUM(jumlah_ayam_sakit) as total_sick'),
DB::raw('SUM(jumlah_ayam_mati) as total_dead'))
->groupBy('populasi_ayam.kandang_id');
$KandangAyams = DB::table('kandang_ayam')
->where('user_id', $userId)
->leftJoinSub($populasiSub, 'populasi', function ($join) {
$join->on('kandang_ayam.id', '=', 'populasi.kandang_id');
})
->leftJoinSub($harianSub, 'harian', function ($join) {
$join->on('kandang_ayam.id', '=', 'harian.kandang_id');
})
->select(
'kandang_ayam.*',
DB::raw('COALESCE(populasi.total_ayam, 0) as total_ayam'),
DB::raw('COALESCE(harian.total_sick, 0) as total_sick'),
DB::raw('COALESCE(harian.total_dead, 0) as total_dead')
)
->having('total_ayam', '>', 0)
->paginate(5);
$pendapatanBulanIni = DB::table('pendapatan')
->whereMonth('tanggal_transaksi', Carbon::now()->month)
->whereYear('tanggal_transaksi', Carbon::now()->year)
->where('user_id', $userId)
->sum('total_pendapatan');
$pengeluaranBulanIni = DB::table('pengeluaran')
->whereMonth('tanggal_pembelian', Carbon::now()->month)
->whereYear('tanggal_pembelian', Carbon::now()->year)
->where('user_id', $userId)
->sum('total_biaya');
return view('dashboard', compact(
'totalKandangAyams',
'totalCapacity',
'totalDeathsThisMonth',
'percentageChange',
'KandangAyams',
'pendapatanBulanIni',
'pengeluaranBulanIni',
));
}
}

View File

@ -0,0 +1,121 @@
<?php
namespace App\Http\Controllers;
use App\Models\KandangAyam;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Auth;
class KandangAyamController extends Controller
{
public function indexKandangManagement(Request $request)
{
try {
$kandangPage = $request->get('kandang_page', 1);
$kandang = KandangAyam::where('user_id', Auth::id())
->latest()
->paginate(4, ['*'], 'kandang_page', $kandangPage);
return view('cage-management', compact('kandang'));
} catch (\Exception $e) {
Log::error('Gagal memuat data kandang: ' . $e->getMessage());
return redirect()->route('cage-management')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memuat data kandang.',
]);
}
}
public function storeKandang(Request $request)
{
$validated = $request->validate([
'nama_kandang' => 'required|string|max:255',
'kapasitas' => 'required|integer|min:1',
'status_kandang' => 'required|in:Aktif,Tidak Aktif',
], [
'nama_kandang.required' => 'Nama kandang harus diisi.',
'nama_kandang.max' => 'Nama kandang maksimal 255 karakter.',
'kapasitas.required' => 'Kapasitas kandang harus diisi.',
'kapasitas.integer' => 'Kapasitas harus berupa angka.',
'kapasitas.min' => 'Kapasitas kandang minimal 1.',
'status_kandang.required' => 'Status kandang harus dipilih.',
'status_kandang.in' => 'Status kandang tidak valid.',
]);
try {
$validated['user_id'] = Auth::id();
KandangAyam::create($validated);
return redirect()->route('cage-management')->with([
'status' => 'success',
'message' => 'Kandang berhasil ditambahkan.',
]);
} catch (\Exception $e) {
Log::error('Gagal menyimpan kandang: ' . $e->getMessage());
return redirect()->route('cage-management')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat menyimpan data kandang.',
]);
}
}
public function updateKandang(Request $request, $id)
{
$validated = $request->validate([
'nama_kandang' => 'required|string|max:255',
'kapasitas' => 'required|integer|min:1',
'status_kandang' => 'required|in:Aktif,Tidak Aktif',
], [
'nama_kandang.required' => 'Nama kandang harus diisi.',
'nama_kandang.max' => 'Nama kandang maksimal 255 karakter.',
'kapasitas.required' => 'Kapasitas kandang harus diisi.',
'kapasitas.integer' => 'Kapasitas harus berupa angka.',
'kapasitas.min' => 'Kapasitas kandang minimal 1.',
'status_kandang.required' => 'Status kandang harus dipilih.',
'status_kandang.in' => 'Status kandang tidak valid.',
]);
try {
$kandang = KandangAyam::where('id', $id)->where('user_id', Auth::id())->firstOrFail();
if ($kandang->status_kandang === 'Aktif' && $validated['status_kandang'] === 'Tidak Aktif') {
$activePopulations = $kandang->populasiAyam()
->whereIn('status_ayam', ['Proses', 'Siap Panen'])
->count();
if ($activePopulations > 0) {
return redirect()->back()->with([
'status' => 'warning',
'message' => "Kandang ini memiliki $activePopulations populasi ayam aktif. Pastikan semua populasi sudah dipanen sebelum menonaktifkan kandang.",
]);
}
}
$kandang->update($validated);
return redirect()->route('cage-management')->with([
'status' => 'success',
'message' => 'Kandang berhasil diperbarui.',
]);
} catch (\Exception $e) {
Log::error('Gagal menyimpan kandang: ' . $e->getMessage());
return redirect()->route('cage-management')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat menyimpan data kandang.',
]);
}
}
public function destroyKandang($id)
{
try {
$kandang = KandangAyam::where('id', $id)->where('user_id', Auth::id())->firstOrFail();
$kandang->delete();
return response()->json(['success' => true, 'message' => 'Kandang berhasil dihapus.']);
} catch (\Exception $e) {
Log::error('Gagal menghapus kandang: ' . $e->getMessage());
return response()->json(['success' => false, 'message' => 'Gagal menghapus kandang.'], 500);
}
}
}

View File

@ -0,0 +1,133 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Pendapatan;
use App\Models\Pengeluaran;
use Illuminate\Support\Facades\Log;
use Carbon\Carbon;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Support\Facades\Auth;
class KeuanganController extends Controller
{
public function indexKeuangan(Request $request)
{
try {
$bulan = $request->get('bulan', Carbon::now()->format('m'));
$tahun = $request->get('tahun', Carbon::now()->format('Y'));
if ($tahun == Carbon::now()->format('Y') && $bulan > Carbon::now()->format('m')) {
$bulan = Carbon::now()->format('m');
}
if ($tahun > Carbon::now()->format('Y')) {
$tahun = Carbon::now()->format('Y');
}
$namaBulan = Carbon::create()->month($bulan)->translatedFormat('F');
$pendapatan = Pendapatan::where('user_id', Auth::id())
->whereYear('tanggal_transaksi', $tahun)
->whereMonth('tanggal_transaksi', $bulan)
->orderBy('tanggal_transaksi', 'asc')
->get()
->map(function ($item) {
return [
'tanggal' => $item->tanggal_transaksi,
'keterangan' => $item->kategori,
'jumlah' => $item->jumlah * $item->harga_per_satuan,
'tipe' => 'pendapatan',
];
});
$pengeluaran = Pengeluaran::where('user_id', Auth::id())
->whereYear('tanggal_pembelian', $tahun)
->whereMonth('tanggal_pembelian', $bulan)
->orderBy('tanggal_pembelian', 'asc')
->get()
->map(function ($item) {
return [
'tanggal' => $item->tanggal_pembelian,
'keterangan' => $item->category . ' - ' . $item->description,
'jumlah' => $item->jumlah * $item->harga_per_satuan,
'tipe' => 'pengeluaran',
];
});
$transaksi = collect($pendapatan)->merge($pengeluaran)->sortBy('tanggal');
$totalPendapatan = $pendapatan->sum('jumlah');
$totalPengeluaran = $pengeluaran->sum('jumlah');
$labaBersih = $totalPendapatan - $totalPengeluaran;
$totalPendapatanHarian = $transaksi->filter(fn($trx) => $trx['tipe'] === 'pendapatan')->sum('jumlah');
$totalPengeluaranHarian = $transaksi->filter(fn($trx) => $trx['tipe'] === 'pengeluaran')->sum('jumlah');
$totalSaldoHarian = $totalPendapatanHarian - $totalPengeluaranHarian;
return view('finance-management', compact(
'transaksi',
'totalPendapatan',
'totalPengeluaran',
'labaBersih',
'bulan',
'tahun',
'namaBulan',
'totalPendapatanHarian',
'totalPengeluaranHarian',
'totalSaldoHarian'
));
} catch (\Exception $e) {
Log::error('Gagal memuat laporan keuangan: ' . $e->getMessage());
return redirect()->route('finance-management')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memuat laporan keuangan.',
]);
}
}
public function exportPDF(Request $request)
{
$bulan = $request->get('bulan', Carbon::now()->format('m'));
$tahun = $request->get('tahun', Carbon::now()->format('Y'));
$namaBulan = Carbon::create()->month($bulan)->translatedFormat('F');
$userId = Auth::id(); // Simpan user ID dalam variabel agar konsisten
$pendapatan = Pendapatan::where('user_id', $userId)
->whereYear('tanggal_transaksi', $tahun)
->whereMonth('tanggal_transaksi', $bulan)
->orderBy('tanggal_transaksi', 'asc')
->get()
->map(fn($item) => [
'tanggal' => $item->tanggal_transaksi,
'keterangan' => $item->kategori,
'jumlah' => $item->jumlah * $item->harga_per_satuan,
'tipe' => 'pendapatan',
]);
$pengeluaran = Pengeluaran::where('user_id', $userId) // Tambahkan filter user_id di sini
->whereYear('tanggal_pembelian', $tahun)
->whereMonth('tanggal_pembelian', $bulan)
->orderBy('tanggal_pembelian', 'asc')
->get()
->map(fn($item) => [
'tanggal' => $item->tanggal_pembelian,
'keterangan' => $item->category . ' - ' . $item->description,
'jumlah' => $item->jumlah * $item->harga_per_satuan,
'tipe' => 'pengeluaran',
]);
$transaksi = collect($pendapatan)->merge($pengeluaran)->sortBy('tanggal');
$totalPendapatan = $pendapatan->sum('jumlah');
$totalPengeluaran = $pengeluaran->sum('jumlah');
$totalSaldo = $totalPendapatan - $totalPengeluaran;
// Tambahkan nama user ke dalam view untuk ditampilkan dalam PDF
$user = Auth::user();
$pdf = Pdf::loadView('cetak.laporan-keuangan', compact(
'transaksi', 'totalPendapatan', 'totalPengeluaran', 'totalSaldo', 'namaBulan', 'tahun', 'user'
));
return $pdf->download("Laporan-Keuangan-{$user->name}-{$namaBulan}-{$tahun}.pdf");
}
}

View File

@ -0,0 +1,141 @@
<?php
namespace App\Http\Controllers;
use App\Models\Pakan;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
class PakanController extends Controller
{
public function indexPakan(Request $request)
{
try {
$pakanPage = $request->get('pakan_page', 1);
$pakan = Pakan::where('user_id', Auth::id())
->latest()
->paginate(5, ['*'], 'pakan_page', $pakanPage);
return view('food-management', compact('pakan'));
} catch (\Exception $e) {
Log::error('Gagal memuat data pakan: ' . $e->getMessage());
return redirect()->route('food-management')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memuat data pakan.',
]);
}
}
public function storePakan(Request $request)
{
try {
$validated = $request->validate([
'nama_pakan' => 'required|string|max:255',
'jenis_pakan' => 'required|string|max:255',
'berat' => 'required|numeric|min:1',
'tanggal_masuk' => 'required|date',
'harga_per_kg' => 'required|numeric|min:1000'
], [
'nama_pakan.required' => 'Nama pakan harus diisi.',
'nama_pakan.max' => 'Nama pakan maksimal 255 karakter.',
'jenis_pakan.required' => 'Jenis pakan harus diisi.',
'jenis_pakan.max' => 'Jenis pakan maksimal 255 karakter.',
'berat.required' => 'Berat pakan harus diisi.',
'berat.numeric' => 'Berat pakan harus berupa angka.',
'berat.min' => 'Berat pakan minimal 1 kg.',
'tanggal_masuk.required' => 'Tanggal masuk harus diisi.',
'tanggal_masuk.date' => 'Format tanggal masuk tidak valid.',
'harga_per_kg.required' => 'Harga per kg harus diisi.',
'harga_per_kg.numeric' => 'Harga per kg harus berupa angka.',
'harga_per_kg.min' => 'Harga per kg minimal Rp 1.000.'
]);
$validated['user_id'] = Auth::id();
Pakan::create($validated);
return redirect()->route('food-management')->with([
'status' => 'success',
'message' => 'Pakan berhasil ditambahkan.'
]);
} catch (ValidationException $e) {
return redirect()->back()->withErrors($e->validator)->withInput();
} catch (\Exception $e) {
Log::error('Gagal menambah pakan: ' . $e->getMessage());
return redirect()->route('food-management')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat menambahkan pakan.'
]);
}
}
public function updatePakan(Request $request, $id)
{
try {
$validated = $request->validate([
'nama_pakan' => 'required|string|max:255',
'jenis_pakan' => 'required|string|max:255',
'berat' => 'required|numeric|min:1',
'tanggal_masuk' => 'required|date',
'harga_per_kg' => 'required|numeric|min:1000'
], [
'nama_pakan.required' => 'Nama pakan harus diisi.',
'nama_pakan.max' => 'Nama pakan maksimal 255 karakter.',
'jenis_pakan.required' => 'Jenis pakan harus diisi.',
'jenis_pakan.max' => 'Jenis pakan maksimal 255 karakter.',
'berat.required' => 'Berat pakan harus diisi.',
'berat.numeric' => 'Berat pakan harus berupa angka.',
'berat.min' => 'Berat pakan minimal 1 kg.',
'tanggal_masuk.required' => 'Tanggal masuk harus diisi.',
'tanggal_masuk.date' => 'Format tanggal masuk tidak valid.',
'harga_per_kg.required' => 'Harga per kg harus diisi.',
'harga_per_kg.numeric' => 'Harga per kg harus berupa angka.',
'harga_per_kg.min' => 'Harga per kg minimal Rp 1.000.'
]);
$pakan = Pakan::where('id', $id)
->where('user_id', Auth::id())
->firstOrFail();
$pakan->update($validated);
return redirect()->route('food-management')->with([
'status' => 'success',
'message' => 'Pakan berhasil diperbarui.'
]);
} catch (ValidationException $e) {
return redirect()->back()->withErrors($e->validator)->withInput();
} catch (\Exception $e) {
Log::error('Gagal memperbarui pakan: ' . $e->getMessage());
return redirect()->route('food-management')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memperbarui pakan.'
]);
}
}
public function destroyPakan($id)
{
try {
$pakan = Pakan::where('id', $id)
->where('user_id', Auth::id())
->firstOrFail();
$pakan->delete();
return response()->json([
'success' => true,
'message' => 'Pakan berhasil dihapus.'
]);
} catch (\Exception $e) {
Log::error('Gagal menghapus pakan: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'Terjadi kesalahan saat menghapus pakan.'
], 500);
}
}
}

View File

@ -0,0 +1,159 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Pendapatan;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
class PendapatanController extends Controller
{
public function indexPendapatan(Request $request)
{
try {
$pendapatanPage = $request->get('pendapatan_page', 1);
$pendapatan = Pendapatan::where('user_id', Auth::id())
->orderBy('tanggal_transaksi', 'desc')
->paginate(10, ['*'], 'pendapatan_page', $pendapatanPage);
return view('finance-management-income', compact('pendapatan'));
} catch (\Exception $e) {
Log::error('Gagal memuat data pendapatan: ' . $e->getMessage());
return redirect()->route('finance-management-income')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memuat data pendapatan.',
]);
}
}
public function storePendapatan(Request $request)
{
try {
$validated = $request->validate([
'kategori' => 'required|in:Penjualan Ayam,Penjualan Kotoran,Pendapatan Kemitraan',
'jumlah' => 'required|numeric|min:1',
'satuan' => 'required|in:ekor,kg,karung',
'harga_per_satuan' => 'required|numeric|min:1000',
'tanggal_transaksi' => 'required|date',
'nama_pembeli' => 'nullable|string|max:255',
'nama_perusahaan' => 'nullable|string|max:255',
], [
'kategori.required' => 'Kategori harus dipilih.',
'kategori.in' => 'Kategori tidak valid.',
'jumlah.required' => 'Jumlah harus diisi.',
'jumlah.numeric' => 'Jumlah harus berupa angka.',
'jumlah.min' => 'Jumlah minimal 1.',
'satuan.required' => 'Satuan harus dipilih.',
'satuan.in' => 'Satuan tidak valid.',
'harga_per_satuan.required' => 'Harga per satuan harus diisi.',
'harga_per_satuan.numeric' => 'Harga per satuan harus berupa angka.',
'harga_per_satuan.min' => 'Harga per satuan minimal Rp 1.000.',
'tanggal_transaksi.required' => 'Tanggal transaksi harus diisi.',
'tanggal_transaksi.date' => 'Format tanggal transaksi tidak valid.',
'nama_pembeli.max' => 'Nama pembeli maksimal 255 karakter.',
'nama_perusahaan.max' => 'Nama perusahaan maksimal 255 karakter.'
]);
$validated['user_id'] = Auth::id();
Pendapatan::create($validated);
return redirect()->route('finance-management-income')->with([
'status' => 'success',
'message' => 'Pendapatan berhasil ditambahkan.',
]);
} catch (ValidationException $e) {
return redirect()->back()
->withErrors($e->validator)
->withInput();
} catch (\Exception $e) {
Log::error('Gagal menyimpan pendapatan: ' . $e->getMessage());
return redirect()->route('finance-management-income')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat menyimpan pendapatan.',
]);
}
}
public function updatePendapatan(Request $request, $id)
{
try {
$validated = $request->validate([
'kategori' => 'required|in:Penjualan Ayam,Penjualan Kotoran,Pendapatan Kemitraan',
'jumlah' => 'required|numeric|min:1',
'satuan' => 'required|in:ekor,kg,karung',
'harga_per_satuan' => 'required|numeric|min:1000',
'tanggal_transaksi' => 'required|date',
'nama_pembeli' => 'nullable|string|max:255',
'nama_perusahaan' => 'nullable|string|max:255',
], [
'kategori.required' => 'Kategori harus dipilih.',
'kategori.in' => 'Kategori tidak valid.',
'jumlah.required' => 'Jumlah harus diisi.',
'jumlah.numeric' => 'Jumlah harus berupa angka.',
'jumlah.min' => 'Jumlah minimal 1.',
'satuan.required' => 'Satuan harus dipilih.',
'satuan.in' => 'Satuan tidak valid.',
'harga_per_satuan.required' => 'Harga per satuan harus diisi.',
'harga_per_satuan.numeric' => 'Harga per satuan harus berupa angka.',
'harga_per_satuan.min' => 'Harga per satuan minimal Rp 1.000.',
'tanggal_transaksi.required' => 'Tanggal transaksi harus diisi.',
'tanggal_transaksi.date' => 'Format tanggal transaksi tidak valid.',
'nama_pembeli.max' => 'Nama pembeli maksimal 255 karakter.',
'nama_perusahaan.max' => 'Nama perusahaan maksimal 255 karakter.'
]);
$pendapatan = Pendapatan::where('id', $id)
->where('user_id', Auth::id())
->firstOrFail();
$pendapatan->update($validated);
return redirect()->route('finance-management-income')->with([
'status' => 'success',
'message' => 'Pendapatan berhasil diperbarui.',
]);
} catch (ValidationException $e) {
return redirect()->back()
->withErrors($e->validator)
->withInput();
} catch (ModelNotFoundException $e) {
return redirect()->route('finance-management-income')->with([
'status' => 'error',
'message' => 'Data pendapatan tidak ditemukan.',
]);
} catch (\Exception $e) {
Log::error('Gagal memperbarui pendapatan: ' . $e->getMessage());
return redirect()->route('finance-management-income')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memperbarui pendapatan.',
]);
}
}
public function destroyPendapatan(Request $request, $id)
{
try {
$pendapatan = Pendapatan::where('id', $id)
->where('user_id', Auth::id())
->firstOrFail();
$pendapatan->delete();
return response()->json(['success' => true, 'message' => 'Pendapatan berhasil dihapus.']);
} catch (\Exception $e) {
Log::error('Gagal menghapus pendapatan: ' . $e->getMessage());
return response()->json(['success' => false, 'message' => 'Gagal menghapus pendapatan.'], 500);
}
}
}

View File

@ -0,0 +1,160 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Pengeluaran;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
class PengeluaranController extends Controller
{
public function indexPengeluaran(Request $request)
{
try {
$pengeluaranPage = $request->get('pengeluaran_page', 1);
$pengeluaran = Pengeluaran::where('user_id', Auth::id())
->orderBy('tanggal_pembelian', 'desc')
->paginate(10, ['*'], 'pengeluaran_page', $pengeluaranPage);
return view('finance-management-outcome', compact('pengeluaran'));
} catch (\Exception $e) {
Log::error('Gagal memuat data pengeluaran: ' . $e->getMessage());
return redirect()->route('finance-management-outcome')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memuat data pengeluaran.',
]);
}
}
public function storePengeluaran(Request $request)
{
try {
$validated = $request->validate([
'category' => 'required|in:Pembelian Ayam,Pakan Ayam,Vitamin',
'description' => 'nullable|string|max:255',
'jumlah' => 'required|numeric|min:1',
'satuan' => 'required|in:ekor,kg,karung,unit',
'harga_per_satuan' => 'required|numeric|min:1',
'tanggal_pembelian' => 'required|date',
'supplier' => 'nullable|string|max:255',
], [
'category.required' => 'Kategori harus dipilih.',
'category.in' => 'Kategori tidak valid.',
'description.max' => 'Deskripsi pengeluaran maksimal 255 karakter.',
'jumlah.required' => 'Jumlah harus diisi.',
'jumlah.numeric' => 'Jumlah harus berupa angka.',
'jumlah.min' => 'Jumlah minimal 1.',
'satuan.required' => 'Satuan harus dipilih.',
'satuan.in' => 'Satuan tidak valid.',
'harga_per_satuan.required' => 'Harga per satuan harus diisi.',
'harga_per_satuan.numeric' => 'Harga per satuan harus berupa angka.',
'harga_per_satuan.min' => 'Harga per satuan minimal Rp 1.',
'tanggal_pembelian.required' => 'Tanggal pembelian harus diisi.',
'tanggal_pembelian.date' => 'Format tanggal pembelian tidak valid.',
'supplier.max' => 'Nama supplier maksimal 255 karakter.',
]);
$validated['user_id'] = Auth::id();
$validated['total_biaya'] = $request->jumlah * $request->harga_per_satuan;
Pengeluaran::create($validated);
return redirect()->route('finance-management-outcome')->with([
'status' => 'success',
'message' => 'Pengeluaran berhasil ditambahkan.',
]);
} catch (ValidationException $e) {
return redirect()->back()
->withErrors($e->validator)
->withInput();
} catch (\Exception $e) {
Log::error('Gagal menyimpan pengeluaran: ' . $e->getMessage());
return redirect()->route('finance-management-outcome')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat menyimpan pengeluaran.',
]);
}
}
public function updatePengeluaran(Request $request, $id)
{
try {
$validated = $request->validate([
'category' => 'required|in:Pembelian Ayam,Pakan Ayam,Vitamin',
'description' => 'nullable|string|max:255',
'jumlah' => 'required|numeric|min:1',
'satuan' => 'required|in:ekor,kg,karung,unit',
'harga_per_satuan' => 'required|numeric|min:1',
'tanggal_pembelian' => 'required|date',
'supplier' => 'nullable|string|max:255',
], [
'category.required' => 'Kategori harus dipilih.',
'category.in' => 'Kategori tidak valid.',
'description.required' => 'Deskripsi pengeluaran harus diisi.',
'description.max' => 'Deskripsi pengeluaran maksimal 255 karakter.',
'jumlah.required' => 'Jumlah harus diisi.',
'jumlah.numeric' => 'Jumlah harus berupa angka.',
'jumlah.min' => 'Jumlah minimal 1.',
'satuan.required' => 'Satuan harus dipilih.',
'satuan.in' => 'Satuan tidak valid.',
'harga_per_satuan.required' => 'Harga per satuan harus diisi.',
'harga_per_satuan.numeric' => 'Harga per satuan harus berupa angka.',
'harga_per_satuan.min' => 'Harga per satuan minimal Rp 1.',
'tanggal_pembelian.required' => 'Tanggal pembelian harus diisi.',
'tanggal_pembelian.date' => 'Format tanggal pembelian tidak valid.',
'supplier.max' => 'Nama supplier maksimal 255 karakter.',
]);
$pengeluaran = Pengeluaran::where('id', $id)
->where('user_id', Auth::id())
->firstOrFail();
$validated['total_biaya'] = $request->jumlah * $request->harga_per_satuan;
$pengeluaran->update($validated);
return redirect()->route('finance-management-outcome')->with([
'status' => 'success',
'message' => 'Pengeluaran berhasil diperbarui.',
]);
} catch (ValidationException $e) {
return redirect()->back()
->withErrors($e->validator)
->withInput();
} catch (ModelNotFoundException $e) {
return redirect()->route('finance-management-outcome')->with([
'status' => 'error',
'message' => 'Data pengeluaran tidak ditemukan.',
]);
} catch (\Exception $e) {
Log::error('Gagal memperbarui pengeluaran: ' . $e->getMessage());
return redirect()->route('finance-management-outcome')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memperbarui pengeluaran.',
]);
}
}
public function destroyPengeluaran(Request $request, $id)
{
try {
$pengeluaran = Pengeluaran::where('id', $id)
->where('user_id', Auth::id())
->firstOrFail();
$pengeluaran->delete();
return response()->json(['success' => true, 'message' => 'Pengeluaran berhasil dihapus.']);
} catch (\Exception $e) {
Log::error('Gagal menghapus pengeluaran: ' . $e->getMessage());
return response()->json(['success' => false, 'message' => 'Gagal menghapus pengeluaran.'], 500);
}
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace App\Http\Controllers;
use App\Models\Pakan;
use App\Models\PenggunaanPakan;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
class PenggunaanPakanController extends Controller
{
public function storePenggunaanPakan(Request $request)
{
try {
$validated = $request->validate([
'nama_pakan' => 'required|string|max:255',
'tanggal_pakai' => 'required|date',
'jumlah_pakai' => 'required|numeric|min:1',
], [
'nama_pakan.required' => 'Nama pakan harus dipilih.',
'nama_pakan.max' => 'Nama pakan maksimal 255 karakter.',
'tanggal_pakai.required' => 'Tanggal pakai harus diisi.',
'tanggal_pakai.date' => 'Format tanggal pakai tidak valid.',
'jumlah_pakai.required' => 'Jumlah pakai harus diisi.',
'jumlah_pakai.numeric' => 'Jumlah pakai harus berupa angka.',
'jumlah_pakai.min' => 'Minimal jumlah pakai adalah 1 kg.'
]);
$pakan = Pakan::where('nama_pakan', $request->nama_pakan)->first();
if (!$pakan) {
return redirect()->back()
->withInput()
->withErrors(['nama_pakan' => 'Pakan tidak ditemukan.']);
}
if ($pakan->berat < $request->jumlah_pakai) {
return redirect()->back()
->withInput()
->withErrors(['jumlah_pakai' => "Stok pakan tidak mencukupi. Tersedia: {$pakan->berat} kg"]);
}
PenggunaanPakan::create([
'pakan_id' => $pakan->id,
'tanggal_pakai' => $request->tanggal_pakai,
'jumlah_pakai' => $request->jumlah_pakai
]);
$pakan->update([
'berat' => $pakan->berat - $request->jumlah_pakai
]);
return redirect()->route('food-management')->with([
'status' => 'success',
'message' => 'Penggunaan pakan berhasil disimpan.'
]);
} catch (ValidationException $e) {
return redirect()->back()
->withErrors($e->validator)
->withInput();
} catch (\Exception $e) {
Log::error('Gagal menyimpan penggunaan pakan: ' . $e->getMessage());
return redirect()->route('food-management')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat menyimpan penggunaan pakan.'
]);
}
}
}

View File

@ -0,0 +1,518 @@
<?php
namespace App\Http\Controllers;
use App\Models\PopulasiAyam;
use App\Models\HarianAyam;
use App\Models\KandangAyam;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Barryvdh\DomPDF\Facade\Pdf;
use Carbon\Carbon;
use Illuminate\Validation\ValidationException;
class PopulasiHarianController extends Controller
{
public function indexChickenManagement(Request $request)
{
try {
$populasiPage = $request->get('populasi_page', 1);
$harianPage = $request->get('harian_page', 1);
$populasi = PopulasiAyam::with('kandang')
->where('user_id', Auth::id())
->latest()
->paginate(5, ['*'], 'populasi_page', $populasiPage);
$harian = HarianAyam::whereHas('populasiAyam', function($query) {
$query->where('user_id', Auth::id());
})
->latest()
->paginate(5, ['*'], 'harian_page', $harianPage);
$batches = PopulasiAyam::where('user_id', Auth::id())->get();
$kandang = KandangAyam::where('status_kandang', 'Aktif')
->where('user_id', Auth::id())
->get();
$userId = Auth::id();
$monthlyData = DB::table('harian_ayam')
->join('populasi_ayam', 'harian_ayam.id_populasi', '=', 'populasi_ayam.id')
->whereIn('populasi_ayam.status_ayam', ['Proses', 'Siap Panen'])
->where('populasi_ayam.user_id', $userId)
->select(
DB::raw('MONTH(harian_ayam.tanggal_input) as month'),
DB::raw('SUM(harian_ayam.jumlah_ayam_sakit) as sick'),
DB::raw('SUM(harian_ayam.jumlah_ayam_mati) as dead')
)
->whereYear('harian_ayam.tanggal_input', Carbon::now()->year)
->groupBy(DB::raw('MONTH(harian_ayam.tanggal_input)'))
->orderBy('month')
->get();
$todayData = DB::table('harian_ayam')
->join('populasi_ayam', 'harian_ayam.id_populasi', '=', 'populasi_ayam.id')
->whereIn('populasi_ayam.status_ayam', ['Proses', 'Siap Panen'])
->where('populasi_ayam.user_id', $userId)
->whereDate('harian_ayam.tanggal_input', Carbon::today())
->select(
DB::raw('SUM(harian_ayam.jumlah_ayam_sakit) as sick'),
DB::raw('SUM(harian_ayam.jumlah_ayam_mati) as dead')
)
->first();
return view('chicken-management', compact('populasi', 'harian', 'batches', 'kandang', 'monthlyData', 'todayData'));
} catch (\Exception $e) {
Log::error('Gagal memuat data: ' . $e->getMessage());
return redirect()->back()->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memuat data.',
]);
}
}
public function storePopulasi(Request $request)
{
try {
$request->validate([
'kandang_id' => 'required|exists:kandang_ayam,id',
'batchCodeSuffix' => 'required|alpha_num|size:3|unique:populasi_ayam,kode_batch',
'batchName' => 'required|string|max:255',
'docDate' => 'required|date',
'chickenQuantity' => 'required|integer|min:1',
], [
'kandang_id.required' => 'Kandang harus dipilih.',
'kandang_id.exists' => 'Kandang yang dipilih tidak valid.',
'batchCodeSuffix.required' => 'Kode populasi harus diisi.',
'batchCodeSuffix.alpha_num' => 'Kode populasi hanya boleh berisi huruf dan angka.',
'batchCodeSuffix.size' => 'Kode populasi harus terdiri dari 3 karakter.',
'batchCodeSuffix.unique' => 'Kode populasi ini sudah digunakan, silakan gunakan kode lain.',
'batchName.required' => 'Nama populasi harus diisi.',
'batchName.max' => 'Nama populasi maksimal 255 karakter.',
'docDate.required' => 'Tanggal DOC harus diisi.',
'docDate.date' => 'Format tanggal DOC tidak valid.',
'docDate.before_or_equal' => 'Tanggal DOC tidak boleh lebih dari hari ini.',
'chickenQuantity.required' => 'Jumlah ayam masuk harus diisi.',
'chickenQuantity.integer' => 'Jumlah ayam masuk harus berupa angka.',
'chickenQuantity.min' => 'Jumlah ayam masuk minimal 1 ekor.',
]);
$kandang = KandangAyam::where('id', $request->kandang_id)
->where('user_id', Auth::id())
->firstOrFail();
$totalAyamDiKandang = PopulasiAyam::where('kandang_id', $kandang->id)
->where('user_id', Auth::id())
->sum('jumlah_ayam_masuk');
if ($totalAyamDiKandang + $request->chickenQuantity > $kandang->kapasitas) {
$sisaKapasitas = $kandang->kapasitas - $totalAyamDiKandang;
return redirect()->back()->with([
'status' => 'error',
'message' => 'Jumlah ayam yang ingin ditambahkan melebihi kapasitas kandang. Kapasitas tersisa: ' .
$sisaKapasitas . ' ekor, sedangkan Anda mencoba menambahkan ' .
$request->chickenQuantity . ' ekor.'
]);
}
$batchCode = 'POPULASI-' . strtoupper($request->batchCodeSuffix);
PopulasiAyam::create([
'kode_batch' => $batchCode,
'nama_batch' => $request->batchName,
'tanggal_doc' => $request->docDate,
'jumlah_ayam_masuk' => $request->chickenQuantity,
'status_ayam' => 'Proses',
'kandang_id' => $kandang->id,
'user_id' => Auth::id(),
]);
return redirect()->route('chicken-management')->with([
'status' => 'success',
'message' => 'Data populasi Ayam berhasil disimpan.'
]);
} catch (ValidationException $e) {
$errors = $e->validator->errors()->all();
return redirect()->back()->with('status', 'error')->with('message', $errors[0]);
} catch (\Exception $e) {
Log::error('Gagal menyimpan data populasi ayam: ' . $e->getMessage());
return redirect()->route('chicken-management')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat menyimpan data populasi ayam.'
]);
}
}
public function storeHarian(Request $request)
{
try {
$request->validate([
'dailyBatchName' => 'required|exists:populasi_ayam,id',
'dailyDate' => 'required|date',
'sickChicken' => 'required|integer|min:0',
'deadChicken' => 'required|integer|min:0',
], [
'dailyBatchName.required' => 'Populasi ayam harus dipilih.',
'dailyBatchName.exists' => 'Populasi ayam yang dipilih tidak valid.',
'dailyDate.required' => 'Tanggal input harus diisi.',
'dailyDate.date' => 'Format tanggal tidak valid.',
'sickChicken.required' => 'Jumlah ayam sakit harus diisi.',
'sickChicken.integer' => 'Jumlah ayam sakit harus berupa angka.',
'sickChicken.min' => 'Jumlah ayam sakit tidak boleh negatif.',
'deadChicken.required' => 'Jumlah ayam mati harus diisi.',
'deadChicken.integer' => 'Jumlah ayam mati harus berupa angka.',
'deadChicken.min' => 'Jumlah ayam mati tidak boleh negatif.',
]);
$populasi = PopulasiAyam::where('id', $request->dailyBatchName)
->where('user_id', Auth::id())
->firstOrFail();
$totalMatiSebelumnya = HarianAyam::where('id_populasi', $populasi->id)->sum('jumlah_ayam_mati');
$totalSakitSebelumnya = HarianAyam::where('id_populasi', $populasi->id)->sum('jumlah_ayam_sakit');
$newSick = (int)$request->sickChicken;
$newDead = (int)$request->deadChicken;
$totalMatiBaru = $totalMatiSebelumnya + $newDead;
$totalSakitBaru = $totalSakitSebelumnya + $newSick;
$totalKeseluruhan = $totalMatiBaru + $totalSakitBaru;
Log::info('Validation harian ayam:', [
'populasi_id' => $populasi->id,
'jumlah_ayam_masuk' => $populasi->jumlah_ayam_masuk,
'total_mati_sebelumnya' => $totalMatiSebelumnya,
'total_sakit_sebelumnya' => $totalSakitSebelumnya,
'new_sick' => $newSick,
'new_dead' => $newDead,
'total_mati_baru' => $totalMatiBaru,
'total_sakit_baru' => $totalSakitBaru,
'total_keseluruhan' => $totalKeseluruhan
]);
if ($totalKeseluruhan > $populasi->jumlah_ayam_masuk) {
$availableCount = $populasi->jumlah_ayam_masuk - $totalMatiSebelumnya - $totalSakitSebelumnya;
return redirect()->back()->with([
'status' => 'error',
'message' => "Jumlah ayam sakit dan mati melebihi jumlah ayam tersedia. Jumlah tersedia: {$availableCount} ekor, sedangkan Anda mencoba menambahkan {$newSick} sakit dan {$newDead} mati (total: " . ($newSick + $newDead) . " ekor)."
]);
}
HarianAyam::create([
'id_populasi' => $populasi->id,
'nama_batch' => $populasi->nama_batch,
'tanggal_input' => $request->dailyDate,
'jumlah_ayam_sakit' => $newSick,
'jumlah_ayam_mati' => $newDead,
'user_id' => Auth::id(),
]);
if ($totalMatiBaru == $populasi->jumlah_ayam_masuk) {
$populasi->update(['status_ayam' => 'Sudah Panen']);
}
return redirect()->route('chicken-management')->with([
'status' => 'success',
'message' => 'Data Harian Ayam berhasil disimpan.'
]);
} catch (\Exception $e) {
Log::error('Gagal menyimpan data harian ayam: ' . $e->getMessage());
return redirect()->route('chicken-management')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat menyimpan data harian ayam: ' . $e->getMessage()
]);
}
}
public function destroyPopulasi($id)
{
try {
$populasi = PopulasiAyam::where('id', $id)
->where('user_id', Auth::id())
->firstOrFail();
$populasi->delete();
return response()->json(['success' => true, 'message' => 'Data berhasil dihapus.']);
} catch (\Exception $e) {
Log::error('Gagal menghapus data populasi: ' . $e->getMessage());
return response()->json(['success' => false, 'message' => 'Gagal menghapus data.'], 500);
}
}
public function destroyHarian($id)
{
try {
$harian = HarianAyam::where('id', $id)
->whereHas('populasiAyam', function($query) {
$query->where('user_id', Auth::id());
})
->firstOrFail();
$harian->delete();
return response()->json([
'success' => true,
'message' => 'Data Harian Ayam berhasil dihapus.'
]);
} catch (\Exception $e) {
Log::error('Gagal menghapus data harian ayam: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'Terjadi kesalahan saat menghapus data harian ayam.'
], 500);
}
}
public function updatePopulasi(Request $request, $id)
{
try {
$populasi = PopulasiAyam::where('id', $id)
->where('user_id', Auth::id())
->firstOrFail();
$validated = $request->validate([
'kandang_id' => 'required|exists:kandang_ayam,id',
'batchCodeSuffix' => 'required|alpha_num|size:3|unique:populasi_ayam,kode_batch,'.$id.',id',
'nama_batch' => 'required|string|max:255',
'tanggal_doc' => 'required|date',
'jumlah_ayam_masuk' => 'required|integer|min:1',
'status_ayam' => 'required|in:Proses,Siap Panen,Sudah Panen',
], [
'kandang_id.required' => 'Kandang harus dipilih.',
'kandang_id.exists' => 'Kandang yang dipilih tidak valid.',
'batchCodeSuffix.required' => 'Kode populasi harus diisi.',
'batchCodeSuffix.alpha_num' => 'Kode populasi hanya boleh berisi huruf dan angka.',
'batchCodeSuffix.size' => 'Kode populasi harus terdiri dari 3 karakter.',
'batchCodeSuffix.unique' => 'Kode populasi ini sudah digunakan, silakan gunakan kode lain.',
'nama_batch.required' => 'Nama populasi harus diisi.',
'nama_batch.max' => 'Nama populasi maksimal 255 karakter.',
'tanggal_doc.required' => 'Tanggal DOC harus diisi.',
'tanggal_doc.date' => 'Format tanggal DOC tidak valid.',
'jumlah_ayam_masuk.required' => 'Jumlah ayam masuk harus diisi.',
'jumlah_ayam_masuk.integer' => 'Jumlah ayam masuk harus berupa angka.',
'jumlah_ayam_masuk.min' => 'Jumlah ayam masuk minimal 1 ekor.',
'status_ayam.required' => 'Status ayam harus dipilih.',
'status_ayam.in' => 'Status ayam tidak valid.',
]);
$kandang = KandangAyam::where('id', $validated['kandang_id'])
->where('user_id', Auth::id())
->firstOrFail();
$changingCage = $populasi->kandang_id != $validated['kandang_id'];
$totalAyamDiKandangBaru = PopulasiAyam::where('kandang_id', $validated['kandang_id'])
->where('user_id', Auth::id())
->where('id', '!=', $id)
->sum('jumlah_ayam_masuk');
Log::info('Perhitungan kapasitas kandang:', [
'kandang_id' => $validated['kandang_id'],
'kapasitas_kandang' => $kandang->kapasitas,
'total_populasi_lain' => $totalAyamDiKandangBaru,
'populasi_yang_diedit_id' => $id,
'jumlah_ayam_masuk_lama' => $populasi->jumlah_ayam_masuk,
'jumlah_ayam_masuk_baru' => $validated['jumlah_ayam_masuk'],
'sisa_kapasitas' => $kandang->kapasitas - $totalAyamDiKandangBaru
]);
if ($totalAyamDiKandangBaru + $validated['jumlah_ayam_masuk'] > $kandang->kapasitas) {
$sisaKapasitas = $kandang->kapasitas - $totalAyamDiKandangBaru;
return redirect()->back()->with([
'status' => 'error',
'message' => 'Jumlah ayam yang ingin ditambahkan melebihi kapasitas kandang. Kapasitas tersisa: ' .
$sisaKapasitas . ' ekor dari ' . $kandang->kapasitas . ' total kapasitas, sedangkan Anda mencoba menambahkan ' .
$validated['jumlah_ayam_masuk'] . ' ekor.'
]);
}
$batchCode = 'POPULASI-' . strtoupper($validated['batchCodeSuffix']);
$populasi->update([
'kode_batch' => $batchCode,
'nama_batch' => $validated['nama_batch'],
'tanggal_doc' => $validated['tanggal_doc'],
'jumlah_ayam_masuk' => $validated['jumlah_ayam_masuk'],
'status_ayam' => $validated['status_ayam'],
'kandang_id' => $validated['kandang_id'],
]);
return redirect()->route('chicken-management')->with([
'status' => 'success',
'message' => 'Data populasi Ayam berhasil diperbarui.'
]);
} catch (ValidationException $e) {
$errors = $e->validator->errors()->all();
return redirect()->back()->with('status', 'error')->with('message', $errors[0]);
} catch (\Exception $e) {
Log::error('Gagal memperbarui data populasi ayam: ' . $e->getMessage());
return redirect()->route('chicken-management')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memperbarui data populasi ayam: ' . $e->getMessage()
]);
}
}
public function updateHarian(Request $request, $id)
{
try {
$validated = $request->validate([
'dailyBatchName' => 'required|exists:populasi_ayam,id',
'tanggal_input' => 'required|date',
'jumlah_ayam_sakit' => 'required|integer|min:0',
'jumlah_ayam_mati' => 'required|integer|min:0',
], [
'dailyBatchName.required' => 'Populasi ayam harus dipilih.',
'dailyBatchName.exists' => 'Populasi ayam yang dipilih tidak valid.',
'tanggal_input.required' => 'Tanggal input harus diisi.',
'tanggal_input.date' => 'Format tanggal tidak valid.',
'jumlah_ayam_sakit.required' => 'Jumlah ayam sakit harus diisi.',
'jumlah_ayam_sakit.integer' => 'Jumlah ayam sakit harus berupa angka.',
'jumlah_ayam_sakit.min' => 'Jumlah ayam sakit tidak boleh negatif.',
'jumlah_ayam_mati.required' => 'Jumlah ayam mati harus diisi.',
'jumlah_ayam_mati.integer' => 'Jumlah ayam mati harus berupa angka.',
'jumlah_ayam_mati.min' => 'Jumlah ayam mati tidak boleh negatif.',
]);
$harian = HarianAyam::where('id', $id)
->whereHas('populasiAyam', function($query) {
$query->where('user_id', Auth::id());
})
->firstOrFail();
$populasi = PopulasiAyam::where('id', $validated['dailyBatchName'])
->where('user_id', Auth::id())
->firstOrFail();
$totalMatiDariRecordLain = HarianAyam::where('id_populasi', $populasi->id)
->where('id', '!=', $id)
->sum('jumlah_ayam_mati');
$totalSakitDariRecordLain = HarianAyam::where('id_populasi', $populasi->id)
->where('id', '!=', $id)
->sum('jumlah_ayam_sakit');
$newSick = (int)$validated['jumlah_ayam_sakit'];
$newDead = (int)$validated['jumlah_ayam_mati'];
$totalTerpantau = $totalMatiDariRecordLain + $totalSakitDariRecordLain + $newSick + $newDead;
Log::info('Update harian ayam calculation:', [
'record_id' => $id,
'populasi_id' => $populasi->id,
'total_population' => $populasi->jumlah_ayam_masuk,
'total_mati_record_lain' => $totalMatiDariRecordLain,
'total_sakit_record_lain' => $totalSakitDariRecordLain,
'new_sick' => $newSick,
'new_dead' => $newDead,
'total_terpantau' => $totalTerpantau
]);
if ($totalTerpantau > $populasi->jumlah_ayam_masuk) {
$availableCount = $populasi->jumlah_ayam_masuk - $totalMatiDariRecordLain - $totalSakitDariRecordLain;
return redirect()->back()->with([
'status' => 'error',
'message' => "Jumlah ayam sakit dan mati melebihi jumlah ayam tersedia. Jumlah tersedia: {$availableCount} ekor, sedangkan Anda mencoba mencatat {$newSick} sakit dan {$newDead} mati (total: " . ($newSick + $newDead) . " ekor)."
]);
}
$harian->update([
'id_populasi' => $validated['dailyBatchName'],
'nama_batch' => $populasi->nama_batch,
'tanggal_input' => $validated['tanggal_input'],
'jumlah_ayam_sakit' => $newSick,
'jumlah_ayam_mati' => $newDead,
]);
$totalMatiSetelahUpdate = HarianAyam::where('id_populasi', $populasi->id)->sum('jumlah_ayam_mati');
if ($totalMatiSetelahUpdate == $populasi->jumlah_ayam_masuk) {
$populasi->update(['status_ayam' => 'Sudah Panen']);
}
return redirect()->route('chicken-management')->with([
'status' => 'success',
'message' => 'Data harian Ayam berhasil diperbarui.'
]);
} catch (ValidationException $e) {
$errors = $e->validator->errors()->all();
return redirect()->back()->with('status', 'error')->with('message', $errors[0]);
} catch (\Exception $e) {
Log::error('Gagal memperbarui data harian ayam: ' . $e->getMessage());
return redirect()->route('chicken-management')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memperbarui data harian ayam: ' . $e->getMessage()
]);
}
}
public function cetak($id)
{
try {
$populasi = PopulasiAyam::with('harianAyam')->findOrFail($id);
$pdf = Pdf::loadView('cetak.laporan-populasi', compact('populasi'));
return $pdf->download("Laporan_Manajemen_Ayam_{$populasi->kode_batch}.pdf");
} catch (\Exception $e) {
return redirect()->back()->with('error', 'Gagal mencetak laporan.');
}
}
public function getAvailableChickenCount($batchId, $recordId = null)
{
try {
$populasi = PopulasiAyam::where('id', $batchId)
->where('user_id', Auth::id())
->firstOrFail();
$harianQuery = HarianAyam::where('id_populasi', $batchId);
if ($recordId) {
$harianQuery->where('id', '!=', $recordId);
}
$totalSick = $harianQuery->sum('jumlah_ayam_sakit');
$totalDead = $harianQuery->sum('jumlah_ayam_mati');
$availableCount = $populasi->jumlah_ayam_masuk - $totalSick - $totalDead;
$availableCount = max(0, $availableCount);
return response()->json([
'success' => true,
'available_count' => $availableCount,
'total_population' => $populasi->jumlah_ayam_masuk,
'recorded_sick' => $totalSick,
'recorded_dead' => $totalDead
]);
} catch (\Exception $e) {
Log::error('Error retrieving chicken count: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'Error retrieving data: ' . $e->getMessage()
], 500);
}
}
public function getHarianRecord($id)
{
try {
$harian = HarianAyam::where('id', $id)
->whereHas('populasiAyam', function($query) {
$query->where('user_id', Auth::id());
})
->firstOrFail();
return response()->json([
'success' => true,
'data' => $harian
]);
} catch (\Exception $e) {
Log::error('Error retrieving harian record: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'Error retrieving record data: ' . $e->getMessage()
], 500);
}
}
}

View File

@ -0,0 +1,201 @@
<?php
namespace App\Http\Controllers;
use App\Models\CardArticle;
use App\Models\Article;
use App\Models\Tag;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\ValidationException;
class UserArticleController extends Controller
{
public function indexUserArtikel(Request $request)
{
try {
$articlePage = $request->get('article_page', 1);
$articles = Article::with('cardArticle')
->where('user_id', Auth::id())
->latest()
->paginate(4, ['*'], 'article_page', $articlePage);
$cardArticles = CardArticle::where('user_id', Auth::id())->get();
$tags = Tag::all();
$articles->appends(['article_page' => $articlePage]);
$totalArticles = Article::where('user_id', Auth::id())->count();
$todayArticles = Article::where('user_id', Auth::id())
->whereDate('created_at', now())
->count();
return view('add-article-detail', compact(
'articles',
'cardArticles',
'tags',
'totalArticles',
'todayArticles'
));
} catch (\Exception $e) {
Log::error('Gagal memuat data artikel: ' . $e->getMessage());
return redirect()->route('add-article-detail')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memuat data artikel.',
]);
}
}
public function storeUserArtikel(Request $request)
{
try {
$validated = $request->validate([
'card_id' => 'required|exists:card_articles,id',
'title' => 'required|string|max:255',
'description' => 'required|string',
'status' => 'required|string|in:Tertunda,Disetujui,Ditolak',
'tags' => 'required|array|min:1|max:3',
'tags.*' => 'exists:tags,id',
'image' => 'required|image|mimes:jpeg,png,jpg,gif|max:2048',
], [
'card_id.required' => 'Artikel grup harus dipilih',
'card_id.exists' => 'Artikel grup yang dipilih tidak valid',
'title.required' => 'Judul artikel harus diisi',
'title.max' => 'Judul artikel maksimal 255 karakter',
'description.required' => 'Deskripsi artikel harus diisi',
'tags.required' => 'Minimal satu tag harus dipilih',
'tags.array' => 'Format tag tidak valid',
'tags.min' => 'Minimal satu tag harus dipilih',
'tags.max' => 'Maksimal tiga tag yang dapat dipilih',
'tags.*.exists' => 'Tag yang dipilih tidak valid',
'image.required' => 'Gambar artikel harus diunggah',
'image.image' => 'File harus berupa gambar',
'image.mimes' => 'Format gambar harus jpeg, png, jpg, atau gif',
'image.max' => 'Ukuran gambar maksimal 2MB',
]);
$imagePath = null;
if ($request->hasFile('image')) {
$imagePath = $request->file('image')->store('articles', 'public');
}
$article = Article::create([
'card_id' => $validated['card_id'],
'title' => $validated['title'],
'description' => $validated['description'],
'status' => $validated['status'],
'image' => $imagePath,
'user_id' => Auth::id(),
]);
if (!empty($validated['tags'])) {
$article->tags()->attach($validated['tags']);
}
return redirect()->route('add-article-detail')->with([
'status' => 'success',
'message' => 'Artikel berhasil dibuat!',
]);
} catch (ValidationException $e) {
return redirect()->back()
->withErrors($e->validator)
->withInput();
} catch (\Exception $e) {
Log::error('Gagal menyimpan artikel: ' . $e->getMessage());
return redirect()->back()->withInput()->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat menyimpan artikel: ' . $e->getMessage(),
]);
}
}
public function updateUserArtikel(Request $request, $id)
{
try {
$validated = $request->validate([
'card_id' => 'required|exists:card_articles,id',
'title' => 'required|string|max:255',
'description' => 'required|string',
'status' => 'required|string|in:Tertunda,Disetujui,Ditolak',
'tags' => 'required|array|min:1|max:3',
'tags.*' => 'exists:tags,id',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
], [
'card_id.required' => 'Artikel grup harus dipilih',
'card_id.exists' => 'Artikel grup yang dipilih tidak valid',
'title.required' => 'Judul artikel harus diisi',
'title.max' => 'Judul artikel maksimal 255 karakter',
'description.required' => 'Deskripsi artikel harus diisi',
'tags.required' => 'Minimal satu tag harus dipilih',
'tags.array' => 'Format tag tidak valid',
'tags.min' => 'Minimal satu tag harus dipilih',
'tags.max' => 'Maksimal tiga tag yang dapat dipilih',
'tags.*.exists' => 'Tag yang dipilih tidak valid',
'image.image' => 'File harus berupa gambar',
'image.mimes' => 'Format gambar harus jpeg, png, jpg, atau gif',
'image.max' => 'Ukuran gambar maksimal 2MB',
]);
$article = Article::where('id', $id)
->where('user_id', Auth::id())
->firstOrFail();
$imagePath = $article->image;
if ($request->hasFile('image')) {
if ($article->image && Storage::disk('public')->exists($article->image)) {
Storage::disk('public')->delete($article->image);
}
$imagePath = $request->file('image')->store('articles', 'public');
}
$article->update([
'card_id' => $validated['card_id'],
'title' => $validated['title'],
'description' => $validated['description'],
'status' => $validated['status'],
'image' => $imagePath,
]);
if (!empty($validated['tags'])) {
$article->tags()->sync($validated['tags']);
} else {
$article->tags()->detach();
}
return redirect()->route('add-article-detail')->with([
'status' => 'success',
'message' => 'Artikel berhasil diperbarui!',
]);
} catch (ValidationException $e) {
return redirect()->back()
->withErrors($e->validator)
->withInput();
} catch (\Exception $e) {
Log::error('Gagal memperbarui artikel: ' . $e->getMessage());
return redirect()->back()->withInput()->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memperbarui artikel: ' . $e->getMessage(),
]);
}
}
public function deleteUserArtikel($id)
{
try {
$article = Article::where('id', $id)
->where('user_id', Auth::id())
->firstOrFail();
$article->tags()->detach();
if ($article->image) {
Storage::disk('public')->delete($article->image);
}
$article->delete();
return response()->json(['success' => true, 'message' => 'Artikel berhasil dihapus.']);
} catch (\Exception $e) {
Log::error('Gagal menghapus artikel: ' . $e->getMessage());
return response()->json(['success' => false, 'message' => 'Gagal menghapus artikel.'], 500);
}
}
}

View File

@ -0,0 +1,171 @@
<?php
namespace App\Http\Controllers;
use App\Models\CardArticle;
use App\Models\Article;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\ValidationException;
class UserCardArticleController extends Controller
{
public function indexUserArtikel(Request $request)
{
try {
$artikelPage = $request->get('artikel_page', 1);
$articles = CardArticle::where('user_id', Auth::id())
->withCount('articles')
->latest()
->paginate(5, ['*'], 'artikel_page', $artikelPage);
$pendingCount = Article::where('status', 'Tertunda')
->where('user_id', Auth::id())
->count();
$approvedCount = Article::where('status', 'Disetujui')
->where('user_id', Auth::id())
->count();
$rejectedCount = Article::where('status', 'Ditolak')
->where('user_id', Auth::id())
->count();
return view('add-card-article', compact('articles', 'pendingCount', 'approvedCount', 'rejectedCount'));
} catch (\Exception $e) {
Log::error('Gagal memuat data artikel: ' . $e->getMessage());
return redirect()->route('add-article')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memuat data artikel.',
]);
}
}
public function storeUserArtikel(Request $request)
{
try {
$validated = $request->validate([
'title' => 'required|string|max:255',
'description' => 'required|string',
'image' => 'required|image|mimes:jpeg,png,jpg,gif|max:2048',
], [
'title.required' => 'Judul grup artikel harus diisi.',
'title.max' => 'Judul grup artikel maksimal 255 karakter.',
'description.required' => 'Deskripsi grup artikel harus diisi.',
'image.required' => 'Gambar grup artikel harus diunggah.',
'image.image' => 'File harus berupa gambar.',
'image.mimes' => 'Format gambar harus jpeg, png, jpg, atau gif.',
'image.max' => 'Ukuran gambar maksimal 2MB.'
]);
$imagePath = null;
if ($request->hasFile('image')) {
$imagePath = $request->file('image')->store('card_articles', 'public');
}
$cardArticle = CardArticle::create([
'title' => $validated['title'],
'description' => $validated['description'],
'image' => $imagePath,
'user_id' => Auth::id(),
]);
return redirect()->route('add-article')->with([
'status' => 'success',
'message' => 'Artikel grup berhasil ditambahkan!',
]);
} catch (ValidationException $e) {
return redirect()->back()
->withErrors($e->validator)
->withInput();
} catch (\Exception $e) {
Log::error('Gagal menyimpan artikel: ' . $e->getMessage());
return redirect()->route('add-article')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat menyimpan artikel.',
]);
}
}
public function updateUserArtikel(Request $request, $id)
{
try {
$validated = $request->validate([
'title' => 'required|string|max:255',
'description' => 'required|string',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
], [
'title.required' => 'Judul grup artikel harus diisi.',
'title.max' => 'Judul grup artikel maksimal 255 karakter.',
'description.required' => 'Deskripsi grup artikel harus diisi.',
'image.image' => 'File harus berupa gambar.',
'image.mimes' => 'Format gambar harus jpeg, png, jpg, atau gif.',
'image.max' => 'Ukuran gambar maksimal 2MB.'
]);
$card = CardArticle::where('id', $id)
->where('user_id', Auth::id())
->firstOrFail();
$imagePath = $card->image;
if ($request->hasFile('image')) {
if ($imagePath && Storage::disk('public')->exists($imagePath)) {
Storage::disk('public')->delete($imagePath);
Log::info('Gambar lama berhasil dihapus: ' . $imagePath);
}
$newImagePath = $request->file('image')->store('card_articles', 'public');
Log::info('Gambar baru berhasil disimpan: ' . $newImagePath);
$imagePath = $newImagePath;
}
$card->update([
'title' => $validated['title'],
'description' => $validated['description'],
'image' => $imagePath,
]);
return redirect()->route('add-article')->with([
'status' => 'success',
'message' => 'Artikel grup berhasil diperbarui!',
]);
} catch (ValidationException $e) {
return redirect()->back()
->withErrors($e->validator)
->withInput();
} catch (ModelNotFoundException $e) {
return redirect()->route('add-article')->with([
'status' => 'error',
'message' => 'Artikel grup tidak ditemukan.',
]);
} catch (\Exception $e) {
Log::error('Gagal memperbarui artikel grup: ' . $e->getMessage());
return redirect()->route('add-article')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memperbarui artikel grup.',
]);
}
}
public function deleteUserArtikel($id)
{
try {
$cardArticle = CardArticle::where('id', $id)
->where('user_id', Auth::id())
->firstOrFail();
if ($cardArticle->image) {
Storage::disk('public')->delete($cardArticle->image);
}
$cardArticle->delete();
return response()->json(['success' => true, 'message' => 'Grup artikel berhasil dihapus.']);
} catch (\Exception $e) {
Log::error('Gagal menghapus artikel: ' . $e->getMessage());
return response()->json(['success' => false, 'message' => 'Gagal menghapus artikel.'], 500);
}
}
}

View File

@ -0,0 +1,261 @@
<?php
namespace App\Http\Controllers;
use App\Models\SubArticle;
use App\Models\Article;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\ValidationException;
class UserSubArticleController extends Controller
{
public function indexUserArtikel(Request $request)
{
try {
$subArticle = null;
$articles = Article::where('user_id', Auth::id())->get();
$selectedArticle = null;
$subArticles = collect();
$articleId = $request->query('article_id');
if ($articleId) {
$selectedArticle = Article::with(['subArticles' => function ($query) {
$query->orderBy('order_number', 'asc')
->orderBy('id', 'desc');
}])->where('user_id', Auth::id())->find($articleId);
if ($selectedArticle) {
$subArticles = $selectedArticle->subArticles;
}
}
$editId = $request->query('edit_id');
if ($editId) {
$subArticle = SubArticle::where('id', $editId)
->whereHas('article', function ($query) {
$query->where('user_id', Auth::id());
})
->first();
}
return view('add-article-sub', compact('subArticles', 'articles', 'selectedArticle', 'subArticle'));
} catch (\Exception $e) {
Log::error('Gagal memuat sub-artikel: ' . $e->getMessage());
return redirect()->route('add-article-sub')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memuat data sub-artikel.',
]);
}
}
public function storeMultipleSubArticles(Request $request)
{
try {
$validated = $request->validate([
'article_id' => 'required|exists:articles,id',
'sub_articles' => 'required|array|min:1',
'sub_articles.*.title' => 'required|string|max:255',
'sub_articles.*.content' => 'required|string',
'sub_articles.*.order_number' => 'required|integer|min:1',
'sub_articles.*.image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
'sub_articles.*.remove_image' => 'nullable|in:0,1',
], [
'article_id.required' => 'Artikel induk harus dipilih.',
'article_id.exists' => 'Artikel induk tidak ditemukan.',
'sub_articles.required' => 'Minimal satu sub artikel harus diisi.',
'sub_articles.min' => 'Minimal satu sub artikel harus diisi.',
'sub_articles.*.title.required' => 'Judul sub artikel harus diisi.',
'sub_articles.*.title.max' => 'Judul sub artikel maksimal 255 karakter.',
'sub_articles.*.content.required' => 'Konten sub artikel harus diisi.',
'sub_articles.*.order_number.required' => 'Urutan sub artikel harus diisi.',
'sub_articles.*.order_number.integer' => 'Urutan sub artikel harus berupa angka.',
'sub_articles.*.order_number.min' => 'Urutan sub artikel minimal 1.',
'sub_articles.*.image.image' => 'File harus berupa gambar.',
'sub_articles.*.image.mimes' => 'Format gambar harus jpeg, png, jpg, atau gif.',
'sub_articles.*.image.max' => 'Ukuran gambar maksimal 2MB.',
]);
$article = Article::where('id', $validated['article_id'])
->where('user_id', Auth::id())
->firstOrFail();
$subArticlesData = [];
foreach ($validated['sub_articles'] as $subArticle) {
$imagePath = null;
if (isset($subArticle['image']) && $subArticle['image'] instanceof \Illuminate\Http\UploadedFile) {
$imagePath = $subArticle['image']->store('sub_articles', 'public');
}
$subArticlesData[] = [
'article_id' => $validated['article_id'],
'title' => $subArticle['title'],
'content' => $subArticle['content'],
'order_number' => $subArticle['order_number'],
'image' => $imagePath,
'user_id' => Auth::id(),
'created_at' => now(),
'updated_at' => now(),
];
}
SubArticle::insert($subArticlesData);
return redirect()->route('add-article-sub', ['article_id' => $validated['article_id']])->with([
'status' => 'success',
'message' => 'Semua sub-artikel berhasil disimpan!',
]);
} catch (ValidationException $e) {
return redirect()->back()->withErrors($e->validator)->withInput();
} catch (\Exception $e) {
Log::error('Gagal menyimpan sub-artikel: ' . $e->getMessage());
return redirect()->route('add-article-sub')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat menyimpan sub-artikel: ' . $e->getMessage(),
]);
}
}
public function updateUserArtikel(Request $request, $id)
{
try {
$subArticle = SubArticle::where('id', $id)
->whereHas('article', function ($query) {
$query->where('user_id', Auth::id());
})
->firstOrFail();
$validated = $request->validate([
'article_id' => 'required|exists:articles,id',
'sub_articles' => 'required|array',
'sub_articles.*.title' => 'required|string|max:255',
'sub_articles.*.content' => 'required|string',
'sub_articles.*.order_number' => 'required|integer|min:1',
'sub_articles.*.image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
'sub_articles.*.remove_image' => 'nullable|in:0,1',
], [
'article_id.required' => 'Artikel induk harus dipilih.',
'article_id.exists' => 'Artikel induk tidak ditemukan.',
'sub_articles.required' => 'Data sub artikel harus diisi.',
'sub_articles.*.title.required' => 'Judul sub artikel harus diisi.',
'sub_articles.*.title.max' => 'Judul sub artikel maksimal 255 karakter.',
'sub_articles.*.content.required' => 'Konten sub artikel harus diisi.',
'sub_articles.*.order_number.required' => 'Urutan sub artikel harus diisi.',
'sub_articles.*.order_number.integer' => 'Urutan sub artikel harus berupa angka.',
'sub_articles.*.order_number.min' => 'Urutan sub artikel minimal 1.',
'sub_articles.*.image.image' => 'File harus berupa gambar.',
'sub_articles.*.image.mimes' => 'Format gambar harus jpeg, png, jpg, atau gif.',
'sub_articles.*.image.max' => 'Ukuran gambar maksimal 2MB.',
]);
$removeImage = $request->input('sub_articles.0.remove_image', '0');
$imagePath = $subArticle->image;
if ($removeImage === '1') {
if ($imagePath && Storage::disk('public')->exists($imagePath)) {
Storage::disk('public')->delete($imagePath);
}
$imagePath = null;
}
elseif ($request->hasFile('sub_articles.0.image')) {
if ($imagePath && Storage::disk('public')->exists($imagePath)) {
Storage::disk('public')->delete($imagePath);
}
$imagePath = $request->file('sub_articles.0.image')->store('sub_articles', 'public');
}
$subArticle->update([
'article_id' => $validated['article_id'],
'title' => $validated['sub_articles'][0]['title'],
'content' => $validated['sub_articles'][0]['content'],
'order_number' => $validated['sub_articles'][0]['order_number'],
'image' => $imagePath,
]);
return redirect()->route('add-article-sub', ['article_id' => $validated['article_id']])->with([
'status' => 'success',
'message' => 'Sub-artikel berhasil diperbarui!',
]);
} catch (ValidationException $e) {
return redirect()->back()->withErrors($e->validator)->withInput();
} catch (\Exception $e) {
Log::error('Gagal memperbarui sub-artikel: ' . $e->getMessage());
return redirect()->route('add-article-sub')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat memperbarui sub-artikel: ' . $e->getMessage(),
]);
}
}
public function editUserArtikel($id)
{
try {
$subArticle = SubArticle::where('id', $id)
->whereHas('article', function ($query) {
$query->where('user_id', Auth::id());
})
->firstOrFail();
$selectedArticle = $subArticle->article;
$articles = Article::where('user_id', Auth::id())->get();
return view('add-article-sub', compact('subArticle', 'selectedArticle', 'articles'));
} catch (\Exception $e) {
return redirect()->route('add-article-sub')->with([
'status' => 'error',
'message' => 'Sub-artikel tidak ditemukan atau tidak dapat diakses.',
]);
}
}
public function deleteUserArtikel($id)
{
try {
$subArticle = SubArticle::where('id', $id)
->whereHas('article', function ($query) {
$query->where('user_id', Auth::id());
})
->firstOrFail();
$articleId = $subArticle->article_id;
if ($subArticle->image) {
Storage::disk('public')->delete($subArticle->image);
}
$subArticle->delete();
if (request()->ajax() || request()->wantsJson()) {
return response()->json([
'success' => true,
'message' => 'Sub-artikel berhasil dihapus!'
]);
}
return redirect()->route('add-article-sub', ['article_id' => $articleId])->with([
'status' => 'success',
'message' => 'Sub-artikel berhasil dihapus!',
]);
} catch (\Exception $e) {
Log::error('Gagal menghapus sub-artikel: ' . $e->getMessage());
if (request()->ajax() || request()->wantsJson()) {
return response()->json([
'success' => false,
'message' => 'Terjadi kesalahan saat menghapus sub-artikel: ' . $e->getMessage()
], 500);
}
return redirect()->route('add-article-sub')->with([
'status' => 'error',
'message' => 'Terjadi kesalahan saat menghapus sub-artikel.',
]);
}
}
}

70
app/Http/Kernel.php Normal file
View File

@ -0,0 +1,70 @@
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array<int, class-string|string>
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
/**
* The application's route middleware groups.
*
* @var array<string, array<int, class-string|string>>
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* The application's middleware aliases.
*
* Aliases may be used instead of class names to conveniently assign middleware to routes and groups.
*
* @var array<string, class-string|string>
*/
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'admin' => \App\Http\Middleware\AdminMiddleware::class,
'user' => \App\Http\Middleware\UserMiddleware::class,
];
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class AdminMiddleware
{
public function handle(Request $request, Closure $next)
{
if (Auth::check() && Auth::user()->role === 'admin') {
return $next($request);
}
return redirect()->route('dashboard'); // Redirect user biasa ke dashboard
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Http\Request;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*/
protected function redirectTo(Request $request): ?string
{
return $request->expectsJson() ? null : route('login');
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.
*
* @var array<int, string>
*/
protected $except = [
//
];
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
class PreventRequestsDuringMaintenance extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
* @var array<int, string>
*/
protected $except = [
//
];
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
use App\Providers\RouteServiceProvider;
class RedirectIfAuthenticated
{
public function handle(Request $request, Closure $next, ...$guards): Response
{
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
return redirect()->intended(RouteServiceProvider::HOME);
}
}
return $next($request);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
/**
* The names of the attributes that should not be trimmed.
*
* @var array<int, string>
*/
protected $except = [
'current_password',
'password',
'password_confirmation',
];
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustHosts as Middleware;
class TrustHosts extends Middleware
{
/**
* Get the host patterns that should be trusted.
*
* @return array<int, string|null>
*/
public function hosts(): array
{
return [
$this->allSubdomainsOfApplicationUrl(),
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array<int, string>|string|null
*/
protected $proxies;
/**
* The headers that should be used to detect proxies.
*
* @var int
*/
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Facades\Auth;
class UserMiddleware
{
public function handle(Request $request, Closure $next)
{
if (Auth::check() && Auth::user()->role === 'user') {
return $next($request);
}
return redirect()->route('dashboard'); // Redirect jika bukan user
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
class ValidateSignature extends Middleware
{
/**
* The names of the query string parameters that should be ignored.
*
* @var array<int, string>
*/
protected $except = [
// 'fbclid',
// 'utm_campaign',
// 'utm_content',
// 'utm_medium',
// 'utm_source',
// 'utm_term',
];
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array<int, string>
*/
protected $except = [
//
];
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreArticleRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreArticleTagRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreCardArticleRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreHarianAyamRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreKandangAyamRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePakanRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePendapatanRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePengeluaranRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePenggunaanPakanRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePopulasiAyamRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreSubArticleRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreTagRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateArticleRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateArticleTagRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateCardArticleRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateHarianAyamRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateKandangAyamRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdatePakanRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdatePendapatanRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdatePengeluaranRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdatePenggunaanPakanRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdatePopulasiAyamRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateSubArticleRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateTagRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Livewire\Actions;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
class Logout
{
public function __invoke(): void
{
Auth::guard('web')->logout();
Session::invalidate();
Session::regenerateToken();
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace App\Livewire\Forms;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Livewire\Attributes\Validate;
use Livewire\Form;
class LoginForm extends Form
{
#[Validate('required|email:rfc,dns|string|max:255')]
public string $email = '';
#[Validate('required|string|min:8')]
public string $password = '';
#[Validate('boolean')]
public bool $remember = false;
protected function messages(): array
{
return [
'email.required' => 'Email wajib diisi.',
'email.email' => 'Format email tidak valid.',
'email.max' => 'Email tidak boleh lebih dari 255 karakter.',
'password.required' => 'Kata sandi wajib diisi.',
'password.min' => 'Kata sandi minimal terdiri dari 8 karakter.',
];
}
/**
*
* @throws \Illuminate\Validation\ValidationException
*/
public function authenticate(): void
{
$this->validate();
$this->ensureIsNotRateLimited();
$user = \App\Models\User::where('email', $this->email)->first();
if (!$user) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'form.email' => 'Email tidak terdaftar.',
]);
}
if (!Auth::attempt($this->only(['email', 'password']), $this->remember)) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'form.password' => 'Kata sandi yang Anda masukkan salah.',
]);
}
RateLimiter::clear($this->throttleKey());
}
protected function ensureIsNotRateLimited(): void
{
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
return;
}
event(new Lockout(request()));
$seconds = RateLimiter::availableIn($this->throttleKey());
throw ValidationException::withMessages([
'form.email' => trans('auth.throttle', [
'seconds' => $seconds,
'minutes' => ceil($seconds / 60),
]),
]);
}
protected function throttleKey(): string
{
return Str::transliterate(Str::lower($this->email).'|'.request()->ip());
}
}

View File

@ -0,0 +1,86 @@
<?php
namespace App\Livewire\Forum;
use Livewire\Attributes\On;
use Livewire\Component;
use App\Models\Comment;
use App\Models\Topic;
class CommentList extends Component
{
public $topic;
public $newComment = '';
public $replyingTo = null;
public $parentComment = null;
protected $rules = [
'newComment' => 'required|min:3|max:500'
];
protected $messages = [
'newComment.required' => 'Komentar tidak boleh kosong',
'newComment.min' => 'Komentar minimal 3 karakter',
'newComment.max' => 'Komentar maksimsal 500 karakter',
];
#[On('commentAdded')]
public function refreshComments()
{
$this->topic = Topic::with(['comments' => function($query) {
$query->with(['user', 'replies.user']);
}])->find($this->topic->id);
}
public function mount(Topic $topic)
{
$this->topic = $topic;
}
public function startReply($commentId)
{
$this->replyingTo = $commentId;
$this->parentComment = Comment::find($commentId);
}
public function cancelReply()
{
$this->replyingTo = null;
$this->parentComment = null;
}
public function addComment()
{
$this->validate();
$comment = Comment::create([
'topic_id' => $this->topic->id,
'user_id' => auth()->id(),
'content' => $this->newComment,
'parent_id' => $this->replyingTo
]);
$this->reset('newComment');
$this->cancelReply();
return redirect(request()->header('Referer'));
}
protected $listeners = [
'commentAdded' => '$refresh',
'refreshComponent' => '$refresh'
];
public function render()
{
$comments = $this->topic->comments()
->with(['user', 'likes', 'replies.user', 'replies.likes'])
->whereNull('parent_id')
->latest()
->get();
return view('livewire.forum.comment-list', [
'comments' => $comments
]);
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Livewire\Forum;
use Livewire\Component;
use App\Models\Topic;
class CreateTopic extends Component
{
public $title;
public $content;
protected $rules = [
'title' => 'required|min:3',
'content' => 'required|min:10',
];
protected $messages = [
'title.required' => 'Judul topik tidak boleh kosong',
'title.min' => 'Judul topik minimal 3 karakter',
'content.required' => 'Isi topik tidak boleh kosong',
'content.min' => 'Isi topik minimal 10 karakter'
];
public function submit()
{
$validated = $this->validate();
$topic = Topic::create([
'title' => $this->title,
'content' => $this->content,
'user_id' => auth()->id(),
]);
session()->flash('status', 'success');
session()->flash('message', 'Topik berhasil dibuat!');
return redirect()->route('forum.index');
}
public function render()
{
return view('livewire.forum.create-topic')
->extends('layouts.dashboard-layout')
->section('content');
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Livewire\Forum;
use Livewire\Component;
use Illuminate\Support\Facades\Log;
class LikeButton extends Component
{
public $model;
public $likesCount;
public $isLiked;
public function mount($model)
{
$this->model = $model;
$this->updateLikeStatus();
}
protected function updateLikeStatus()
{
$this->likesCount = $this->model->likes()->count();
$this->isLiked = auth()->check()
? $this->model->likes()->where('user_id', auth()->id())->exists()
: false;
}
public function toggleLike()
{
if (!auth()->check()) {
return redirect()->route('login');
}
try {
$existingLike = $this->model->likes()
->where('user_id', auth()->id())
->first();
if ($existingLike) {
$existingLike->delete();
} else {
$this->model->likes()->create([
'user_id' => auth()->id()
]);
}
$this->updateLikeStatus();
} catch (\Exception $e) {
Log::error('Like Toggle Error', [
'error' => $e->getMessage(),
'model_type' => get_class($this->model),
'model_id' => $this->model->id,
'user_id' => auth()->id()
]);
session()->flash('error', 'Unable to process like/unlike');
}
}
public function render()
{
return view('livewire.forum.like-button');
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Livewire\Forum;
use App\Models\Topic;
use Livewire\Component;
use Livewire\WithPagination;
use Livewire\Attributes\Url;
class TopicList extends Component
{
use WithPagination;
protected $paginationTheme = 'tailwind';
#[Url]
public $search = '';
protected $listeners = ['topicCreated' => '$refresh'];
public function mount()
{
$this->search = request()->get('search', '');
$this->dispatch('listen-for-new-topic');
}
public function setSearch($value)
{
$this->search = $value;
$this->resetPage();
}
public function render()
{
$topics = Topic::with('user')
->withCount('comments', 'likes')
->when($this->search, function($query) {
return $query->where('title', 'like', '%' . $this->search . '%');
})
->latest()
->paginate(10);
return view('livewire.forum.topic-list', [
'topics' => $topics
]);
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Livewire\Forum;
use App\Models\Topic;
use Livewire\Component;
use Illuminate\Support\Facades\Log;
class TopicView extends Component
{
public $topic;
public function mount($topic)
{
try {
$this->topic = is_numeric($topic)
? Topic::findOrFail($topic)
: $topic;
Log::info('Topic loaded', [
'topic_id' => $this->topic->id,
'topic_title' => $this->topic->title
]);
} catch (\Exception $e) {
Log::error('Topic loading error', [
'error' => $e->getMessage(),
'topic' => $topic
]);
session()->flash('error', 'Topic not found');
return redirect()->route('forum.index');
}
}
public function render()
{
$topic = Topic::with(['user', 'comments'])
->findOrFail($this->topic->id);
return view('livewire.forum.topic-view');
}
}

41
app/Models/Article.php Normal file
View File

@ -0,0 +1,41 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Article extends Model
{
protected $fillable = [
'card_id',
'title',
'catatan',
'description',
'image',
'status',
'user_id',];
public function cardArticle(): BelongsTo
{
return $this->belongsTo(CardArticle::class, 'card_id');
}
public function subArticles(): HasMany
{
return $this->hasMany(SubArticle::class, 'article_id');
}
public function tags()
{
return $this->belongsToMany(Tag::class, 'article_tags');
}
public function user()
{
return $this->belongsTo(User::class);
}
}

11
app/Models/ArticleTag.php Normal file
View File

@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ArticleTag extends Model
{
use HasFactory;
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany; // Tambahkan namespace ini!
class CardArticle extends Model
{
use HasFactory;
protected $table = 'card_articles';
protected $fillable = [
'title',
'description',
'image',
'user_id',
];
public function articles(): HasMany
{
return $this->hasMany(Article::class, 'card_id');
}
public function user()
{
return $this->belongsTo(User::class);
}
protected static function booted()
{
static::deleting(function ($card) {
// Hapus semua Articles terkait
$card->articles()->each(function ($article) {
// Hapus semua SubArticles terkait dengan Article
$article->subArticles()->delete();
// Hapus Article
$article->delete();
});
});
}
}

38
app/Models/Comment.php Normal file
View File

@ -0,0 +1,38 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
use HasFactory;
protected $fillable = ['user_id', 'topic_id', 'content', 'parent_id'];
public function user()
{
return $this->belongsTo(User::class);
}
public function topic()
{
return $this->belongsTo(Topic::class);
}
public function likes()
{
return $this->morphMany(Like::class, 'likeable');
}
public function parent()
{
return $this->belongsTo(Comment::class, 'parent_id');
}
public function replies()
{
return $this->hasMany(Comment::class, 'parent_id');
}
}

40
app/Models/HarianAyam.php Normal file
View File

@ -0,0 +1,40 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class HarianAyam extends Model
{
use HasFactory;
protected $table = 'harian_ayam';
protected $fillable = [
'id_populasi',
'nama_batch',
'tanggal_input',
'jumlah_ayam_mati',
'jumlah_ayam_sakit',
'user_id',
];
public function populasiAyam()
{
return $this->belongsTo(PopulasiAyam::class, 'id_populasi');
}
public function user()
{
return $this->belongsTo(User::class);
}
protected static function booted()
{
static::creating(function ($harian) {
$harian->nama_batch = $harian->populasiAyam->nama_batch;
});
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class KandangAyam extends Model
{
use HasFactory;
protected $table = 'kandang_ayam';
protected $fillable = [
'nama_kandang',
'kapasitas',
'status_kandang',
'user_id',
];
const STATUS_AKTIF = 'Aktif';
const STATUS_TIDAK_AKTIF = 'Tidak Aktif';
public static function getStatusOptions()
{
return [self::STATUS_AKTIF, self::STATUS_TIDAK_AKTIF];
}
public function populasiAyam()
{
return $this->hasMany(PopulasiAyam::class, 'kandang_id');
}
public function user()
{
return $this->belongsTo(User::class);
}
}

24
app/Models/Like.php Normal file
View File

@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Like extends Model
{
use HasFactory;
protected $fillable = ['user_id', 'likeable_type', 'likeable_id'];
// Polymorphic relationship
public function likeable()
{
return $this->morphTo();
}
public function user()
{
return $this->belongsTo(User::class);
}
}

32
app/Models/Pakan.php Normal file
View File

@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Pakan extends Model
{
use HasFactory;
protected $table = 'pakan';
protected $fillable = [
'nama_pakan',
'jenis_pakan',
'berat',
'tanggal_masuk',
'harga_per_kg',
'user_id',
];
// Relasi dengan PenggunaanPakan
public function penggunaan()
{
return $this->hasMany(PenggunaanPakan::class, 'pakan_id');
}
public function user()
{
return $this->belongsTo(User::class);
}
}

40
app/Models/Pendapatan.php Normal file
View File

@ -0,0 +1,40 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Pendapatan extends Model
{
use HasFactory;
protected $table = 'pendapatan';
protected $fillable = [
'kategori',
'jumlah',
'satuan',
'harga_per_satuan',
'total_pendapatan',
'tanggal_transaksi',
'nama_pembeli',
'nama_perusahaan',
'user_id',
];
public static function boot()
{
parent::boot();
static::creating(function ($pendapatan) {
$pendapatan->total_pendapatan = $pendapatan->jumlah * $pendapatan->harga_per_satuan;
});
static::updating(function ($pendapatan) {
$pendapatan->total_pendapatan = $pendapatan->jumlah * $pendapatan->harga_per_satuan;
});
}
public function user()
{
return $this->belongsTo(User::class);
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Pengeluaran extends Model
{
use HasFactory;
protected $table = 'pengeluaran';
protected $fillable = [
'category',
'description',
'jumlah',
'satuan',
'harga_per_satuan',
'total_biaya',
'supplier',
'tanggal_pembelian',
'user_id',
];
public static function boot()
{
parent::boot();
static::creating(function ($pengeluaran) {
$pengeluaran->total_biaya = $pengeluaran->jumlah * $pengeluaran->harga_per_satuan;
});
static::updating(function ($pengeluaran) {
$pengeluaran->total_biaya = $pengeluaran->jumlah * $pengeluaran->harga_per_satuan;
});
}
public function user()
{
return $this->belongsTo(User::class);
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PenggunaanPakan extends Model
{
protected $table = 'penggunaan_pakan';
use HasFactory;
protected $fillable = [
'pakan_id',
'tanggal_pakai',
'jumlah_pakai',
'user_id',
];
// Relasi dengan Pakan
public function pakan()
{
return $this->belongsTo(Pakan::class, 'pakan_id');
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PopulasiAyam extends Model
{
use HasFactory;
protected $table = 'populasi_ayam';
protected $fillable = [
'kandang_id',
'kode_batch',
'nama_batch',
'tanggal_doc',
'jumlah_ayam_masuk',
'status_ayam',
'user_id',
];
public function harianAyam()
{
return $this->hasMany(HarianAyam::class, 'id_populasi');
}
public function kandang()
{
return $this->belongsTo(KandangAyam::class, 'kandang_id');
}
public function user()
{
return $this->belongsTo(User::class);
}
protected static function booted()
{
static::updated(function ($populasi) {
// Perbarui field nama_batch di semua record harian ayam yang terkait
$populasi->harianAyam()->update(['nama_batch' => $populasi->nama_batch]);
});
}
}

28
app/Models/SubArticle.php Normal file
View File

@ -0,0 +1,28 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class SubArticle extends Model
{
protected $fillable = [
'article_id',
'title',
'image',
'content',
'order_number',
'user_id',
];
public function article(): BelongsTo
{
return $this->belongsTo(Article::class, 'article_id');
}
public function user()
{
return $this->belongsTo(User::class);
}
}

15
app/Models/Tag.php Normal file
View File

@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
protected $fillable = ['name'];
public function articles()
{
return $this->belongsToMany(Article::class, 'article_tags');
}
}

28
app/Models/Topic.php Normal file
View File

@ -0,0 +1,28 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Topic extends Model
{
use HasFactory;
protected $table = 'topics';
protected $fillable = ['user_id', 'title', 'content', 'is_pinned'];
public function user()
{
return $this->belongsTo(User::class);
}
public function comments()
{
return $this->hasMany(Comment::class);
}
public function likes()
{
return $this->morphMany(Like::class, 'likeable');
}
}

61
app/Models/User.php Normal file
View File

@ -0,0 +1,61 @@
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'email',
'password',
'role',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
public function topics()
{
return $this->hasMany(Topic::class);
}
public function comments()
{
return $this->hasMany(Comment::class);
}
public function likes()
{
return $this->hasMany(Like::class);
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Policies;
use App\Models\Article;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class ArticlePolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
//
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Article $article): bool
{
//
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
//
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Article $article): bool
{
//
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Article $article): bool
{
//
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Article $article): bool
{
//
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Article $article): bool
{
//
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Policies;
use App\Models\ArticleTag;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class ArticleTagPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
//
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, ArticleTag $articleTag): bool
{
//
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
//
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, ArticleTag $articleTag): bool
{
//
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, ArticleTag $articleTag): bool
{
//
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, ArticleTag $articleTag): bool
{
//
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, ArticleTag $articleTag): bool
{
//
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Policies;
use App\Models\CardArticle;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class CardArticlePolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
//
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, CardArticle $cardArticle): bool
{
//
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
//
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, CardArticle $cardArticle): bool
{
//
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, CardArticle $cardArticle): bool
{
//
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, CardArticle $cardArticle): bool
{
//
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, CardArticle $cardArticle): bool
{
//
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Policies;
use App\Models\HarianAyam;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class HarianAyamPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
//
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, HarianAyam $harianAyam): bool
{
//
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
//
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, HarianAyam $harianAyam): bool
{
//
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, HarianAyam $harianAyam): bool
{
//
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, HarianAyam $harianAyam): bool
{
//
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, HarianAyam $harianAyam): bool
{
//
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Policies;
use App\Models\KandangAyam;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class KandangAyamPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
//
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, KandangAyam $kandangAyam): bool
{
//
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
//
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, KandangAyam $kandangAyam): bool
{
//
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, KandangAyam $kandangAyam): bool
{
//
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, KandangAyam $kandangAyam): bool
{
//
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, KandangAyam $kandangAyam): bool
{
//
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Policies;
use App\Models\Pakan;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class PakanPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
//
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Pakan $pakan): bool
{
//
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
//
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Pakan $pakan): bool
{
//
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Pakan $pakan): bool
{
//
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Pakan $pakan): bool
{
//
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Pakan $pakan): bool
{
//
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Policies;
use App\Models\Pendapatan;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class PendapatanPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
//
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Pendapatan $pendapatan): bool
{
//
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
//
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Pendapatan $pendapatan): bool
{
//
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Pendapatan $pendapatan): bool
{
//
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Pendapatan $pendapatan): bool
{
//
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Pendapatan $pendapatan): bool
{
//
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Policies;
use App\Models\Pengeluaran;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class PengeluaranPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
//
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Pengeluaran $pengeluaran): bool
{
//
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
//
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Pengeluaran $pengeluaran): bool
{
//
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Pengeluaran $pengeluaran): bool
{
//
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Pengeluaran $pengeluaran): bool
{
//
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Pengeluaran $pengeluaran): bool
{
//
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Policies;
use App\Models\PenggunaanPakan;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class PenggunaanPakanPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
//
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, PenggunaanPakan $penggunaanPakan): bool
{
//
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
//
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, PenggunaanPakan $penggunaanPakan): bool
{
//
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, PenggunaanPakan $penggunaanPakan): bool
{
//
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, PenggunaanPakan $penggunaanPakan): bool
{
//
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, PenggunaanPakan $penggunaanPakan): bool
{
//
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Policies;
use App\Models\PopulasiAyam;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class PopulasiAyamPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
//
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, PopulasiAyam $populasiAyam): bool
{
//
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
//
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, PopulasiAyam $populasiAyam): bool
{
//
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, PopulasiAyam $populasiAyam): bool
{
//
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, PopulasiAyam $populasiAyam): bool
{
//
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, PopulasiAyam $populasiAyam): bool
{
//
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Policies;
use App\Models\SubArticle;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class SubArticlePolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
//
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, SubArticle $subArticle): bool
{
//
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
//
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, SubArticle $subArticle): bool
{
//
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, SubArticle $subArticle): bool
{
//
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, SubArticle $subArticle): bool
{
//
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, SubArticle $subArticle): bool
{
//
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Policies;
use App\Models\Tag;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class TagPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
//
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Tag $tag): bool
{
//
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
//
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Tag $tag): bool
{
//
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Tag $tag): bool
{
//
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Tag $tag): bool
{
//
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Tag $tag): bool
{
//
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
//
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Providers;
// use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The model to policy mappings for the application.
*
* @var array<class-string, class-string>
*/
protected $policies = [
//
];
/**
* Register any authentication / authorization services.
*/
public function boot(): void
{
//
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Broadcast::routes();
require base_path('routes/channels.php');
}
}

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