done payroll revision

pull/1/head
Aji Kamaludin 3 years ago
parent b0445011d1
commit c6b63f7531
No known key found for this signature in database
GPG Key ID: 670E1F26AD5A8099

@ -2,7 +2,10 @@
namespace App\Http\Controllers;
use DB;
use App\Models\Product;
use App\Models\Payroll;
use App\Models\PayrollItem;
use Illuminate\Http\Request;
class PayrollController extends Controller
@ -16,12 +19,43 @@ class PayrollController extends Controller
{
$query = Payroll::with('employee');
$startDate = now()->startOfMonth()->toDateString();
$endDate = now()->endOfMonth()->toDateString();
if ($request->startDate != null && $request->endDate != null) {
$query->whereBetween('date', [$request->startDate, $request->endDate]);
$startDate = $request->startDate;
$endDate = $request->endDate;
} else {
$query->whereBetween('date', [$startDate, $endDate]);
}
return inertia('Payrolls', [
return inertia('Payrolls/Index', [
'payrolls' => $query->orderBy('date', 'desc')->paginate(10),
'_startDate' => $startDate,
'_endDate' => $endDate
]);
}
/**
* Created resource in storage.
*
* @return \Illuminate\Http\Response
*/
public function create(Request $request)
{
$query = Product::orderBy('id', 'desc');
if ($request->q != null) {
$query = Product::where('name', 'like', '%'.$request->q.'%')
->orWhere('description', 'like', '%'.$request->q.'%')
->orderBy('created_at', 'desc');
}
return inertia('Payrolls/Create', [
'products' => $query->paginate(8),
'_search' => $request->q ? $request->q : '',
'_page' => $request->page ? $request->page : 1,
]);
}
@ -36,27 +70,68 @@ class PayrollController extends Controller
$request->validate([
'employee_id' => 'required|exists:employees,id',
'date' => 'required|date',
'amount' => 'required|numeric',
'cuts' => 'nullable|numeric',
'bonus' => 'nullable|numeric',
'item_count' => 'nullable|numeric',
'items' => 'required|array',
'items.*.product_id' => 'required|exists:products,id',
'items.*.quantity' => 'required|numeric',
'items.*.price' => 'required|numeric'
]);
$recived = ($request->amount + $request->bonus) - $request->cuts;
$items = collect($request->items)->map(function ($item) {
return [
'product_id' => $item['product_id'],
'quantity' => $item['quantity'],
'price' => $item['price'],
'subtotal' => $item['quantity'] * $item['price']
];
});
Payroll::create([
$amount = $items->sum('subtotal');
$itemCount = $items->sum('quantity');
$recived = ($amount + $request->bonus) - $request->cuts;
DB::beginTransaction();
$payroll = Payroll::create([
'employee_id' => $request->employee_id,
'date' => $request->date,
'amount' => $request->amount,
'amount' => $amount,
'cuts' => $request->cuts,
'bonus' => $request->bonus,
'item_count' => $request->item_count,
'item_count' => $itemCount,
'recived' => $recived,
]);
$payroll->items()->saveMany($items->mapInto(PayrollItem::class));
DB::commit();
return redirect()->route('payrolls.index');
}
/**
* Edit resource in storage.
*
* @return \Illuminate\Http\Response
*/
public function edit(Request $request, Payroll $payroll)
{
$query = Product::orderBy('id', 'desc');
if ($request->q != null) {
$query = Product::where('name', 'like', '%'.$request->q.'%')
->orWhere('description', 'like', '%'.$request->q.'%')
->orderBy('created_at', 'desc');
}
return inertia('Payrolls/Edit', [
'payroll' => $payroll->load(['items.product', 'employee']),
'products' => $query->paginate(8),
'_search' => $request->q ? $request->q : '',
'_page' => $request->page ? $request->page : 1,
]);
}
/**
* Update the specified resource in storage.
*
@ -69,24 +144,43 @@ class PayrollController extends Controller
$request->validate([
'employee_id' => 'required|exists:employees,id',
'date' => 'required|date',
'amount' => 'required|numeric',
'cuts' => 'nullable|numeric',
'bonus' => 'nullable|numeric',
'item_count' => 'nullable|numeric',
'items' => 'required|array',
'items.*.product_id' => 'required|exists:products,id',
'items.*.quantity' => 'required|numeric',
'items.*.price' => 'required|numeric'
]);
$recived = ($request->amount + $request->bonus) - $request->cuts;
$items = collect($request->items)->map(function ($item) {
return [
'product_id' => $item['product_id'],
'quantity' => $item['quantity'],
'price' => $item['price'],
'subtotal' => $item['quantity'] * $item['price']
];
});
$amount = $items->sum('subtotal');
$itemCount = $items->sum('quantity');
$recived = ($amount + $request->bonus) - $request->cuts;
DB::beginTransaction();
$payroll->update([
'employee_id' => $request->employee_id,
'date' => $request->date,
'amount' => $request->amount,
'amount' => $amount,
'cuts' => $request->cuts,
'bonus' => $request->bonus,
'item_count' => $request->item_count,
'item_count' => $itemCount,
'recived' => $recived,
]);
$payroll->items()->delete();
$payroll->items()->saveMany($items->mapInto(PayrollItem::class));
DB::commit();
return redirect()->route('payrolls.index');
}
@ -98,7 +192,11 @@ class PayrollController extends Controller
*/
public function destroy(Payroll $payroll)
{
DB::transaction(function () use ($payroll) {
$payroll->items()->delete();
$payroll->delete();
});
return redirect()->route('payrolls.index');
}
}

@ -16,13 +16,14 @@ class ProductController extends Controller
public function index(Request $request)
{
if ($request->q != null) {
$query = Product::where('name', 'like', '%'.$request->q.'%')->orWhere('description', 'like', '%'.$request->q.'%')->orderBy('id');
$query = Product::where('name', 'like', '%'.$request->q.'%')->orWhere('description', 'like', '%'.$request->q.'%')->orderBy('created_at', 'desc');
} else {
$query = Product::orderBy('id');
$query = Product::orderBy('created_at', 'desc');
}
return inertia('Products', [
'products' => $query->paginate(10),
'_search' => $request->q ? $request->q : ''
]);
}

@ -0,0 +1,31 @@
<?php
namespace App\Http\Controllers;
use App\Models\Payroll;
use Illuminate\Http\Request;
class ReportController extends Controller
{
public function index(Request $request)
{
$query = Payroll::with('employee');
$startDate = now()->startOfMonth()->toDateString();
$endDate = now()->endOfMonth()->toDateString();
if ($request->startDate != null && $request->endDate != null) {
$query->whereBetween('date', [$request->startDate, $request->endDate]);
$startDate = $request->startDate;
$endDate = $request->endDate;
} else {
$query->whereBetween('date', [$startDate, $endDate]);
}
return inertia('Report', [
'payrolls' => $query->orderBy('date', 'desc')->paginate(10),
'_startDate' => $startDate,
'_endDate' => $endDate
]);
}
}

@ -23,4 +23,9 @@ class Payroll extends Model
{
return $this->belongsTo(Employee::class);
}
public function items()
{
return $this->hasMany(PayrollItem::class);
}
}

@ -0,0 +1,23 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PayrollItem extends Model
{
use HasFactory;
protected $fillable = [
'product_id',
'payroll_id',
'quantity',
'price',
];
public function product()
{
return $this->belongsTo(Product::class);
}
}

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePayrollItemsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('payroll_items', function (Blueprint $table) {
$table->id();
$table->foreignId('payroll_id')->constrained();
$table->foreignId('product_id')->constrained();
$table->decimal('quantity', 12, 2)->default(0);
$table->decimal('price', 12, 2)->default(0);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('payroll_items');
}
}

25
package-lock.json generated

@ -7,6 +7,7 @@
"dependencies": {
"daisyui": "^1.20.0",
"moment": "^2.29.1",
"qs": "^6.10.2",
"react-datepicker": "^4.5.0",
"react-number-format": "^4.9.0",
"react-toastify": "^8.1.0",
@ -2928,7 +2929,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
@ -4774,8 +4774,7 @@
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
@ -4799,7 +4798,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
@ -4909,7 +4907,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1"
},
@ -4930,7 +4927,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
@ -6684,7 +6680,6 @@
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
"integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@ -7837,7 +7832,6 @@
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.2.tgz",
"integrity": "sha512-mSIdjzqznWgfd4pMii7sHtaYF8rx8861hBO80SraY5GT0XQibWZWJSid0avzHGkDIZLImux2S5mXO0Hfct2QCw==",
"dev": true,
"dependencies": {
"side-channel": "^1.0.4"
},
@ -8671,7 +8665,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
@ -12382,7 +12375,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dev": true,
"requires": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
@ -13825,8 +13817,7 @@
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"gensync": {
"version": "1.0.0-beta.2",
@ -13844,7 +13835,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
"dev": true,
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
@ -13930,7 +13920,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"requires": {
"function-bind": "^1.1.1"
}
@ -13944,8 +13933,7 @@
"has-symbols": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
"dev": true
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
},
"has-tostringtag": {
"version": "1.0.0",
@ -15263,8 +15251,7 @@
"object-inspect": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
"integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==",
"dev": true
"integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g=="
},
"object-is": {
"version": "1.1.5",
@ -16054,7 +16041,6 @@
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.2.tgz",
"integrity": "sha512-mSIdjzqznWgfd4pMii7sHtaYF8rx8861hBO80SraY5GT0XQibWZWJSid0avzHGkDIZLImux2S5mXO0Hfct2QCw==",
"dev": true,
"requires": {
"side-channel": "^1.0.4"
}
@ -16707,7 +16693,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"dev": true,
"requires": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",

@ -29,6 +29,7 @@
"dependencies": {
"daisyui": "^1.20.0",
"moment": "^2.29.1",
"qs": "^6.10.2",
"react-datepicker": "^4.5.0",
"react-number-format": "^4.9.0",
"react-toastify": "^8.1.0",

443
public/css/app.css vendored

@ -1162,6 +1162,13 @@ html {
--tw-shadow: 0 0 0 1px var(--border-color) !important;
}
[data-theme=lofi] *:where(.badge) {
border-color: var(--border-color) !important;
--tw-border-opacity: 1 !important;
--tw-text-opacity: 1 !important;
--tw-shadow: 0 0 0 1px var(--border-color) !important;
}
[data-theme=lofi] *:where(.card) {
border-color: var(--border-color) !important;
--tw-border-opacity: 1 !important;
@ -1440,6 +1447,16 @@ html {
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: var(--tw-empty,/*!*/ /*!*/);
--tw-brightness: var(--tw-empty,/*!*/ /*!*/);
--tw-contrast: var(--tw-empty,/*!*/ /*!*/);
--tw-grayscale: var(--tw-empty,/*!*/ /*!*/);
--tw-hue-rotate: var(--tw-empty,/*!*/ /*!*/);
--tw-invert: var(--tw-empty,/*!*/ /*!*/);
--tw-saturate: var(--tw-empty,/*!*/ /*!*/);
--tw-sepia: var(--tw-empty,/*!*/ /*!*/);
--tw-drop-shadow: var(--tw-empty,/*!*/ /*!*/);
--tw-filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}
.alert>:not([hidden])~:not([hidden]) {
--tw-space-y-reverse: 0;
@ -1476,6 +1493,30 @@ html {
align-items: center;
justify-content: center;
}
.badge {
display: inline-flex;
align-items: center;
justify-content: center;
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
transition-duration: .2s;
transition-timing-function: cubic-bezier(.4, 0, .2, 1);
height: 1.25rem;
font-size: .875rem;
line-height: 1.25rem;
width: -webkit-fit-content;
width: -moz-fit-content;
width: fit-content;
padding-left: .563rem;
padding-right: .563rem;
--tw-bg-opacity: 1;
background-color: hsla(var(--n) / var(--tw-bg-opacity, 1));
--tw-border-opacity: 1;
border-color: hsla(var(--n) / var(--tw-border-opacity, 1));
border-width: 1px;
--tw-text-opacity: 1;
color: hsla(var(--nc) / var(--tw-text-opacity, 1));
border-radius: var(--rounded-badge, 1.9rem);
}
.btn {
border-color: hsla(var(--n) / var(--tw-border-opacity, 1));
cursor: pointer;
@ -1563,6 +1604,10 @@ html {
transform: rotate(1turn);
}
}
.btn-group {
display: flex;
flex-wrap: wrap;
}
.btn-group>input[type=radio].btn {
-webkit-appearance: none;
-moz-appearance: none;
@ -1591,6 +1636,16 @@ html {
.card figure,.card figure>* {
width: 100%;
}
.card-actions {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
margin-left: -.25rem;
margin-right: -.25rem;
}
.card-actions>* {
margin: .25rem;
}
.card.image-full {
display: grid;
}
@ -1658,9 +1713,34 @@ html {
outline-offset: 2px;
box-shadow: 0 0 0 2px hsl(var(--b1)), 0 0 0 4px hsla(var(--bc) / .2);
}
.input-group {
display: flex;
align-items: stretch;
width: 100%;
}
.input-group>*,.input-group>.input {
border-radius: 0;
}
.input-group :where(span) {
--tw-bg-opacity: 1;
background-color: hsla(var(--b3) / var(--tw-bg-opacity, 1));
display: flex;
align-items: center;
padding-left: 1rem;
padding-right: 1rem;
}
.input-group :first-child {
border-top-left-radius: var(--rounded-btn, .5rem);
border-top-right-radius: 0;
border-bottom-left-radius: var(--rounded-btn, .5rem);
border-bottom-right-radius: 0;
}
.input-group :last-child {
border-top-left-radius: 0;
border-top-right-radius: var(--rounded-btn, .5rem);
border-bottom-left-radius: 0;
border-bottom-right-radius: var(--rounded-btn, .5rem);
}
.link {
cursor: pointer;
text-decoration: underline;
@ -1743,55 +1823,6 @@ html {
justify-content: flex-end;
margin-top: 1.5rem;
}
.select {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
cursor: pointer;
display: inline-flex;
flex-shrink: 0;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
height: 3rem;
font-size: .875rem;
line-height: 2;
padding-left: 1rem;
padding-right: 2.5rem;
min-height: 3rem;
--tw-bg-opacity: 1;
background-color: hsla(var(--b1) / var(--tw-bg-opacity, 1));
--tw-border-opacity: 0;
border-color: hsla(var(--bc) / var(--tw-border-opacity, 1));
border-width: 1px;
font-weight: 600;
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;
transition-duration: .2s;
transition-timing-function: cubic-bezier(.4, 0, .2, 1);
border-radius: var(--rounded-btn, .5rem);
background-image: linear-gradient(45deg, transparent 50%, currentColor 0),
linear-gradient(135deg, currentColor 50%, transparent 0);
background-position: calc(100% - 20px) calc(1px + 50%),
calc(100% - 16px) calc(1px + 50%);
background-size: 4px 4px,
4px 4px;
background-repeat: no-repeat;
}
.select:focus {
outline: 2px solid transparent;
outline-offset: 2px;
box-shadow: 0 0 0 2px hsl(var(--b1)), 0 0 0 4px hsla(var(--bc) / .2);
}
.select-disabled,.select[disabled] {
pointer-events: none;
--tw-bg-opacity: 1;
background-color: hsla(var(--b2) / var(--tw-bg-opacity, 1));
--tw-border-opacity: 1;
border-color: hsla(var(--b2) / var(--tw-border-opacity, 1));
cursor: not-allowed;
--tw-text-opacity: 0.2;
}
.table {
position: relative;
text-align: left;
@ -1840,6 +1871,12 @@ html {
--tw-text-opacity: 1;
color: hsla(var(--b2) / var(--tw-text-opacity, 1));
}
.btn-outline .badge {
--tw-border-opacity: 1;
border-color: hsla(var(--nf) / var(--tw-border-opacity, 1));
--tw-text-opacity: 1;
color: hsla(var(--nc) / var(--tw-text-opacity, 1));
}
.btn-outline.btn-primary .badge {
--tw-bg-opacity: 1;
background-color: hsla(var(--p) / var(--tw-bg-opacity, 1));
@ -1856,6 +1893,19 @@ html {
--tw-text-opacity: 1;
color: hsla(var(--sc) / var(--tw-text-opacity, 1));
}
.btn-outline.btn-accent .badge {
--tw-bg-opacity: 1;
background-color: hsla(var(--a) / var(--tw-bg-opacity, 1));
--tw-border-opacity: 1;
border-color: hsla(var(--a) / var(--tw-border-opacity, 1));
--tw-text-opacity: 1;
color: hsla(var(--ac) / var(--tw-text-opacity, 1));
}
.btn-outline .badge.outline {
background-color: transparent;
--tw-border-opacity: 1;
border-color: hsla(var(--nf) / var(--tw-border-opacity, 1));
}
.btn-outline.btn-primary .badge-outline {
background-color: transparent;
--tw-border-opacity: 1;
@ -1870,6 +1920,27 @@ html {
--tw-text-opacity: 1;
color: hsla(var(--s) / var(--tw-text-opacity, 1));
}
.btn-outline.btn-accent .badge-outline {
background-color: transparent;
--tw-border-opacity: 1;
border-color: hsla(var(--a) / var(--tw-border-opacity, 1));
--tw-text-opacity: 1;
color: hsla(var(--a) / var(--tw-text-opacity, 1));
}
.btn-outline:hover .badge {
--tw-bg-opacity: 1;
background-color: hsla(var(--b2) / var(--tw-bg-opacity, 1));
--tw-border-opacity: 1;
border-color: hsla(var(--b2) / var(--tw-border-opacity, 1));
--tw-text-opacity: 1;
color: hsla(var(--bc) / var(--tw-text-opacity, 1));
}
.btn-outline:hover .badge.outline {
--tw-border-opacity: 1;
border-color: hsla(var(--b2) / var(--tw-border-opacity, 1));
--tw-text-opacity: 1;
color: hsla(var(--nc) / var(--tw-text-opacity, 1));
}
.btn-outline.btn-primary:hover .badge {
--tw-bg-opacity: 1;
background-color: hsla(var(--pc) / var(--tw-bg-opacity, 1));
@ -1902,6 +1973,22 @@ html {
--tw-text-opacity: 1;
color: hsla(var(--sc) / var(--tw-text-opacity, 1));
}
.btn-outline.btn-accent:hover .badge {
--tw-bg-opacity: 1;
background-color: hsla(var(--ac) / var(--tw-bg-opacity, 1));
--tw-border-opacity: 1;
border-color: hsla(var(--ac) / var(--tw-border-opacity, 1));
--tw-text-opacity: 1;
color: hsla(var(--a) / var(--tw-text-opacity, 1));
}
.btn-outline.btn-accent:hover .badge.outline {
--tw-bg-opacity: 1;
background-color: hsla(var(--af) / var(--tw-bg-opacity, 1));
--tw-border-opacity: 1;
border-color: hsla(var(--ac) / var(--tw-border-opacity, 1));
--tw-text-opacity: 1;
color: hsla(var(--ac) / var(--tw-text-opacity, 1));
}
.btn:active:focus,.btn:active:hover {
-webkit-animation: none;
animation: none;
@ -1950,6 +2037,23 @@ html {
.btn-secondary:focus-visible {
box-shadow: 0 0 0 2px hsl(var(--b1)), 0 0 0 4px hsl(var(--s));
}
.btn-info {
--tw-bg-opacity: 1;
background-color: hsla(var(--in) / var(--tw-bg-opacity, 1));
--tw-border-opacity: 1;
border-color: hsla(var(--in) / var(--tw-border-opacity, 1));
--tw-text-opacity: 1;
color: hsla(var(--b2) / var(--tw-text-opacity, 1));
}
.btn-info.btn-active,.btn-info:hover {
--tw-bg-opacity: 1;
background-color: hsla(var(--in) / var(--tw-bg-opacity, 1));
--tw-border-opacity: 1;
border-color: hsla(var(--in) / var(--tw-border-opacity, 1));
}
.btn-info:focus-visible {
box-shadow: 0 0 0 2px hsl(var(--b1)), 0 0 0 4px hsl(var(--in));
}
.btn.glass.btn-active,.btn.glass:hover {
--glass-opacity: 25%;
--glass-border-opacity: 15%;
@ -1957,6 +2061,20 @@ html {
.btn.glass:focus-visible {
box-shadow: 0 0 0 2px currentColor;
}
.btn-outline {
background-color: transparent;
border-color: currentColor;
--tw-text-opacity: 1;
color: hsla(var(--bc) / var(--tw-text-opacity, 1));
}
.btn-outline:hover {
--tw-bg-opacity: 1;
background-color: hsla(var(--bc) / var(--tw-bg-opacity, 1));
--tw-border-opacity: 1;
border-color: hsla(var(--bc) / var(--tw-border-opacity, 1));
--tw-text-opacity: 1;
color: hsla(var(--b1) / var(--tw-text-opacity, 1));
}
.btn-outline.btn-primary {
--tw-text-opacity: 1;
color: hsla(var(--p) / var(--tw-text-opacity, 1));
@ -1981,6 +2099,66 @@ html {
--tw-text-opacity: 1;
color: hsla(var(--sc) / var(--tw-text-opacity, 1));
}
.btn-outline.btn-accent {
--tw-text-opacity: 1;
color: hsla(var(--a) / var(--tw-text-opacity, 1));
}
.btn-outline.btn-accent:hover {
--tw-bg-opacity: 1;
background-color: hsla(var(--af) / var(--tw-bg-opacity, 1));
--tw-border-opacity: 1;
border-color: hsla(var(--af) / var(--tw-border-opacity, 1));
--tw-text-opacity: 1;
color: hsla(var(--ac) / var(--tw-text-opacity, 1));
}
.btn-outline.btn-success {
--tw-text-opacity: 1;
color: hsla(var(--su) / var(--tw-text-opacity, 1));
}
.btn-outline.btn-success:hover {
--tw-bg-opacity: 1;
background-color: hsla(var(--su) / var(--tw-bg-opacity, 1));
--tw-border-opacity: 1;
border-color: hsla(var(--su) / var(--tw-border-opacity, 1));
--tw-text-opacity: 1;
color: hsla(var(--nc) / var(--tw-text-opacity, 1));
}
.btn-outline.btn-info {
--tw-text-opacity: 1;
color: hsla(var(--in) / var(--tw-text-opacity, 1));
}
.btn-outline.btn-info:hover {
--tw-bg-opacity: 1;
background-color: hsla(var(--in) / var(--tw-bg-opacity, 1));
--tw-border-opacity: 1;
border-color: hsla(var(--in) / var(--tw-border-opacity, 1));
--tw-text-opacity: 1;
color: hsla(var(--nc) / var(--tw-text-opacity, 1));
}
.btn-outline.btn-warning {
--tw-text-opacity: 1;
color: hsla(var(--wa) / var(--tw-text-opacity, 1));
}
.btn-outline.btn-warning:hover {
--tw-bg-opacity: 1;
background-color: hsla(var(--wa) / var(--tw-bg-opacity, 1));
--tw-border-opacity: 1;
border-color: hsla(var(--wa) / var(--tw-border-opacity, 1));
--tw-text-opacity: 1;
color: hsla(var(--nc) / var(--tw-text-opacity, 1));
}
.btn-outline.btn-error {
--tw-text-opacity: 1;
color: hsla(var(--er) / var(--tw-text-opacity, 1));
}
.btn-outline.btn-error:hover {
--tw-bg-opacity: 1;
background-color: hsla(var(--er) / var(--tw-bg-opacity, 1));
--tw-border-opacity: 1;
border-color: hsla(var(--er) / var(--tw-border-opacity, 1));
--tw-text-opacity: 1;
color: hsla(var(--nc) / var(--tw-text-opacity, 1));
}
.btn.loading.btn-circle:before,.btn.loading.btn-square:before {
margin-right: 0;
}
@ -2056,6 +2234,12 @@ html {
.card.compact .card-title {
margin-bottom: .25rem;
}
.card-actions:first-child {
margin-bottom: .5rem;
}
.card-actions:last-child {
margin-top: 1.5rem;
}
.checkbox {
--chkbg: var(--bc);
--chkfg: var(--b1);
@ -2122,6 +2306,9 @@ html {
.drawer-toggle:focus-visible~.drawer-content .drawer-button.btn-secondary {
box-shadow: 0 0 0 2px hsl(var(--b1)), 0 0 0 4px hsl(var(--s));
}
.drawer-toggle:focus-visible~.drawer-content .drawer-button.btn-info {
box-shadow: 0 0 0 2px hsl(var(--b1)), 0 0 0 4px hsl(var(--in));
}
.label-text {
font-size: .875rem;
line-height: 1.25rem;
@ -2308,26 +2495,6 @@ html {
box-shadow: 0 0 0 4px hsl(var(--b1)) inset, 0 0 0 4px hsl(var(--b1)) inset, var(--focus-shadow);
}
}
.select-disabled::-moz-placeholder,.select[disabled]::-moz-placeholder {
--tw-placeholder-opacity: 0.2;
color: hsla(var(--bc) / var(--tw-placeholder-opacity, 1));
}
.select-disabled:-ms-input-placeholder,.select[disabled]:-ms-input-placeholder {
--tw-placeholder-opacity: 0.2;
color: hsla(var(--bc) / var(--tw-placeholder-opacity, 1));
}
.select-disabled::-moz-placeholder, .select[disabled]::-moz-placeholder {
--tw-placeholder-opacity: 0.2;
color: hsla(var(--bc) / var(--tw-placeholder-opacity, 1));
}
.select-disabled:-ms-input-placeholder, .select[disabled]:-ms-input-placeholder {
--tw-placeholder-opacity: 0.2;
color: hsla(var(--bc) / var(--tw-placeholder-opacity, 1));
}
.select-disabled::placeholder,.select[disabled]::placeholder {
--tw-placeholder-opacity: 0.2;
color: hsla(var(--bc) / var(--tw-placeholder-opacity, 1));
}
.table :where(th,td) {
padding: 1rem;
vertical-align: middle;
@ -2448,6 +2615,24 @@ html {
border-color: transparent;
cursor: not-allowed;
}
.btn-sm {
height: 2rem;
padding-left: .75rem;
padding-right: .75rem;
min-height: 2rem;
font-size: .875rem;
}
.btn-square:where(.btn-sm) {
height: 2rem;
padding: 0;
width: 2rem;
}
.btn-circle:where(.btn-sm) {
border-radius: 9999px;
height: 2rem;
padding: 0;
width: 2rem;
}
.visible {
visibility: visible;
}
@ -2475,6 +2660,18 @@ html {
.left-0 {
left: 0px;
}
.right-2\.5 {
right: 0.625rem;
}
.top-2\.5 {
top: 0.625rem;
}
.right-2 {
right: 0.5rem;
}
.top-2 {
top: 0.5rem;
}
.z-0 {
z-index: 0;
}
@ -2492,6 +2689,10 @@ html {
margin-left: 0.25rem;
margin-right: 0.25rem;
}
.my-auto {
margin-top: auto;
margin-bottom: auto;
}
.my-2 {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
@ -2570,6 +2771,9 @@ html {
.mt-1 {
margin-top: 0.25rem;
}
.mb-2 {
margin-bottom: 0.5rem;
}
.block {
display: block;
}
@ -2597,21 +2801,15 @@ html {
.h-16 {
height: 4rem;
}
.h-4 {
height: 1rem;
}
.h-6 {
height: 1.5rem;
}
.h-4 {
height: 1rem;
}
.h-24 {
height: 6rem;
}
.max-h-11 {
max-height: 2.75rem;
}
.max-h-96 {
max-height: 24rem;
}
.max-h-screen {
max-height: 100vh;
}
@ -2627,6 +2825,9 @@ html {
.w-auto {
width: auto;
}
.w-6 {
width: 1.5rem;
}
.w-48 {
width: 12rem;
}
@ -2636,8 +2837,8 @@ html {
.w-4 {
width: 1rem;
}
.w-6 {
width: 1.5rem;
.w-1\/6 {
width: 16.666667%;
}
.max-w-xl {
max-width: 36rem;
@ -2696,6 +2897,12 @@ html {
.grid-cols-1 {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
.grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.flex-row {
flex-direction: row;
}
@ -2720,6 +2927,15 @@ html {
.justify-between {
justify-content: space-between;
}
.justify-items-center {
justify-items: center;
}
.gap-4 {
gap: 1rem;
}
.gap-2 {
gap: 0.5rem;
}
.space-x-8 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(2rem * var(--tw-space-x-reverse));
@ -2745,6 +2961,11 @@ html {
margin-right: calc(0.5rem * var(--tw-space-x-reverse));
margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
}
.space-y-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(1rem * var(--tw-space-y-reverse));
}
.overflow-hidden {
overflow: hidden;
}
@ -2754,14 +2975,6 @@ html {
.overflow-y-auto {
overflow-y: auto;
}
.overscroll-auto {
-ms-scroll-chaining: chained;
overscroll-behavior: auto;
}
.overscroll-contain {
-ms-scroll-chaining: none;
overscroll-behavior: contain;
}
.rounded-md {
border-radius: 0.375rem;
}
@ -2779,6 +2992,10 @@ html {
border-top-right-radius: 0.375rem;
border-bottom-right-radius: 0.375rem;
}
.rounded-l-none {
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
}
.border {
border-width: 1px;
}
@ -2863,6 +3080,9 @@ html {
.p-1 {
padding: 0.25rem;
}
.p-4 {
padding: 1rem;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
@ -2911,6 +3131,14 @@ html {
padding-top: 3rem;
padding-bottom: 3rem;
}
.px-0\.5 {
padding-left: 0.125rem;
padding-right: 0.125rem;
}
.px-0 {
padding-left: 0px;
padding-right: 0px;
}
.pt-8 {
padding-top: 2rem;
}
@ -3087,6 +3315,9 @@ html {
.opacity-100 {
opacity: 1;
}
.opacity-70 {
opacity: 0.7;
}
.shadow-sm {
--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
@ -3127,6 +3358,9 @@ html {
.ring-opacity-5 {
--tw-ring-opacity: 0.05;
}
.filter {
filter: var(--tw-filter);
}
.transition {
transition-property: color, background-color, border-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-text-decoration-color, -webkit-backdrop-filter;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
@ -3393,6 +3627,11 @@ html {
--tw-text-opacity: 1;
color: rgb(55 65 81 / var(--tw-text-opacity));
}
.table td, .table th {
padding: 1rem;
vertical-align: middle;
white-space: normal;
}
.hover\:border-gray-300:hover {
--tw-border-opacity: 1;
border-color: rgb(209 213 219 / var(--tw-border-opacity));
@ -3634,10 +3873,38 @@ html {
}
@media (min-width: 768px) {
.md\:w-2\/3 {
width: 66.666667%;
}
.md\:w-1\/3 {
width: 33.333333%;
}
.md\:grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.md\:flex-row {
flex-direction: row;
}
.md\:items-stretch {
align-items: stretch;
}
.md\:space-y-0 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(0px * var(--tw-space-y-reverse));
}
.md\:space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(1rem * var(--tw-space-x-reverse));
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
}
.md\:border-t-0 {
border-top-width: 0px;
}

2045
public/js/app.js vendored

File diff suppressed because it is too large Load Diff

@ -96,3 +96,9 @@
.react-datepicker__day--range-end {
@apply bg-blue-500 text-white hover:text-gray-700 hover:bg-white;
}
.table td, .table th {
padding: 1rem;
vertical-align: middle;
white-space: normal;
}

@ -0,0 +1,22 @@
import React from 'react'
export default function CloseIcon({ onClick, className }) {
return (
<div className={className} onClick={onClick}>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</div>
)
}

@ -1,37 +1,62 @@
import { Link } from '@inertiajs/inertia-react';
import { Inertia } from '@inertiajs/inertia'
import qs from 'qs'
const PageLink = ({ active, label, url }) => {
const className = `mr-1 mb-1 px-4 py-3 border border-solid border-gray-300 rounded text-sm bg-white hover:bg-white ${ active && 'focus:outline-none focus:border-indigo-700 focus:text-indigo-700 border-indigo-600 bg-indigo-600 text-white hover:bg-indigo-400'}`
const PageLink = ({ active, label, url, params }) => {
const className = `mr-1 mb-1 px-4 py-3 border border-solid border-gray-300 rounded text-sm bg-white hover:bg-white ${
active &&
'focus:outline-none focus:border-indigo-700 focus:text-indigo-700 border-indigo-600 bg-indigo-600 text-white hover:bg-indigo-400'
}`
const onClick = () => {
Inertia.get(
`${url}&${qs.stringify(params)}`,
{},
{
replace: true,
preserveState: true,
}
)
}
return (
<Link className={className} href={url}>
<div className={className} onClick={onClick}>
<span dangerouslySetInnerHTML={{ __html: label }}></span>
</Link>
);
};
</div>
)
}
// Previous, if on first page
// Next, if on last page
// and dots, if exists (...)
const PageInactive = ({ label }) => {
const className = 'mr-1 mb-1 px-4 py-3 text-sm border rounded border-solid border-gray-300 text-gray'
const className =
'mr-1 mb-1 px-4 py-3 text-sm border rounded border-solid border-gray-300 text-gray'
return (
<div className={className} dangerouslySetInnerHTML={{ __html: label }} />
);
};
<div
className={className}
dangerouslySetInnerHTML={{ __html: label }}
/>
)
}
export default ({ links = [] }) => {
export default ({ links = [], params = null }) => {
// dont render, if there's only 1 page (previous, 1, next)
if (links.length === 3) return null;
if (links.length === 3) return null
return (
<div className="flex flex-wrap mt-6 -mb-1">
{links.map(({ active, label, url }) => {
return url === null ? (
<PageInactive key={label} label={label} />
) : (
<PageLink key={label} label={label} active={active} url={url} />
);
<PageLink
key={label}
label={label}
active={active}
url={url}
params={params}
/>
)
})}
</div>
);
};
)
}

@ -53,7 +53,7 @@ export default function Authenticated({ auth, header, children }) {
</NavLink>
<NavLink
href={route('payrolls.index')}
active={route().current('payrolls.index')}
active={route().current('payrolls.*')}
>
Gaji
</NavLink>
@ -188,7 +188,7 @@ export default function Authenticated({ auth, header, children }) {
</ResponsiveNavLink>
<ResponsiveNavLink
href={route('payrolls.index')}
active={route().current('payrolls.index')}
active={route().current('payrolls.*')}
>
Gaji
</ResponsiveNavLink>

@ -1,5 +1,4 @@
import React, { useEffect, useRef } from 'react'
import NumberFormat from 'react-number-format'
import { useForm } from '@inertiajs/inertia-react'
import { toast } from 'react-toastify'
import { formatIDR } from '@/utils'
@ -65,7 +64,7 @@ export default function FormEmployeeModal(props) {
setData({
name: employee?.name ? employee.name : '',
whatsapp: employee?.whatsapp ? employee.whatsapp : '',
basic_salary: employee?.basic_salary ? formatIDR(employee.basic_salary) : '',
basic_salary: employee?.basic_salary ? formatIDR(employee.basic_salary) : 0,
img_alt: employee?.photo_url ? employee.photo_url : null,
})
}, [employee])
@ -123,25 +122,6 @@ export default function FormEmployeeModal(props) {
</span>
</label>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Gaji Pokok</span>
</label>
<NumberFormat
thousandSeparator={true}
className={`input input-bordered ${
errors.basic_salary ? 'input-error' : ''
}`}
value={data.basic_salary}
thousandSeparator="."
decimalSeparator=","
onValueChange={({ value }) => setData('basic_salary', value)}
placeholder="gaji pokok"
/>
<label className="label">
<span className="label-text-alt">{errors.basic_salary}</span>
</label>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Foto</span>

@ -138,7 +138,7 @@ export default function Employees(props) {
</tbody>
</table>
</div>
<Pagination links={links} />
<Pagination links={links} params={{ q: search }} />
</div>
</div>
</div>

@ -1,129 +0,0 @@
import React, { useState } from 'react'
import { Head } from '@inertiajs/inertia-react'
import { Inertia } from '@inertiajs/inertia'
import { toast } from 'react-toastify'
import { useModalState } from '@/Hooks'
import Authenticated from '@/Layouts/Authenticated'
import Pagination from '@/Components/Pagination'
import ModalConfirm from '@/Components/ModalConfirm'
import FormPayrollModal from '@/Modals/FormPayrollModal'
import { formatIDR, formatDate } from '@/utils'
export default function Payrolls(props) {
const { data: payrolls, links } = props.payrolls
const [payroll, setPayroll] = useState(null)
const formModal = useModalState(false)
const toggle = (payroll = null) => {
setPayroll(payroll)
formModal.toggle()
}
const confirmModal = useModalState(false)
const handleDelete = (payroll) => {
confirmModal.setData(payroll)
confirmModal.toggle()
}
const onDelete = () => {
const payroll = confirmModal.data
if (payroll != null) {
Inertia.delete(route('payrolls.destroy', payroll), {
onSuccess: () => toast.success('The Data has been deleted'),
})
}
}
return (
<Authenticated
auth={props.auth}
errors={props.errors}
header={
<h2 className="font-semibold text-xl text-gray-800 leading-tight">
Gaji
</h2>
}
>
<Head title="Payroll" />
<div className="py-12">
<div className="flex flex-col w-full sm:px-6 lg:px-8 space-y-2">
<div className="card bg-white w-full">
<div className="card-body">
<div className="flex w-full mb-4 justify-between">
<div
className="btn btn-neutral"
onClick={() => toggle()}
>
Tambah
</div>
</div>
<div className="overflow-x-auto">
<table className="table w-full table-zebra">
<thead>
<tr>
<th>Tanggal</th>
<th>Nama Karyawan</th>
<th>Gaji</th>
<th>Potongan</th>
<th>Bonus</th>
<th></th>
</tr>
</thead>
<tbody>
{payrolls.map((payroll) => (
<tr key={payroll.id}>
<th>{formatDate(payroll.date)}</th>
<td>{payroll.employee.name}</td>
<td>
{formatIDR(payroll.amount)}
</td>
<td>
{formatIDR(payroll.cuts)}
</td>
<td>
{formatIDR(payroll.bonus)}
</td>
<td className="text-right">
<div
className="btn btn-primary mx-1"
onClick={() =>
toggle(payroll)
}
>
Edit
</div>
<div
className="btn btn-secondary mx-1"
onClick={() =>
handleDelete(
payroll
)
}
>
Delete
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
<Pagination links={links} />
</div>
</div>
</div>
</div>
<FormPayrollModal
isOpen={formModal.isOpen}
toggle={toggle}
payroll={payroll}
/>
<ModalConfirm
isOpen={confirmModal.isOpen}
toggle={confirmModal.toggle}
onConfirm={onDelete}
/>
</Authenticated>
)
}

@ -0,0 +1,333 @@
import React, { useState, useEffect } from 'react'
import DatePicker from 'react-datepicker'
import NumberFormat from 'react-number-format'
import { Inertia } from '@inertiajs/inertia'
import { usePrevious } from 'react-use'
import { toast } from 'react-toastify'
import { Head, useForm } from '@inertiajs/inertia-react'
import Authenticated from '@/Layouts/Authenticated'
import Pagination from '@/Components/Pagination'
import CloseIcon from '@/Components/CloseIcon'
import EmployeeSelectInput from '@/Selects/EmployeeSelectInput'
import { formatIDR } from '@/utils'
export default function Create(props) {
const { data: products, links } = props.products
const { _search, _page } = props
const { data, setData, post, errors, processing } = useForm({
date: new Date(),
employee_id: null,
employee_name: '',
cuts: 0,
bonus: 0,
items: [],
})
const [loading, setLoading] = useState(false)
const [search, setSearch] = useState(_search)
const preValue = usePrevious(search)
const handleSelectedEmployee = (employee) => {
setData({
...data,
employee_id: employee.id,
employee_name: `${employee.name} - ${employee.whatsapp}`,
})
}
const addItem = (product) => {
const itemExist = data.items.find(item => item.id === product.id)
if(itemExist) {
setData(
'items',
data.items.map(item => {
if(item.id === product.id){
return {
...item,
quantity: +item.quantity + 1,
}
} else {
return item
}
})
)
return
}
setData('items', data.items.concat({
...product,
product_id: product.id,
quantity: 1
}))
}
const remoteItem = (product) => {
setData('items', data.items.filter(item => item.id !== product.id))
}
const handleReset = () => {
setData({
employee_id: null,
employee_name: '',
cuts: 0,
bonus: 0,
items: [],
})
}
const handleSubmit = () => {
if (data.items.length <= 0) {
alert('barang belum di pilih')
return
}
post(route('payrolls.store'), {
onSuccess: () =>
Promise.all([
handleReset(),
toast.success('The Data has been saved'),
]),
})
}
const itemAmount = data.items.reduce((amt, item) => amt + (+item.quantity * +item.price), 0)
const totalAmount = itemAmount - +data.cuts + +data.bonus
useEffect(() => {
if (preValue) {
setLoading(true)
Inertia.get(
route(route().current()),
{ q: search, page: _page },
{
replace: true,
preserveState: true,
onSuccess: () => { setLoading(false) },
}
)
}
}, [search])
return (
<Authenticated
auth={props.auth}
errors={props.errors}
header={
<h2 className="font-semibold text-xl text-gray-800 leading-tight">
Gaji
</h2>
}
>
<Head title="Payrolls" />
<div className="py-12">
<div className="flex flex-col md:flex-row w-full sm:px-6 lg:px-8 space-y-4 md:space-x-4 md:space-y-0">
<div className="card bg-white w-full md:w-2/3">
<div className="p-4">
<div className="flex flex-row justify-end mb-2">
<div className="form-control">
<input
type="text"
className="input input-bordered"
value={search}
onChange={(e) =>
setSearch(e.target.value)
}
placeholder="Search"
/>
</div>
</div>
<div
className={`grid grid-cols-4 gap-4 ${
loading && 'opacity-70'
}`}
>
{products.map((product) => (
<div
className="rounded bg-white shadow-md"
key={product.id}
onClick={() => addItem(product)}
>
<img
src={product.photo_url}
style={{ height: '100px' }}
/>
<div className="p-4 flex flex-col justify-items-center items-center space-y-4">
<div className="font-bold text-center">
{product.name}
</div>
<div className="badge">
{formatIDR(product.price)}
</div>
</div>
</div>
))}
</div>
<div className="flex flex-col items-center">
<div>
<Pagination
links={links}
params={{ q: search }}
/>
</div>
</div>
</div>
</div>
<div className="card bg-white w-full md:w-1/3">
<div className="flex flex-col p-2 mb-4">
<div>
<DatePicker
selected={data.date}
onChange={(date) => setData('date', date)}
format="dd/mm/yyyy"
className={`input input-bordered ${
errors.date ? 'input-error' : ''
}`}
nextMonthButtonLabel=">"
previousMonthButtonLabel="<"
/>
{errors.date && (
<label className="label">
<span className="label-text-alt">
{errors.date}
</span>
</label>
)}
</div>
<div className="w-full">
<EmployeeSelectInput
value={data.employee_name}
onItemSelected={handleSelectedEmployee}
invalid={errors.employee_id ? true : false}
/>
<label className="label">
<span className="label-text-alt">
{errors.employee_id}
</span>
</label>
</div>
<div
className="overflow-x-auto"
style={{ minHeight: '280px' }}
>
<table className="table w-full">
<thead>
<tr>
<th>Barang</th>
<th>Qty</th>
<th>Subtotal</th>
<th></th>
</tr>
</thead>
<tbody>
{data.items.map((item) => (
<tr key={item.id}>
<td>{item.name}</td>
<td>
{formatIDR(item.quantity)}
</td>
<td>
{formatIDR(
item.quantity * item.price
)}
</td>
<td>
<CloseIcon
className="btn btn-outline btn-sm px-0.5"
onClick={() =>
remoteItem(item)
}
/>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="card-actions">
<div className="form-control w-full">
<label className="input-group w-full">
<span>Potongan</span>
<NumberFormat
thousandSeparator={true}
className={`input input-bordered w-full text-right ${
errors.cuts ? 'input-error' : ''
}`}
value={data.cuts}
thousandSeparator="."
decimalSeparator=","
onValueChange={({ value }) =>
setData('cuts', value)
}
placeholder="potongan"
/>
</label>
{errors.cuts && (
<label className="label">
<span className="label-text-alt">
{errors.cuts}
</span>
</label>
)}
</div>
<div className="form-control w-full">
<label className="input-group w-full">
<span>Bonus</span>
<NumberFormat
thousandSeparator={true}
className={`input input-bordered w-full text-right ${
errors.bonus ? 'input-error' : ''
}`}
value={data.bonus}
thousandSeparator="."
decimalSeparator=","
onValueChange={({ value }) =>
setData('bonus', value)
}
placeholder="bonus"
/>
</label>
{errors.bonus && (
<label className="label">
<span className="label-text-alt">
{errors.bonus}
</span>
</label>
)}
</div>
<div className="form-control w-full mt-2">
<label className="input-group w-full">
<span>Total</span>
<NumberFormat
thousandSeparator={true}
className="input input-bordered w-full text-right"
value={totalAmount}
thousandSeparator="."
decimalSeparator=","
readOnly={true}
placeholder="total"
/>
</label>
</div>
<div className="grid grid-cols-2 gap-2 w-full">
<div
className="btn btn-primary"
disabled={processing}
onClick={handleSubmit}
>
Simpan
</div>
<div
className="btn btn-primary"
disabled={processing}
>
Cetak
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</Authenticated>
)
}

@ -0,0 +1,353 @@
import React, { useState, useEffect } from 'react'
import DatePicker from 'react-datepicker'
import NumberFormat from 'react-number-format'
import { Inertia } from '@inertiajs/inertia'
import { usePrevious } from 'react-use'
import { toast } from 'react-toastify'
import { Head, useForm } from '@inertiajs/inertia-react'
import Authenticated from '@/Layouts/Authenticated'
import Pagination from '@/Components/Pagination'
import CloseIcon from '@/Components/CloseIcon'
import EmployeeSelectInput from '@/Selects/EmployeeSelectInput'
import { formatIDR } from '@/utils'
export default function Edit(props) {
const { data: products, links } = props.products
const { payroll, _search, _page } = props
const { data, setData, put, errors, processing } = useForm({
date: new Date(payroll.date),
employee_id: payroll.employee_id,
employee_name: `${payroll.employee.name} - ${payroll.employee.whatsapp}`,
cuts: payroll.cuts,
bonus: payroll.bonus,
items: payroll.items.map(item => {
return {
product_id: item.product_id,
quantity: item.quantity,
...item.product,
}
}),
})
const [loading, setLoading] = useState(false)
const [search, setSearch] = useState(_search)
const preValue = usePrevious(search)
const handleSelectedEmployee = (employee) => {
setData({
...data,
employee_id: employee.id,
employee_name: `${employee.name} - ${employee.whatsapp}`,
})
}
const addItem = (product) => {
const itemExist = data.items.find((item) => item.id === product.id)
if (itemExist) {
setData(
'items',
data.items.map((item) => {
if (item.id === product.id) {
return {
...item,
quantity: +item.quantity + 1,
}
} else {
return item
}
})
)
return
}
setData(
'items',
data.items.concat({
...product,
product_id: product.id,
quantity: 1,
})
)
}
const remoteItem = (product) => {
setData(
'items',
data.items.filter((item) => item.id !== product.id)
)
}
const handleReset = () => {
setData({
employee_id: null,
employee_name: '',
cuts: 0,
bonus: 0,
items: [],
})
}
const handleSubmit = () => {
if (data.items.length <= 0) {
alert('barang belum di pilih')
return
}
put(route('payrolls.update', payroll), {
onSuccess: () =>
Promise.all([
handleReset(),
toast.success('The Data has been saved'),
]),
})
}
const itemAmount = data.items.reduce(
(amt, item) => amt + +item.quantity * +item.price,
0
)
const totalAmount = itemAmount - +data.cuts + +data.bonus
useEffect(() => {
if (preValue) {
setLoading(true)
Inertia.get(
route(route().current(), payroll),
{ q: search, page: _page },
{
replace: true,
preserveState: true,
onSuccess: () => {
setLoading(false)
},
}
)
}
}, [search])
return (
<Authenticated
auth={props.auth}
errors={props.errors}
header={
<h2 className="font-semibold text-xl text-gray-800 leading-tight">
Gaji
</h2>
}
>
<Head title="Payrolls" />
<div className="py-12">
<div className="flex flex-col md:flex-row w-full sm:px-6 lg:px-8 space-y-4 md:space-x-4 md:space-y-0">
<div className="card bg-white w-full md:w-2/3">
<div className="p-4">
<div className="flex flex-row justify-end mb-2">
<div className="form-control">
<input
type="text"
className="input input-bordered"
value={search}
onChange={(e) =>
setSearch(e.target.value)
}
placeholder="Search"
/>
</div>
</div>
<div
className={`grid grid-cols-4 gap-4 ${
loading && 'opacity-70'
}`}
>
{products.map((product) => (
<div
className="rounded bg-white shadow-md"
key={product.id}
onClick={() => addItem(product)}
>
<img
src={product.photo_url}
style={{ height: '100px' }}
/>
<div className="p-4 flex flex-col justify-items-center items-center space-y-4">
<div className="font-bold text-center">
{product.name}
</div>
<div className="badge">
{formatIDR(product.price)}
</div>
</div>
</div>
))}
</div>
<div className="flex flex-col items-center">
<div>
<Pagination
links={links}
params={{ q: search }}
/>
</div>
</div>
</div>
</div>
<div className="card bg-white w-full md:w-1/3">
<div className="flex flex-col p-2 mb-4">
<div>
<DatePicker
selected={data.date}
onChange={(date) => setData('date', date)}
format="dd/mm/yyyy"
className={`input input-bordered ${
errors.date ? 'input-error' : ''
}`}
nextMonthButtonLabel=">"
previousMonthButtonLabel="<"
/>
{errors.date && (
<label className="label">
<span className="label-text-alt">
{errors.date}
</span>
</label>
)}
</div>
<div className="w-full">
<EmployeeSelectInput
value={data.employee_name}
onItemSelected={handleSelectedEmployee}
invalid={errors.employee_id ? true : false}
/>
<label className="label">
<span className="label-text-alt">
{errors.employee_id}
</span>
</label>
</div>
<div
className="overflow-x-auto"
style={{ minHeight: '280px' }}
>
<table className="table w-full">
<thead>
<tr>
<th>Barang</th>
<th>Qty</th>
<th>Subtotal</th>
<th></th>
</tr>
</thead>
<tbody>
{data.items.map((item) => (
<tr key={item.id}>
<td>{item.name}</td>
<td>
{formatIDR(item.quantity)}
</td>
<td>
{formatIDR(
item.quantity *
item.price
)}
</td>
<td>
<CloseIcon
className="btn btn-outline btn-sm px-0.5"
onClick={() =>
remoteItem(item)
}
/>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="card-actions">
<div className="form-control w-full">
<label className="input-group w-full">
<span>Potongan</span>
<NumberFormat
thousandSeparator={true}
className={`input input-bordered w-full text-right ${
errors.cuts ? 'input-error' : ''
}`}
value={formatIDR(data.cuts)}
thousandSeparator="."
decimalSeparator=","
onValueChange={({ value }) =>
setData('cuts', value)
}
placeholder="potongan"
/>
</label>
{errors.cuts && (
<label className="label">
<span className="label-text-alt">
{errors.cuts}
</span>
</label>
)}
</div>
<div className="form-control w-full">
<label className="input-group w-full">
<span>Bonus</span>
<NumberFormat
thousandSeparator={true}
className={`input input-bordered w-full text-right ${
errors.bonus
? 'input-error'
: ''
}`}
value={formatIDR(data.bonus)}
thousandSeparator="."
decimalSeparator=","
onValueChange={({ value }) =>
setData('bonus', value)
}
placeholder="bonus"
/>
</label>
{errors.bonus && (
<label className="label">
<span className="label-text-alt">
{errors.bonus}
</span>
</label>
)}
</div>
<div className="form-control w-full mt-2">
<label className="input-group w-full">
<span>Total</span>
<NumberFormat
thousandSeparator={true}
className="input input-bordered w-full text-right"
value={totalAmount}
thousandSeparator="."
decimalSeparator=","
readOnly={true}
placeholder="total"
/>
</label>
</div>
<div className="grid grid-cols-2 gap-2 w-full">
<div
className="btn btn-primary"
disabled={processing}
onClick={handleSubmit}
>
Simpan
</div>
<div
className="btn btn-primary"
disabled={processing}
>
Cetak
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</Authenticated>
)
}

@ -0,0 +1,217 @@
import React, { useState, useEffect } from 'react'
import DatePicker from 'react-datepicker'
import moment from 'moment'
import { Head, Link } from '@inertiajs/inertia-react'
import { Inertia } from '@inertiajs/inertia'
import { usePrevious } from 'react-use'
import { toast } from 'react-toastify'
import { useModalState } from '@/Hooks'
import Authenticated from '@/Layouts/Authenticated'
import Pagination from '@/Components/Pagination'
import ModalConfirm from '@/Components/ModalConfirm'
import { formatIDR, formatDate } from '@/utils'
export default function Payrolls(props) {
const { data: payrolls, links } = props.payrolls
const { _startDate, _endDate } = props
const [startDate, setStartDate] = useState(_startDate ? new Date(_startDate) : new Date())
const [endDate, setEndDate] = useState(_endDate ? new Date(_endDate) : new Date())
const preValue = usePrevious(`${startDate}-${endDate}`)
const confirmModal = useModalState(false)
const handleDelete = (payroll) => {
confirmModal.setData(payroll)
confirmModal.toggle()
}
const onDelete = () => {
const payroll = confirmModal.data
if (payroll != null) {
Inertia.delete(route('payrolls.destroy', payroll), {
onSuccess: () => toast.success('The Data has been deleted'),
})
}
}
const params = {
startDate: moment(startDate).format('yyyy-MM-DD'),
endDate: moment(endDate).format('yyyy-MM-DD'),
}
useEffect(() => {
if (preValue) {
Inertia.get(
route(route().current()),
params,
{
replace: true,
preserveState: true,
}
)
}
}, [startDate, endDate])
return (
<Authenticated
auth={props.auth}
errors={props.errors}
header={
<h2 className="font-semibold text-xl text-gray-800 leading-tight">
Gaji
</h2>
}
>
<Head title="Payroll" />
<div className="py-12">
<div className="flex flex-col w-full sm:px-6 lg:px-8 space-y-2">
<div
className="card bg-white w-full"
style={{ minHeight: '400px' }}
>
<div className="card-body">
<div className="flex flex-col md:flex-row space-y-2 md:space-y-0 items-start md:items-stretch w-full mb-4 justify-between">
<Link
className="btn btn-neutral my-auto"
href={route('payrolls.create')}
>
Tambah
</Link>
<div className="flex flex-row md:space-x-4">
<div>
<label className="label">
<span className="label-text">
Tanggal Awal
</span>
</label>
<div className="relative">
<DatePicker
selected={startDate}
onChange={(date) => {
setStartDate(date)
}}
format="dd/mm/yyyy"
className="input input-bordered"
nextMonthButtonLabel=">"
previousMonthButtonLabel="<"
/>
<div className="absolute right-2.5 rounded-l-none y-0 flex items-center top-2.5">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
</div>
</div>
</div>
<div>
<label className="label">
<span className="label-text">
Tanggal Akhir
</span>
</label>
<div className="relative">
<DatePicker
selected={endDate}
onChange={(date) => {
setEndDate(date)
}}
format="dd/mm/yyyy"
className="input input-bordered"
nextMonthButtonLabel=">"
previousMonthButtonLabel="<"
/>
<div className="absolute right-2.5 rounded-l-none y-0 flex items-center top-2.5">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
</div>
</div>
</div>
</div>
</div>
<div className="overflow-x-auto">
<table className="table w-full table-zebra">
<thead>
<tr>
<th>Tanggal</th>
<th>Nama Karyawan</th>
<th>Potongan</th>
<th>Bonus</th>
<th>Total</th>
<th></th>
</tr>
</thead>
<tbody>
{payrolls.map((payroll) => (
<tr key={payroll.id}>
<th>
{formatDate(payroll.date)}
</th>
<td>{payroll.employee.name}</td>
<td>
{formatIDR(payroll.cuts)}
</td>
<td>
{formatIDR(payroll.bonus)}
</td>
<td>
{formatIDR(payroll.recived)}
</td>
<td className="text-right">
<Link
className="btn btn-primary mx-1"
href={route('payrolls.edit', payroll)}
>
Edit
</Link>
<div
className="btn btn-secondary mx-1"
onClick={() =>
handleDelete(
payroll
)
}
>
Delete
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
<Pagination links={links} params={params} />
</div>
</div>
</div>
</div>
<ModalConfirm
isOpen={confirmModal.isOpen}
toggle={confirmModal.toggle}
onConfirm={onDelete}
/>
</Authenticated>
)
}

@ -14,7 +14,7 @@ import FormProductModal from '@/Modals/FormProductModal'
export default function Products(props) {
const { data: products, links } = props.products
const [search, setSearch] = useState('')
const [search, setSearch] = useState(props._search)
const preValue = usePrevious(search)
const [product, setProduct] = useState(null)
@ -90,7 +90,6 @@ export default function Products(props) {
<table className="table w-full table-zebra">
<thead>
<tr>
<th>Id</th>
<th>Nama</th>
<th>Harga</th>
<th>Deskripsi</th>
@ -101,7 +100,6 @@ export default function Products(props) {
<tbody>
{products?.map((product) => (
<tr key={product.id}>
<th>{product.id}</th>
<td>{product.name}</td>
<td>
{formatIDR(product.price)}
@ -143,7 +141,7 @@ export default function Products(props) {
</tbody>
</table>
</div>
<Pagination links={links} />
<Pagination links={links} params={{ q: search }} />
</div>
</div>
</div>

@ -1,22 +1,175 @@
import React from 'react'
import Authenticated from '@/Layouts/Authenticated'
import React, { useState, useEffect } from 'react'
import DatePicker from 'react-datepicker'
import moment from 'moment'
import { Head } from '@inertiajs/inertia-react'
import { Inertia } from '@inertiajs/inertia'
import { usePrevious } from 'react-use'
import Authenticated from '@/Layouts/Authenticated'
import Pagination from '@/Components/Pagination'
import { formatIDR, formatDate } from '@/utils'
export default function Reports(props) {
const { data: payrolls, links } = props.payrolls
const { _startDate, _endDate } = props
const [startDate, setStartDate] = useState(
_startDate ? new Date(_startDate) : new Date()
)
const [endDate, setEndDate] = useState(
_endDate ? new Date(_endDate) : new Date()
)
const preValue = usePrevious(`${startDate}-${endDate}`)
useEffect(() => {
if (preValue) {
Inertia.get(
route(route().current()),
{
startDate: moment(startDate).format('yyyy-MM-DD'),
endDate: moment(endDate).format('yyyy-MM-DD'),
},
{
replace: true,
preserveState: true,
}
)
}
}, [startDate, endDate])
export default function Report(props) {
return (
<Authenticated
auth={props.auth}
errors={props.errors}
header={
<h2 className="font-semibold text-xl text-gray-800 leading-tight">
Laporan
Gaji
</h2>
}
>
<Head title="Report" />
<Head title="Payroll" />
<div className="py-12">
<div className="flex flex-row w-full sm:px-6 lg:px-8 space-x-4">
<div className="flex flex-col w-full sm:px-6 lg:px-8 space-y-2">
<div
className="card bg-white w-full"
style={{ minHeight: '400px' }}
>
<div className="card-body">
<div className="flex flex-col md:flex-row space-y-2 md:space-y-0 items-start md:items-stretch w-full mb-4 justify-between">
<div className="btn-group my-auto">
<div
className="btn btn-info btn-outline"
onClick={() => {}}
>
Download Excel
</div>
</div>
<div className="flex flex-row md:space-x-4">
<div>
<label className="label">
<span className="label-text">
Tanggal Awal
</span>
</label>
<div className="relative">
<DatePicker
selected={startDate}
onChange={(date) => {
setStartDate(date)
}}
format="dd/mm/yyyy"
className="input input-bordered"
nextMonthButtonLabel=">"
previousMonthButtonLabel="<"
/>
<div className="absolute right-2.5 rounded-l-none y-0 flex items-center top-2.5">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
</div>
</div>
</div>
<div>
<label className="label">
<span className="label-text">
Tanggal Akhir
</span>
</label>
<div className="relative">
<DatePicker
selected={endDate}
onChange={(date) => {
setEndDate(date)
}}
format="dd/mm/yyyy"
className="input input-bordered"
nextMonthButtonLabel=">"
previousMonthButtonLabel="<"
/>
<div className="absolute right-2.5 rounded-l-none y-0 flex items-center top-2.5">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
</div>
</div>
</div>
</div>
</div>
<div className="overflow-x-auto">
<table className="table w-full table-zebra">
<thead>
<tr>
<th>Tanggal</th>
<th>Nama Karyawan</th>
<th>Kontak</th>
<th>Total</th>
<th>Jumlah Item</th>
</tr>
</thead>
<tbody>
{payrolls.map((payroll) => (
<tr key={payroll.id}>
<th>
{formatDate(payroll.date)}
</th>
<td>{payroll.employee.name}</td>
<td>{payroll.employee.whatsapp}</td>
<td>
{formatIDR(payroll.recived)}
</td>
<td>
{formatIDR(payroll.item_count)}
</td>
</tr>
))}
</tbody>
</table>
</div>
<Pagination links={links} />
</div>
</div>
</div>
</div>
</Authenticated>

@ -7,6 +7,7 @@ use App\Http\Controllers\UserController;
use App\Http\Controllers\ProductController;
use App\Http\Controllers\EmployeeController;
use App\Http\Controllers\PayrollController;
use App\Http\Controllers\ReportController;
use App\Models\User;
/*
@ -50,11 +51,13 @@ Route::middleware(['auth'])->group(function () {
Route::delete('/employees/{employee}', [EmployeeController::class, 'destroy'])->name('employees.destroy');
Route::get('/payrolls', [PayrollController::class, 'index'])->name('payrolls.index');
Route::get('/payrolls/create', [PayrollController::class, 'create'])->name('payrolls.create');
Route::post('/payrolls', [PayrollController::class, 'store'])->name('payrolls.store');
Route::get('/payrolls/{payroll}', [PayrollController::class, 'edit'])->name('payrolls.edit');
Route::put('/payrolls/{payroll}', [PayrollController::class, 'update'])->name('payrolls.update');
Route::delete('/payrolls/{payroll}', [PayrollController::class, 'destroy'])->name('payrolls.destroy');
Route::get('/report', fn () => inertia('Report'))->name('report');
Route::get('/report', [ReportController::class, 'index'])->name('report');
});
require __DIR__.'/auth.php';

Loading…
Cancel
Save