Merge pull request 'Add electricity bills module (API, model, PDF)' (#156) from that-bill into master
Reviewed-on: #156
This commit is contained in:
commit
beccf5796c
|
|
@ -0,0 +1,295 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Admin\app\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Traits\HasFilterRequest;
|
||||
use App\Traits\HasOrderByRequest;
|
||||
use App\Traits\HasSearchRequest;
|
||||
use Illuminate\Http\Request;
|
||||
use Modules\Admin\app\Models\ElectricityBill;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ElectricityBillController extends Controller
|
||||
{
|
||||
use HasOrderByRequest;
|
||||
use HasFilterRequest;
|
||||
use HasSearchRequest;
|
||||
|
||||
/**
|
||||
* Get all electricity bills with pagination
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
try {
|
||||
$bills = new ElectricityBill;
|
||||
|
||||
// Order by
|
||||
$this->orderByRequest($bills, $request);
|
||||
|
||||
// Filter
|
||||
$this->filterRequest(
|
||||
builder: $bills,
|
||||
request: $request,
|
||||
filterKeys: [
|
||||
'billing_date' => self::F_TEXT,
|
||||
]
|
||||
);
|
||||
|
||||
// Search
|
||||
$this->searchRequest(
|
||||
builder: $bills,
|
||||
value: $request->get('search'),
|
||||
fields: ['billing_date', 'notes']
|
||||
);
|
||||
|
||||
$responseData = $bills
|
||||
->leftJoin('users as creator', 'electricity_bills.created_by', '=', 'creator.id')
|
||||
->leftJoin('users as updater', 'electricity_bills.updated_by', '=', 'updater.id')
|
||||
->orderBy('electricity_bills.billing_date', 'desc')
|
||||
->select(
|
||||
'electricity_bills.*',
|
||||
'creator.name as creator_name',
|
||||
'updater.name as updater_name'
|
||||
)
|
||||
->paginate($request->get('per_page', 15));
|
||||
|
||||
return $this->ResultSuccess($responseData);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching electricity bills: ' . $e->getMessage());
|
||||
return $this->ResultError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new electricity bill
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'billing_date' => 'required|string',
|
||||
'previous_reading' => 'required|numeric|min:0',
|
||||
'current_reading' => 'required|numeric|min:0',
|
||||
'unit_price' => 'required|numeric|min:0',
|
||||
'notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
// Check if billing_date already exists
|
||||
$existingBill = ElectricityBill::where('billing_date', $validated['billing_date'])->first();
|
||||
if ($existingBill) {
|
||||
return $this->ResultError('Bill for this month already exists', 422);
|
||||
}
|
||||
|
||||
// Calculate total amount
|
||||
$consumption = $validated['current_reading'] - $validated['previous_reading'];
|
||||
$totalAmount = $consumption * $validated['unit_price'];
|
||||
|
||||
$bill = ElectricityBill::create([
|
||||
'billing_date' => $validated['billing_date'],
|
||||
'previous_reading' => $validated['previous_reading'],
|
||||
'current_reading' => $validated['current_reading'],
|
||||
'unit_price' => $validated['unit_price'],
|
||||
'total_amount' => $totalAmount,
|
||||
'notes' => $validated['notes'] ?? null,
|
||||
'created_by' => auth('admins')->user()->id ?? null,
|
||||
]);
|
||||
|
||||
return $this->ResultSuccess($bill, 'Electricity bill created successfully');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating electricity bill: ' . $e->getMessage());
|
||||
return $this->ResultError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update electricity bill
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'billing_date' => 'sometimes|string',
|
||||
'previous_reading' => 'sometimes|numeric|min:0',
|
||||
'current_reading' => 'sometimes|numeric|min:0',
|
||||
'unit_price' => 'sometimes|numeric|min:0',
|
||||
'notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$bill = ElectricityBill::findOrFail($id);
|
||||
|
||||
// Check if billing_date already exists (excluding current record)
|
||||
if (isset($validated['billing_date'])) {
|
||||
$existingBill = ElectricityBill::where('billing_date', $validated['billing_date'])
|
||||
->where('id', '!=', $id)
|
||||
->first();
|
||||
if ($existingBill) {
|
||||
return $this->ResultError('Bill for this month already exists', 422);
|
||||
}
|
||||
}
|
||||
|
||||
// Recalculate total if any reading or price changed
|
||||
$previousReading = $validated['previous_reading'] ?? $bill->previous_reading;
|
||||
$currentReading = $validated['current_reading'] ?? $bill->current_reading;
|
||||
$unitPrice = $validated['unit_price'] ?? $bill->unit_price;
|
||||
$consumption = $currentReading - $previousReading;
|
||||
$totalAmount = $consumption * $unitPrice;
|
||||
|
||||
$bill->update(array_merge($validated, [
|
||||
'total_amount' => $totalAmount,
|
||||
'updated_by' => auth('admins')->user()->id ?? null,
|
||||
]));
|
||||
|
||||
return $this->ResultSuccess($bill, 'Electricity bill updated successfully');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating electricity bill: ' . $e->getMessage());
|
||||
return $this->ResultError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete electricity bill
|
||||
*/
|
||||
public function delete(Request $request, $id)
|
||||
{
|
||||
try {
|
||||
$bill = ElectricityBill::findOrFail($id);
|
||||
$bill->delete();
|
||||
|
||||
return $this->ResultSuccess(null, 'Electricity bill deleted successfully');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting electricity bill: ' . $e->getMessage());
|
||||
return $this->ResultError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export electricity bill to PDF
|
||||
*/
|
||||
public function exportPdf(Request $request, $id)
|
||||
{
|
||||
try {
|
||||
$bill = ElectricityBill::findOrFail($id);
|
||||
|
||||
// Get month name from billing_date
|
||||
$consumption = $bill->current_reading - $bill->previous_reading;
|
||||
$totalText = $this->numberToVietnamese($bill->total_amount);
|
||||
$date = Carbon::parse($bill->billing_date);
|
||||
|
||||
$dateNow = 'Ngày ' . $date->day .
|
||||
' tháng ' . $date->month .
|
||||
' năm ' . $date->year;
|
||||
|
||||
// Generate PDF
|
||||
$pdf = Pdf::loadView('admin::admin.electricity_bills.pdf', [
|
||||
'bill' => $bill,
|
||||
'consumption' => $consumption,
|
||||
'dateNow' => $dateNow,
|
||||
'totalText' => $totalText
|
||||
]);
|
||||
|
||||
$fileName = 'electricity_bill_' . $bill->billing_date . '.pdf';
|
||||
$filePath = 'electricity_bills/' . $fileName;
|
||||
|
||||
// đảm bảo folder tồn tại
|
||||
if (!Storage::disk('public')->exists('electricity_bills')) {
|
||||
Storage::disk('public')->makeDirectory('electricity_bills');
|
||||
}
|
||||
|
||||
// 👇 render 1 lần
|
||||
$pdfContent = $pdf->output();
|
||||
|
||||
// 👇 lưu file
|
||||
Storage::disk('public')->put($filePath, $pdfContent);
|
||||
|
||||
// update DB
|
||||
$bill->update(['file_path' => $filePath]);
|
||||
|
||||
// 👇 trả về đúng file đã tạo
|
||||
return response($pdfContent)
|
||||
->header('Content-Type', 'application/pdf')
|
||||
->header('Content-Disposition', 'attachment; filename="' . $fileName . '"');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error exporting electricity bill to PDF: ' . $e->getMessage());
|
||||
return $this->ResultError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get electricity bill by ID
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
try {
|
||||
$bill = ElectricityBill::with(['creator', 'updater'])->findOrFail($id);
|
||||
return $this->ResultSuccess($bill);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching electricity bill: ' . $e->getMessage());
|
||||
return $this->ResultError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
function numberToVietnamese($number)
|
||||
{
|
||||
$units = ["", "một", "hai", "ba", "bốn", "năm", "sáu", "bảy", "tám", "chín"];
|
||||
$levels = ["", "nghìn", "triệu", "tỷ"];
|
||||
|
||||
if ($number == 0) return "không đồng";
|
||||
|
||||
$number = (int)$number;
|
||||
$result = "";
|
||||
$level = 0;
|
||||
|
||||
while ($number > 0) {
|
||||
$threeDigits = $number % 1000;
|
||||
if ($threeDigits != 0) {
|
||||
$result = $this->readThreeDigits($threeDigits, $units) . " " . $levels[$level] . " " . $result;
|
||||
}
|
||||
$number = floor($number / 1000);
|
||||
$level++;
|
||||
}
|
||||
|
||||
return ucfirst(trim(preg_replace('/\s+/', ' ', $result))) . " đồng";
|
||||
}
|
||||
|
||||
function readThreeDigits($number, $units)
|
||||
{
|
||||
$hundreds = floor($number / 100);
|
||||
$tens = floor(($number % 100) / 10);
|
||||
$ones = $number % 10;
|
||||
|
||||
$result = "";
|
||||
|
||||
if ($hundreds > 0) {
|
||||
$result .= $units[$hundreds] . " trăm";
|
||||
if ($tens == 0 && $ones > 0) {
|
||||
$result .= " lẻ";
|
||||
}
|
||||
}
|
||||
|
||||
if ($tens > 1) {
|
||||
$result .= " " . $units[$tens] . " mươi";
|
||||
if ($ones == 1) {
|
||||
$result .= " mốt";
|
||||
} elseif ($ones == 5) {
|
||||
$result .= " lăm";
|
||||
} elseif ($ones > 0) {
|
||||
$result .= " " . $units[$ones];
|
||||
}
|
||||
} elseif ($tens == 1) {
|
||||
$result .= " mười";
|
||||
if ($ones == 5) {
|
||||
$result .= " lăm";
|
||||
} elseif ($ones > 0) {
|
||||
$result .= " " . $units[$ones];
|
||||
}
|
||||
} elseif ($ones > 0) {
|
||||
$result .= " " . $units[$ones];
|
||||
}
|
||||
|
||||
return trim($result);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Admin\app\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Traits\HasCacheModel;
|
||||
|
||||
class ElectricityBill extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use HasCacheModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->table = 'electricity_bills';
|
||||
$this->guarded = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate total amount based on reading difference and unit price
|
||||
*/
|
||||
public function calculateTotal(): float
|
||||
{
|
||||
$consumption = $this->current_reading - $this->previous_reading;
|
||||
return round($consumption * $this->unit_price, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get consumption in kWh
|
||||
*/
|
||||
public function getConsumption(): float
|
||||
{
|
||||
return $this->current_reading - $this->previous_reading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user who created this record
|
||||
*/
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class, 'created_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user who updated this record
|
||||
*/
|
||||
public function updater()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class, 'updated_by');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="vi">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Bảng kê thanh toán tiền điện</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: DejaVu Sans, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.mt-10 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.mt-20 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 15px;
|
||||
}
|
||||
table, th, td {
|
||||
border: 1px solid #000;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
.no-border {
|
||||
border: none;
|
||||
}
|
||||
.signature {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
}
|
||||
.signature td {
|
||||
border: none;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h3 class="text-center">BẢNG KÊ THANH TOÁN TIỀN ĐIỆN</h3>
|
||||
<p class="text-center">({{ $dateNow ?? '' }})</p>
|
||||
|
||||
<div class="mt-20">
|
||||
<p>- Tên doanh nghiệp: Công ty TNHH Kỹ Thuật Công Nghệ APAC</p>
|
||||
<p>- Mã số thuế: 0110038408</p>
|
||||
<p>- Địa chỉ: Số 219/26/3 đường Lĩnh Nam, Phường Vĩnh Hưng, thành phố Hà Nội, Việt Nam</p>
|
||||
<p>- Tên chủ sở hữu cho thuê địa điểm sản xuất kinh doanh: Lâm Văn Mười</p>
|
||||
<p>- Địa chỉ thuê: 50B31 tại Khu dân cư 91B giai đoạn 3, phường Tân An, thành phố Cần Thơ</p>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Số điện đầu kỳ</th>
|
||||
<th>Số điện cuối kỳ</th>
|
||||
<th>Số điện tiêu thụ</th>
|
||||
<th>Đơn giá</th>
|
||||
<th>Thành tiền</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ number_format($bill->previous_reading) ?? 0 }}</td>
|
||||
<td>{{ number_format($bill->current_reading) ?? 0 }}</td>
|
||||
<td>{{ $consumption ?? 0 }}</td>
|
||||
<td>{{ number_format($bill->unit_price) ?? '0' }}</td>
|
||||
<td>{{ number_format($bill->total_amount) ?? '0' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p class="mt-20">
|
||||
- Tổng tiền thanh toán: <strong>{{ number_format($bill->total_amount) ?? '0' }} VND</strong>
|
||||
({{ $totalText ?? '' }})
|
||||
</p>
|
||||
|
||||
<table class="signature no-border">
|
||||
<tr>
|
||||
<td>
|
||||
Người lập bảng kê<br>
|
||||
(Ký, ghi rõ họ tên)
|
||||
</td>
|
||||
<td>
|
||||
Đại diện doanh nghiệp<br>
|
||||
(Ký, ghi rõ họ tên)
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -23,6 +23,7 @@ use Modules\Admin\app\Http\Controllers\ProjectReviewController;
|
|||
use Modules\Admin\app\Http\Controllers\ProfileController;
|
||||
use Modules\Admin\app\Http\Controllers\TechnicalController;
|
||||
use Modules\Admin\app\Http\Controllers\TestCaseForSprintController;
|
||||
use Modules\Admin\app\Http\Controllers\ElectricityBillController;
|
||||
use Modules\Admin\app\Http\Middleware\AdminMiddleware;
|
||||
|
||||
/*
|
||||
|
|
@ -173,6 +174,18 @@ Route::middleware('api')
|
|||
Route::post('/handle-ticket', [TicketController::class, 'handleTicket'])->middleware('check.permission:admin');
|
||||
});
|
||||
|
||||
// Electricity Bills
|
||||
Route::group([
|
||||
'prefix' => 'electricity-bill',
|
||||
], function () {
|
||||
Route::get('/', [ElectricityBillController::class, 'index'])->middleware('check.permission:admin.hr.staff.accountant');
|
||||
Route::get('/{id}', [ElectricityBillController::class, 'show'])->middleware('check.permission:admin.hr.staff.accountant');
|
||||
Route::post('/create', [ElectricityBillController::class, 'create'])->middleware('check.permission:admin.hr.staff.accountant');
|
||||
Route::put('/{id}', [ElectricityBillController::class, 'update'])->middleware('check.permission:admin.hr.staff.accountant');
|
||||
Route::get('/delete/{id}', [ElectricityBillController::class, 'delete'])->middleware('check.permission:admin.hr.staff.accountant');
|
||||
Route::get('/export-pdf/{id}', [ElectricityBillController::class, 'exportPdf'])->middleware('check.permission:admin.hr.staff.accountant');
|
||||
});
|
||||
|
||||
Route::group([
|
||||
'prefix' => 'profile',
|
||||
], function () {
|
||||
|
|
|
|||
|
|
@ -9,4 +9,21 @@ use Illuminate\Routing\Controller as BaseController;
|
|||
class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, ValidatesRequests;
|
||||
|
||||
protected function ResultSuccess($data = null, $message = 'Success', $code = 200)
|
||||
{
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => $message,
|
||||
'data' => $data
|
||||
], $code);
|
||||
}
|
||||
|
||||
protected function ResultError($message = 'Error', $code = 500)
|
||||
{
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => $message
|
||||
], $code);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,16 @@
|
|||
"name": "laravel/laravel",
|
||||
"type": "project",
|
||||
"description": "The skeleton application for the Laravel framework.",
|
||||
"keywords": ["laravel", "framework"],
|
||||
"keywords": [
|
||||
"laravel",
|
||||
"framework"
|
||||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"barryvdh/laravel-debugbar": "^3.9",
|
||||
"barryvdh/laravel-dompdf": "^2.0",
|
||||
"doctrine/dbal": "^3.10",
|
||||
"drnxloc/laravel-simple-html-dom": "^1.9",
|
||||
"guzzlehttp/guzzle": "^7.8",
|
||||
"laravel/framework": "^10.10",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('electricity_bills', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->date('billing_date')->comment('Ngày lập hóa đơn');
|
||||
$table->decimal('previous_reading', 12, 2)->comment('Số điện kỳ trước');
|
||||
$table->decimal('current_reading', 12, 2)->comment('Số điện kỳ này');
|
||||
$table->decimal('unit_price', 12, 2)->comment('Đơn giá điện');
|
||||
$table->decimal('total_amount', 12, 2)->comment('Tổng tiền điện');
|
||||
$table->string('notes')->nullable()->comment('Ghi chú');
|
||||
$table->string('file_path')->nullable()->comment('Đường dẫn file PDF');
|
||||
$table->unsignedBigInteger('created_by')->nullable();
|
||||
$table->unsignedBigInteger('updated_by')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('created_by')->references('id')->on('users')->onDelete('set null');
|
||||
$table->foreign('updated_by')->references('id')->on('users')->onDelete('set null');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('electricity_bills');
|
||||
}
|
||||
};
|
||||
|
|
@ -125,6 +125,19 @@ export const deleteDocument = API_URL + 'v1/admin/document/delete'
|
|||
// Download File
|
||||
export const downloadFile = API_URL + 'v1/admin/download-file'
|
||||
|
||||
// Electricity Bills
|
||||
export const getElectricityBills = API_URL + 'v1/admin/electricity-bill'
|
||||
export const getElectricityBillById = (id: number) =>
|
||||
API_URL + `v1/admin/electricity-bill/${id}`
|
||||
export const createElectricityBill =
|
||||
API_URL + 'v1/admin/electricity-bill/create'
|
||||
export const updateElectricityBill = (id: number) =>
|
||||
API_URL + `v1/admin/electricity-bill/${id}`
|
||||
export const deleteElectricityBill = (id: number) =>
|
||||
API_URL + `v1/admin/electricity-bill/delete/${id}`
|
||||
export const exportElectricityBillPdf = (id: number) =>
|
||||
API_URL + `v1/admin/electricity-bill/export-pdf/${id}`
|
||||
|
||||
// Files APIs
|
||||
export const getFiles = API_URL + 'v1/admin/profile/files'
|
||||
export const uploadFiles = API_URL + 'v1/admin/profile/upload-files'
|
||||
|
|
|
|||
|
|
@ -250,7 +250,14 @@ export const DataTableAll = ({
|
|||
if (query !== '') {
|
||||
setTData(
|
||||
data.filter((obj) =>
|
||||
Object.values(obj)?.find((c: any) => c.toString().normalize('NFC').toLowerCase().includes(query.normalize('NFC').toLowerCase())))
|
||||
Object.values(obj)?.find((c: any) =>
|
||||
c
|
||||
.toString()
|
||||
.normalize('NFC')
|
||||
.toLowerCase()
|
||||
.includes(query.normalize('NFC').toLowerCase()),
|
||||
),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
if (pagination) {
|
||||
|
|
@ -456,7 +463,7 @@ export const DataTablePagination = ({
|
|||
})
|
||||
const [selectedRows, setSelectedRows] = useState<any[]>([])
|
||||
const navigate = useNavigate()
|
||||
const urlParams = new URLSearchParams(location.search)
|
||||
let urlParams = new URLSearchParams(location.search)
|
||||
|
||||
// Render headers
|
||||
const headers = columns.map((col) => (
|
||||
|
|
@ -596,7 +603,7 @@ export const DataTablePagination = ({
|
|||
|
||||
// Remove specific parameters
|
||||
params.delete(name)
|
||||
|
||||
urlParams.delete(name)
|
||||
// Update the URL without reloading the page
|
||||
window.history.replaceState({}, document.title, url.toString())
|
||||
}
|
||||
|
|
@ -628,9 +635,9 @@ export const DataTablePagination = ({
|
|||
Array.isArray(dataFilter[key])
|
||||
? dataFilter[key]
|
||||
: key === 'to_date'
|
||||
? Math.floor(dataFilter[key].getTime() / 1000) +
|
||||
(60 * 60 * 23 + 60 * 59 + 59)
|
||||
: Math.floor(dataFilter[key].getTime() / 1000),
|
||||
? Math.floor(dataFilter[key].getTime() / 1000) +
|
||||
(60 * 60 * 23 + 60 * 59 + 59)
|
||||
: Math.floor(dataFilter[key].getTime() / 1000),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
@ -660,9 +667,8 @@ export const DataTablePagination = ({
|
|||
date_used_to: date_used,
|
||||
})
|
||||
}
|
||||
|
||||
// Add all attributes in 'params' to URL params
|
||||
Object.entries(params).forEach((param) => urlParams.set(...param))
|
||||
urlParams = new URLSearchParams(Object.entries(params))
|
||||
Object.entries(dataFilter).forEach(([key, value]) => {
|
||||
const typeFilter = filterInfo.find((o) => o.key === key).type
|
||||
const hasType = {
|
||||
|
|
@ -679,17 +685,16 @@ export const DataTablePagination = ({
|
|||
if (hasType.Date) {
|
||||
value = value ? Date.parse(String(value)) / 1000 : '' // to unix timestamp
|
||||
}
|
||||
|
||||
console.log(String(value))
|
||||
String(value).length
|
||||
? urlParams.set(key, String(value))
|
||||
: urlParams.delete(key)
|
||||
})
|
||||
|
||||
// Request to get data API
|
||||
const res = await get(url, Object.fromEntries(urlParams.entries()))
|
||||
if (res.status) {
|
||||
setBaseData(res)
|
||||
setTData(res.data)
|
||||
if (res.status || res.success) {
|
||||
setBaseData(res.success ? res?.data : res)
|
||||
setTData(res.success ? res.data.data : res.data)
|
||||
setSkeletion(false)
|
||||
navigate({
|
||||
pathname: location.pathname,
|
||||
|
|
@ -765,7 +770,7 @@ export const DataTablePagination = ({
|
|||
|
||||
if (order_by_) {
|
||||
const sortParam = {
|
||||
name: order_by_.split('=')[0].split('_')[2],
|
||||
name: order_by_.split('=')[0].split('_').slice(2).join('_'),
|
||||
status: order_by_.split('=')[1],
|
||||
}
|
||||
if (JSON.stringify(sortParam) !== JSON.stringify(statusSort)) {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import {
|
|||
IconReport,
|
||||
IconScan,
|
||||
IconSettings,
|
||||
IconShredder,
|
||||
IconSun,
|
||||
IconTicket,
|
||||
IconUsersGroup,
|
||||
|
|
@ -156,6 +157,13 @@ const data = [
|
|||
group: 'admin',
|
||||
permissions: 'admin,accountant',
|
||||
},
|
||||
{
|
||||
link: '/office-support',
|
||||
label: 'Office Support',
|
||||
icon: IconShredder,
|
||||
group: 'other',
|
||||
permissions: 'admin,hr,accountant',
|
||||
},
|
||||
// { link: '/jira', label: 'Jira', icon: IconSubtask },
|
||||
// { link: '/custom-theme', label: 'Custom Theme', icon: IconBrush },
|
||||
// { link: '/general-setting', label: 'General Setting', icon: IconSettings },
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
.title {
|
||||
background-color: light-dark(var(white), var(--mantine-color-dark-7));
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 var(--mantine-spacing-sm) var(--mantine-spacing-lg)
|
||||
var(--mantine-spacing-sm);
|
||||
border-bottom: solid rgba(201, 201, 201, 0.377) 1px;
|
||||
}
|
||||
|
||||
.optionIcon {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.deleteIcon {
|
||||
color: red;
|
||||
cursor: pointer;
|
||||
padding: 2px;
|
||||
border-radius: 25%;
|
||||
}
|
||||
|
||||
|
||||
.editIcon {
|
||||
color: rgb(9, 132, 132);
|
||||
cursor: pointer;
|
||||
padding: 2px;
|
||||
border-radius: 25%;
|
||||
}
|
||||
|
||||
.editIcon:hover {
|
||||
background-color: rgba(203, 203, 203, 0.809);
|
||||
}
|
||||
|
||||
.deleteIcon:hover {
|
||||
background-color: rgba(203, 203, 203, 0.809);
|
||||
}
|
||||
|
||||
.dialog {
|
||||
background-color: light-dark(white, #2d353c);
|
||||
text-align: center;
|
||||
border: solid 1px rgb(255, 145, 0);
|
||||
}
|
||||
|
||||
.dialogText {
|
||||
color: light-dark(#2d353c, white);
|
||||
}
|
||||
|
|
@ -0,0 +1,655 @@
|
|||
import {
|
||||
createElectricityBill,
|
||||
deleteElectricityBill,
|
||||
exportElectricityBillPdf,
|
||||
getElectricityBills,
|
||||
updateElectricityBill,
|
||||
} from '@/api/Admin'
|
||||
import { DataTablePagination } from '@/components/DataTable/DataTable'
|
||||
import { Xdelete } from '@/rtk/helpers/CRUD'
|
||||
import { get, post, put } from '@/rtk/helpers/apiService'
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Dialog,
|
||||
Group,
|
||||
Modal,
|
||||
NumberInput,
|
||||
Select,
|
||||
Text,
|
||||
Textarea,
|
||||
Tabs,
|
||||
Flex,
|
||||
ActionIcon,
|
||||
} from '@mantine/core'
|
||||
import { useForm } from '@mantine/form'
|
||||
import { notifications } from '@mantine/notifications'
|
||||
import {
|
||||
IconDownload,
|
||||
IconEdit,
|
||||
IconFileInvoice,
|
||||
IconHistory,
|
||||
IconTrash,
|
||||
} from '@tabler/icons-react'
|
||||
import moment from 'moment'
|
||||
import { useEffect, useState } from 'react'
|
||||
import classes from './OfficeSupport.module.css'
|
||||
import { _NOTIFICATION_MESS } from '@/rtk/helpers/notificationMess'
|
||||
import { getHeaderInfo } from '@/rtk/helpers/tokenCreator'
|
||||
import { DateInput, DateTimePicker } from '@mantine/dates'
|
||||
|
||||
interface ElectricityBill {
|
||||
id: number
|
||||
billing_date: string
|
||||
previous_reading: number
|
||||
current_reading: number
|
||||
unit_price: number
|
||||
total_amount: number
|
||||
notes: string | null
|
||||
file_path: string | null
|
||||
created_by: number | null
|
||||
updated_by: number | null
|
||||
created_at: string | null
|
||||
updated_at: string | null
|
||||
creator_name?: string
|
||||
updater_name?: string
|
||||
}
|
||||
|
||||
interface ElectricityBillsResponse {
|
||||
data: ElectricityBill[]
|
||||
current_page: number
|
||||
last_page: number
|
||||
per_page: number
|
||||
total: number
|
||||
}
|
||||
|
||||
const OfficeSupport = () => {
|
||||
const [activeTab, setActiveTab] = useState<string | null>('calculate')
|
||||
const [listBills, setListBills] = useState<ElectricityBillsResponse>({
|
||||
data: [],
|
||||
current_page: 1,
|
||||
last_page: 1,
|
||||
per_page: 15,
|
||||
total: 0,
|
||||
})
|
||||
const [action, setAction] = useState('')
|
||||
const [item, setItem] = useState<ElectricityBill | null>(null)
|
||||
const [activeBtn, setActiveBtn] = useState(false)
|
||||
const [disableBtn, setDisableBtn] = useState(false)
|
||||
|
||||
const [confirmModal, setConfirmModal] = useState(false)
|
||||
const [confirmMessage, setConfirmMessage] = useState('')
|
||||
const [confirmValues, setConfirmValues] = useState<any>(null)
|
||||
const [confirmLoading, setConfirmLoading] = useState(false)
|
||||
const filterInfo: any[] = []
|
||||
|
||||
const getAllBills = async (page: number = 1) => {
|
||||
try {
|
||||
const params = { page }
|
||||
const res = await get(getElectricityBills, params)
|
||||
if (res?.data) {
|
||||
setListBills(res?.data)
|
||||
}
|
||||
} catch (error: any) {
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
message: error.message ?? error,
|
||||
color: 'red',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllBills()
|
||||
}, [])
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'billing_date',
|
||||
size: '15%',
|
||||
header: 'Date',
|
||||
render: (row: ElectricityBill) => {
|
||||
const date = new Date(row.billing_date)
|
||||
return <Text fz={'sm'}>{moment(date).format('DD MMMM YYYY')}</Text>
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'previous_reading',
|
||||
size: '15%',
|
||||
header: 'Previous Reading',
|
||||
render: (row: ElectricityBill) => (
|
||||
<Text fz={'sm'}>
|
||||
{Number(row.previous_reading)?.toLocaleString()} kWh
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'current_reading',
|
||||
size: '15%',
|
||||
header: 'Current Reading',
|
||||
render: (row: ElectricityBill) => (
|
||||
<Text fz={'sm'}>
|
||||
{Number(row.current_reading)?.toLocaleString()} kWh
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '#',
|
||||
size: '10%',
|
||||
header: 'Consumption',
|
||||
render: (row: ElectricityBill) => {
|
||||
const consumption =
|
||||
Number(row.current_reading) - Number(row.previous_reading)
|
||||
return (
|
||||
<Text fz={'sm'} fw={600}>
|
||||
{consumption.toLocaleString()} kWh
|
||||
</Text>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'unit_price',
|
||||
size: '10%',
|
||||
header: 'Unit Price',
|
||||
render: (row: ElectricityBill) => (
|
||||
<Text fz={'sm'}>
|
||||
{Number(row.unit_price)?.toLocaleString(undefined, {
|
||||
minimumFractionDigits: 0,
|
||||
})}{' '}
|
||||
VNĐ
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'total_amount',
|
||||
size: '15%',
|
||||
header: 'Total Amount',
|
||||
render: (row: ElectricityBill) => (
|
||||
<Text fz={'sm'} fw={700} c="green">
|
||||
{Number(row.total_amount)?.toLocaleString(undefined, {
|
||||
minimumFractionDigits: 0,
|
||||
})}{' '}
|
||||
VNĐ
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
size: '15%',
|
||||
header: 'Actions',
|
||||
render: (row: ElectricityBill) => {
|
||||
return (
|
||||
<Group gap="xs">
|
||||
<ActionIcon
|
||||
disabled={disableBtn}
|
||||
onClick={() => handleExportPdf(row.id, row.billing_date)}
|
||||
variant="outline"
|
||||
w={20}
|
||||
h={20}
|
||||
color={'blue'}
|
||||
>
|
||||
<IconDownload className={classes.deleteIcon} color="blue" />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
disabled={disableBtn}
|
||||
onClick={() => {
|
||||
setItem(row)
|
||||
setAction('edit')
|
||||
form.setFieldValue(
|
||||
'billing_date',
|
||||
row?.billing_date || moment().format('YYYY-MM-DD'),
|
||||
)
|
||||
form.setFieldValue(
|
||||
'current_reading',
|
||||
Number(row?.current_reading) || 0,
|
||||
)
|
||||
form.setFieldValue(
|
||||
'previous_reading',
|
||||
Number(row?.previous_reading) || 0,
|
||||
)
|
||||
form.setFieldValue('unit_price', row?.unit_price || 4000)
|
||||
form.setFieldValue('notes', row?.notes || '')
|
||||
}}
|
||||
variant="outline"
|
||||
w={20}
|
||||
h={20}
|
||||
color={'green'}
|
||||
>
|
||||
<IconEdit className={classes.deleteIcon} color="green" />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
disabled={disableBtn}
|
||||
onClick={() => {
|
||||
setAction('delete')
|
||||
setItem(row)
|
||||
}}
|
||||
variant="outline"
|
||||
w={20}
|
||||
h={20}
|
||||
color={'red'}
|
||||
>
|
||||
<IconTrash className={classes.deleteIcon} color="red" />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
)
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const handleCreate = async (values: any) => {
|
||||
try {
|
||||
setDisableBtn(true)
|
||||
|
||||
const params = {
|
||||
billing_date: values.billing_date,
|
||||
previous_reading: values.previous_reading,
|
||||
current_reading: values.current_reading,
|
||||
unit_price: values.unit_price,
|
||||
notes: values.notes || null,
|
||||
}
|
||||
|
||||
let res
|
||||
if (action === 'add') {
|
||||
res = await post(createElectricityBill, params)
|
||||
} else if (action === 'edit' && item) {
|
||||
res = await put(updateElectricityBill(item.id), params)
|
||||
}
|
||||
|
||||
if (res?.success) {
|
||||
notifications.show({
|
||||
title: 'Success',
|
||||
message:
|
||||
action === 'add'
|
||||
? _NOTIFICATION_MESS.create_success
|
||||
: 'Updated successfully',
|
||||
color: 'green',
|
||||
})
|
||||
setAction('')
|
||||
form.reset()
|
||||
|
||||
// Auto export PDF after creating
|
||||
if (action === 'add' && res.data?.id) {
|
||||
handleExportPdf(res.data.id, res.data.billing_date)
|
||||
}
|
||||
|
||||
getAllBills()
|
||||
} else if (!res?.success && res?.errors) {
|
||||
if (!res?.data?.success && res?.data?.message) {
|
||||
setConfirmMessage(res.data?.message)
|
||||
setConfirmValues(values)
|
||||
setConfirmModal(true)
|
||||
} else {
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
message: res.message ?? _NOTIFICATION_MESS.create_error,
|
||||
color: 'red',
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.response?.message) {
|
||||
const errorMess = error.response.message
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
message: errorMess,
|
||||
color: 'red',
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
setDisableBtn(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
await Xdelete(deleteElectricityBill(id), {}, () => getAllBills())
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleExportPdf = async (id: number, date: string) => {
|
||||
try {
|
||||
setDisableBtn(true)
|
||||
const header = await getHeaderInfo()
|
||||
const res = await fetch(exportElectricityBillPdf(id), { ...header })
|
||||
|
||||
if (!res.ok) throw new Error('Export failed')
|
||||
|
||||
const blob = await res.blob()
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const newDate = moment(new Date(date)).format('DD-M-YYYY')
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `Bảng thanh toán tiền điện APAC - ${newDate}.pdf`
|
||||
a.click()
|
||||
|
||||
// notifications.show({
|
||||
// title: 'Success',
|
||||
// message: 'PDF exported successfully',
|
||||
// color: 'green',
|
||||
// })
|
||||
setDisableBtn(false)
|
||||
} catch (error: any) {
|
||||
setDisableBtn(false)
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
message: error.message,
|
||||
color: 'red',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const getLastReading = () => {
|
||||
if (!listBills?.data?.length) return 0
|
||||
|
||||
const sorted = [...listBills.data].sort(
|
||||
(a, b) =>
|
||||
new Date(b.billing_date).getTime() - new Date(a.billing_date).getTime(),
|
||||
)
|
||||
return sorted[0] ? Number(sorted[0]?.current_reading) : 0
|
||||
}
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
id: 0,
|
||||
billing_date: moment().format('YYYY-MM-DD'),
|
||||
previous_reading: 0,
|
||||
current_reading: 0,
|
||||
unit_price: 4000,
|
||||
notes: '',
|
||||
},
|
||||
validate: {
|
||||
billing_date: (value) => (!value ? 'Date is required' : null),
|
||||
previous_reading: (value) =>
|
||||
value < 0 ? 'Previous reading must be positive' : null,
|
||||
current_reading: (value) =>
|
||||
value < 0 ? 'Current reading must be positive' : null,
|
||||
unit_price: (value) =>
|
||||
value <= 0 ? 'Unit price must be greater than 0' : null,
|
||||
},
|
||||
})
|
||||
|
||||
// Calculate preview
|
||||
const calculatePreview = () => {
|
||||
const consumption =
|
||||
form.values.current_reading - form.values.previous_reading
|
||||
const total = consumption * form.values.unit_price
|
||||
return { consumption, total }
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={classes.title}>
|
||||
<h3>Office Support</h3>
|
||||
</div>
|
||||
|
||||
<Box p={20}>
|
||||
<Tabs value={activeTab} onChange={setActiveTab}>
|
||||
<Tabs.List>
|
||||
<Tabs.Tab
|
||||
value="calculate"
|
||||
leftSection={<IconFileInvoice size={16} />}
|
||||
>
|
||||
Electricity Bill
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Panel value="calculate" pt="md">
|
||||
{/* Calculate Tab Content */}
|
||||
<Box>
|
||||
<Button
|
||||
m={5}
|
||||
onClick={() => {
|
||||
setAction('add')
|
||||
form.reset()
|
||||
form.setFieldValue('previous_reading', getLastReading())
|
||||
form.setFieldValue('current_reading', getLastReading())
|
||||
}}
|
||||
>
|
||||
+ Add New Bill
|
||||
</Button>
|
||||
</Box>
|
||||
{/* History Tab Content */}
|
||||
{listBills.data.length > 0 ? (
|
||||
<DataTablePagination
|
||||
filterInfo={filterInfo}
|
||||
data={listBills}
|
||||
columns={columns}
|
||||
searchInput
|
||||
size=""
|
||||
/>
|
||||
) : (
|
||||
<Text c="dimmed" ta="center" py="xl">
|
||||
No electricity bills found. Go to Calculate tab to add new bill.
|
||||
</Text>
|
||||
)}
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
{/* Add/Edit Modal */}
|
||||
<Modal
|
||||
opened={action === 'add' || action === 'edit'}
|
||||
onClose={() => {
|
||||
setAction('')
|
||||
setItem(null)
|
||||
form.reset()
|
||||
}}
|
||||
title={
|
||||
<Text pl={'sm'} fw={700} fz={'lg'}>
|
||||
{action === 'add' && 'Add Electricity Bill'}
|
||||
{action === 'edit' && 'Edit Electricity Bill'}
|
||||
</Text>
|
||||
}
|
||||
size="lg"
|
||||
>
|
||||
<form
|
||||
onSubmit={form.onSubmit(async (values) => {
|
||||
setDisableBtn(true)
|
||||
await handleCreate(values)
|
||||
setDisableBtn(false)
|
||||
})}
|
||||
>
|
||||
<Box pl={'md'} pr={'md'}>
|
||||
<DateInput
|
||||
required
|
||||
mb="md"
|
||||
label="Billing Date"
|
||||
placeholder="Pick date"
|
||||
valueFormat="DD-MM-YYYY"
|
||||
value={
|
||||
form.values.billing_date
|
||||
? new Date(form.values.billing_date)
|
||||
: null
|
||||
}
|
||||
error={form.errors.billing_date}
|
||||
onChange={(date) =>
|
||||
form.setFieldValue(
|
||||
'billing_date',
|
||||
date ? moment(date).format('YYYY-MM-DD') : '',
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<Flex gap={'md'}>
|
||||
<Box style={{ flex: 1 }}>
|
||||
<NumberInput
|
||||
required
|
||||
mb={'md'}
|
||||
label={'Previous Reading (kWh)'}
|
||||
value={form.values.previous_reading}
|
||||
error={form.errors.previous_reading}
|
||||
onChange={(e) =>
|
||||
form.setFieldValue('previous_reading', Number(e))
|
||||
}
|
||||
min={0}
|
||||
thousandSeparator=","
|
||||
/>
|
||||
</Box>
|
||||
<Box style={{ flex: 1 }}>
|
||||
<NumberInput
|
||||
required
|
||||
mb={'md'}
|
||||
label={'Current Reading (kWh)'}
|
||||
value={form.values.current_reading}
|
||||
error={form.errors.current_reading}
|
||||
onChange={(e) =>
|
||||
form.setFieldValue('current_reading', Number(e))
|
||||
}
|
||||
min={0}
|
||||
thousandSeparator=","
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<NumberInput
|
||||
required
|
||||
mb={'md'}
|
||||
label={'Unit Price (VNĐ/kWh)'}
|
||||
value={form.values.unit_price}
|
||||
error={form.errors.unit_price}
|
||||
onChange={(e) => form.setFieldValue('unit_price', Number(e))}
|
||||
min={0}
|
||||
thousandSeparator=","
|
||||
/>
|
||||
|
||||
{/* Preview */}
|
||||
<Box
|
||||
p="md"
|
||||
style={{
|
||||
backgroundColor: '#f8f9fa',
|
||||
borderRadius: '8px',
|
||||
marginBottom: '16px',
|
||||
}}
|
||||
>
|
||||
<Text fw={600} mb="sm">
|
||||
Preview:
|
||||
</Text>
|
||||
<Text>
|
||||
Consumption:{' '}
|
||||
<Text span fw={600}>
|
||||
{calculatePreview().consumption.toLocaleString()} kWh
|
||||
</Text>
|
||||
</Text>
|
||||
<Text>
|
||||
Total Amount:{' '}
|
||||
<Text span fw={700} c="green" size="lg">
|
||||
{calculatePreview().total.toLocaleString()} VNĐ
|
||||
</Text>
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box ta={'center'}>
|
||||
<Button
|
||||
mt={'lg'}
|
||||
bg={'green'}
|
||||
type="submit"
|
||||
disabled={disableBtn}
|
||||
>
|
||||
{action === 'add' ? 'Create & Export PDF' : 'Update'}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<Dialog
|
||||
className={classes.dialog}
|
||||
opened={action === 'delete'}
|
||||
withCloseButton
|
||||
onClose={() => setAction('')}
|
||||
size="lg"
|
||||
radius="md"
|
||||
position={{ top: 30, right: 10 }}
|
||||
>
|
||||
<Text className={classes.dialogText} size="sm" mb="xs" fw={500}>
|
||||
Do you want to delete this record?
|
||||
<Group justify="center" m={10}>
|
||||
<Button
|
||||
disabled={activeBtn}
|
||||
fw={700}
|
||||
size="xs"
|
||||
variant="light"
|
||||
onClick={async () => {
|
||||
setActiveBtn(true)
|
||||
if (item) {
|
||||
await handleDelete(item.id)
|
||||
}
|
||||
setActiveBtn(false)
|
||||
setAction('')
|
||||
setItem(null)
|
||||
}}
|
||||
>
|
||||
Yes
|
||||
</Button>
|
||||
<Button
|
||||
fw={700}
|
||||
size="xs"
|
||||
variant="light"
|
||||
onClick={() => {
|
||||
setAction('')
|
||||
setItem(null)
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Text>
|
||||
</Dialog>
|
||||
|
||||
{/* Confirm Modal */}
|
||||
<Modal
|
||||
opened={confirmModal}
|
||||
onClose={() => !confirmLoading && setConfirmModal(false)}
|
||||
title={
|
||||
<Text fw={700} fz="lg">
|
||||
Warning
|
||||
</Text>
|
||||
}
|
||||
centered
|
||||
closeOnClickOutside={!confirmLoading}
|
||||
closeOnEscape={!confirmLoading}
|
||||
>
|
||||
<Box p="md">
|
||||
<Text style={{ whiteSpace: 'pre-line' }} mb={20}>
|
||||
{confirmMessage}
|
||||
</Text>
|
||||
<Group justify="center">
|
||||
<Button
|
||||
color="green"
|
||||
loading={confirmLoading}
|
||||
onClick={async () => {
|
||||
if (confirmValues) {
|
||||
try {
|
||||
setConfirmLoading(true)
|
||||
await handleCreate(confirmValues)
|
||||
setConfirmLoading(false)
|
||||
setConfirmModal(false)
|
||||
} catch (error) {
|
||||
setConfirmLoading(false)
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
</Button>
|
||||
<Button
|
||||
color="red"
|
||||
disabled={confirmLoading}
|
||||
onClick={() => {
|
||||
setConfirmModal(false)
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Box>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default OfficeSupport
|
||||
|
|
@ -8,6 +8,7 @@ import PageLogin from '@/pages/Auth/Login/Login'
|
|||
import Document from '@/pages/Document/Document'
|
||||
import LeaveManagement from '@/pages/LeaveManagement/LeaveManagement'
|
||||
import PageNotFound from '@/pages/NotFound/NotFound'
|
||||
import OfficeSupport from '@/pages/OfficeSupport/OfficeSupport'
|
||||
import OrganizationSettings from '@/pages/OrganizationSettings/OrganizationSettings'
|
||||
import Profile from '@/pages/Profile/Profile'
|
||||
import SprintReview from '@/pages/SprintReview/SprintReview'
|
||||
|
|
@ -264,6 +265,20 @@ const mainRoutes = [
|
|||
</ProtectedRoute>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/office-support',
|
||||
element: (
|
||||
<ProtectedRoute mode="route" permission="admin,hr,accountant">
|
||||
<BasePage
|
||||
main={
|
||||
<>
|
||||
<OfficeSupport />
|
||||
</>
|
||||
}
|
||||
></BasePage>
|
||||
</ProtectedRoute>
|
||||
),
|
||||
},
|
||||
// {
|
||||
// path: '/packages',
|
||||
// element: (
|
||||
|
|
|
|||
|
|
@ -36,7 +36,11 @@ export const create = async (
|
|||
if (res.status === false) {
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
message: <div style={{ whiteSpace: 'pre-line' }}>{res.message ?? _NOTIFICATION_MESS.create_error}</div>,
|
||||
message: (
|
||||
<div style={{ whiteSpace: 'pre-line' }}>
|
||||
{res.message ?? _NOTIFICATION_MESS.create_error}
|
||||
</div>
|
||||
),
|
||||
color: 'red',
|
||||
})
|
||||
}
|
||||
|
|
@ -116,7 +120,7 @@ export const Xdelete = async (url: string, data: any, fnc?: () => void) => {
|
|||
try {
|
||||
const res = await get(url, data)
|
||||
|
||||
if (res.status) {
|
||||
if (res.status || res.success) {
|
||||
notifications.show({
|
||||
title: 'Success',
|
||||
message: _NOTIFICATION_MESS.delete_success,
|
||||
|
|
@ -124,7 +128,7 @@ export const Xdelete = async (url: string, data: any, fnc?: () => void) => {
|
|||
})
|
||||
fnc && fnc()
|
||||
}
|
||||
if (res.status === false) {
|
||||
if (res.status === false && !res.success) {
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
message: res.message ?? _NOTIFICATION_MESS.delete_error,
|
||||
|
|
|
|||
Loading…
Reference in New Issue