oke dah , kurang share , reminder send mail sama calender

original
ajikamaludin 2 years ago
parent df9ea70806
commit cffa0e509a
No known key found for this signature in database
GPG Key ID: E4F565A376B260B7

1
.gitignore vendored

@ -2,6 +2,7 @@
/public/build
/public/hot
/public/storage
/public/documents
/storage/*.key
/vendor
.env

@ -0,0 +1,165 @@
<?php
namespace App\Http\Controllers;
use App\Models\Department;
use App\Models\Document;
use App\Models\TypeDoc;
use Illuminate\Http\Request;
class DocumentController extends Controller
{
public function index(Request $request)
{
$query = Document::with(['department', 'type'])->orderBy('created_at');
if ($request->q != null || $request->q != '') {
$query->where('no_doc', 'like', '%'.$request->q.'%')
->orWhere('company_name', 'like', '%'.$request->q.'%')
->orWhere('pic_name', 'like', '%'.$request->q.'%')
->orWhere('email', 'like', '%'.$request->q.'%');
}
if ($request->department_id != ''){
$query->where('department_id', $request->department_id);
}
if ($request->status != ''){
$query->where('status', $request->status);
}
if ($request->type_doc_id != ''){
$query->where('type_doc_id', $request->type_doc_id);
}
return inertia('Document/Index', [
'docs' => $query->paginate(10),
'types' => TypeDoc::all(),
'departments' => Department::all(),
]);
}
public function create()
{
return inertia('Document/Form', [
'types' => TypeDoc::all(),
'departments' => Department::all(),
]);
}
public function store(Request $request)
{
$request->validate([
'no_doc' => 'required|string',
'company_name' => 'required|string',
'first_person_name' => 'required|string',
'second_person_name' => 'required|string',
'start_date' => 'required|date',
'end_date' => 'required|date',
'type_doc_id' => 'required|exists:type_docs,id',
'department_id' => 'required|exists:departments,id',
'pic_name' => 'required|string',
'email' => 'required|email',
// 'document' => 'required|file',
'note' => 'nullable',
'status' => 'required|numeric',
]);
$lastDocs = Document::orderBy('created_at', 'desc')->first();
$lastDocs = $lastDocs ? $lastDocs : Document::make(['no' => 0]);
$docs = Document::make([
'no' => $lastDocs->no + 1,
'no_doc' => $request->no_doc,
'company_name' => $request->company_name,
'first_person_name' => $request->first_person_name,
'second_person_name' => $request->second_person_name,
'start_date' => $request->start_date,
'end_date' => $request->end_date,
'pic_name' => $request->pic_name,
'email' => $request->email,
'note' => $request->note,
'type_doc_id' => $request->type_doc_id,
'department_id' => $request->department_id,
'status' => $request->status,
'user_id' => auth()->user()->id,
]);
// $file = $request->file('document');
// $file->store('documents', 'public');
$docs->document = '';
$docs->save();
return redirect()->route('docs.index')
->with('message', ['type' => 'success', 'message' => 'The data has beed saved']);
}
public function edit(Document $doc)
{
return inertia('Document/Form', [
'types' => TypeDoc::all(),
'departments' => Department::all(),
'doc' => $doc
]);
}
public function update(Request $request, Document $doc)
{
$request->validate([
'no_doc' => 'required|string',
'company_name' => 'required|string',
'first_person_name' => 'required|string',
'second_person_name' => 'required|string',
'start_date' => 'required|date',
'end_date' => 'required|date',
'type_doc_id' => 'required|exists:type_docs,id',
'department_id' => 'required|exists:departments,id',
'pic_name' => 'required|string',
'email' => 'required|email',
'document' => 'nullable|file',
'note' => 'nullable',
'status' => 'required|numeric',
]);
$doc->fill([
'no_doc' => $request->no_doc,
'company_name' => $request->company_name,
'first_person_name' => $request->first_person_name,
'second_person_name' => $request->second_person_name,
'start_date' => $request->start_date,
'end_date' => $request->end_date,
'pic_name' => $request->pic_name,
'email' => $request->email,
'note' => $request->note,
'type_doc_id' => $request->type_doc_id,
'department_id' => $request->department_id,
'status' => $request->status,
]);
$file = $request->file('document');
if($file != null) {
$file->store('documents', 'public');
$doc->document = $file->hashName();
}
$doc->save();
return redirect()->route('docs.index')
->with('message', ['type' => 'success', 'message' => 'The data has beed saved']);
}
public function show(Document $doc)
{
return inertia('Document/Detail', [
'doc' => $doc->load(['department', 'type', 'creator']),
'doc_url' => asset('document/'.$doc->document),
]);
}
public function destroy(Document $doc)
{
$doc->delete();
return redirect()->back();
}
}

@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers;
use App\Models\Document;
use Illuminate\Http\Request;
class GeneralController extends Controller
{
public function index()
{
return inertia('Dashboard', [
'count_active' => Document::where('status', Document::ACTIVE)->count(),
'count_update' => Document::where('status', Document::UPDATE)->count(),
'count_expired' => Document::where('status', Document::EXPIRED)->count(),
'count_total' => Document::count()
]);
}
}

@ -43,6 +43,9 @@ class HandleInertiaRequests extends Middleware
'location' => $request->url(),
]);
},
'flash' => [
'message' => fn () => $request->session()->get('message')
],
]);
}
}

@ -23,9 +23,19 @@ class Document extends Model
'email',
'note',
'document',
'status',
'user_id',
];
protected $cast = [
'start_date' => 'date',
'end_date' => 'date'
];
public const ACTIVE = 0;
public const UPDATE = 1;
public const EXPIRED = 2;
public function department()
{
return $this->belongsTo(Department::class, 'department_id');

@ -38,7 +38,7 @@ return [
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'root' => base_path('public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => false,

@ -29,6 +29,7 @@ return new class extends Migration
$table->text('note');
$table->string('document');
$table->foreignId('user_id')->constrained();
$table->smallInteger('status')->default(0);
$table->timestamps();
});
}

@ -0,0 +1,31 @@
import React from "react";
export default function InputFile({ file, isError, inputRef, handleChange }) {
return (
<div className="btn-group w-full">
<input
readOnly={true}
className={`input input-bordered w-full ${
isError && 'input-error'
}`}
value={file ? file.name : ''}
/>
<div
className="btn btn-active w-1/6"
onClick={() => {
console.log(inputRef.current.click())
}}
>
Pilih File
</div>
<input
ref={inputRef}
type="file"
className="hidden"
name="document"
onChange={(e) => handleChange(e)}
/>
</div>
)
}

@ -9,6 +9,8 @@ export default function TextInput({
required,
isFocused,
handleChange,
isError,
readOnly = false
}) {
const input = useRef();
@ -18,20 +20,37 @@ export default function TextInput({
}
}, []);
if (type === "textarea") {
return (
<textarea
className={`
${isError ? ' textarea-error ' : ''}
textarea textarea-bordered ${className}
`}
required={required}
onChange={(e) => handleChange(e)}
value={value}
name={name}
readOnly={readOnly}
>
</textarea>
)
}
return (
<div className="flex flex-col items-start">
<div className="flex flex-col items-start px-1">
<input
type={type}
name={name}
value={value}
className={
`input input-bordered w-full ` +
className
}
className={`
${isError ? ' input-error ' : ''}
input input-bordered w-full ${className}
`}
ref={input}
autoComplete={autoComplete}
required={required}
onChange={(e) => handleChange(e)}
readOnly={readOnly}
/>
</div>
);

@ -0,0 +1,15 @@
export const IconMenu = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</svg>
)
}
export const IconFilter = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75" />
</svg>
)
}

@ -1,23 +1,29 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import ApplicationLogo from '@/Components/ApplicationLogo';
import Dropdown from '@/Components/Dropdown';
import { ToastContainer } from 'react-toastify'
import { ToastContainer, toast } from 'react-toastify'
import ResponsiveNavLink from '@/Components/ResponsiveNavLink';
import { Link } from '@inertiajs/inertia-react';
import MenuItem from '@/Components/SidebarMenuItem';
export default function Authenticated({ auth, children }) {
export default function Authenticated({ auth, children, flash }) {
const [showingNavigationDropdown, setShowingNavigationDropdown] = useState(false);
useEffect(() => {
if (flash.message !== null) {
toast(flash.message.message, {type: flash.message.type})
}
}, [flash])
return (
<div className="min-h-screen bg-base-200">
<div className="min-h-screen bg-base-200 pb-10">
<nav className="bg-base-100 border-b border-base-100">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex">
<div className="shrink-0 flex items-center">
<Link href="/">
<ApplicationLogo className="block h-9 w-auto" />
<ApplicationLogo className="font-bold text-3xl block h-9 w-auto" />
</Link>
</div>

@ -3,36 +3,39 @@ import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head } from '@inertiajs/inertia-react';
export default function Dashboard(props) {
const { count_active, count_update, count_expired, count_total } = props
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
>
<Head title="Dashboard" />
<div className='max-w-7xl mx-auto sm:px-6 lg:px-8 flex justify-between space-x-1'>
<div class="stats bg-base-100 border-base-300 border md:w-1/4">
<div class="stat">
<div class="stat-title">Dokumen Aktif</div>
<div class="stat-value">1,400</div>
<div className='mx-auto px-2 md:px-4 lg:px-8 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-1'>
<div className="stats bg-base-100 shadow-md w-full">
<div className="stat">
<div className="stat-title">Dokumen Aktif</div>
<div className="stat-value">{count_active}</div>
</div>
</div>
<div class="stats bg-base-100 border-base-300 border md:w-1/4">
<div class="stat">
<div class="stat-title">Dokumen Diperbarui</div>
<div class="stat-value">2,400</div>
<div className="stats bg-base-100 shadow-md w-full">
<div className="stat">
<div className="stat-title">Dokumen Diperbarui</div>
<div className="stat-value">{count_update}</div>
</div>
</div>
<div class="stats bg-base-100 border-base-300 border md:w-1/4">
<div class="stat">
<div class="stat-title">Dokumen Berakhir</div>
<div class="stat-value">1,400</div>
<div className="stats bg-base-100 shadow-md w-full">
<div className="stat">
<div className="stat-title">Dokumen Berakhir</div>
<div className="stat-value">{count_expired}</div>
</div>
</div>
<div class="stats bg-base-100 border-base-300 border md:w-1/4">
<div class="stat">
<div class="stat-title">Total Dokumen</div>
<div class="stat-value">4,400</div>
<div className="stats bg-base-100 shadow-md w-full">
<div className="stat">
<div className="stat-title">Total Dokumen</div>
<div className="stat-value">{count_total}</div>
</div>
</div>
</div>

@ -0,0 +1,170 @@
import React from 'react'
import { Link, Head } from '@inertiajs/inertia-react'
import DocStatusItem from './DocStatusItem'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import InputLabel from '@/Components/InputLabel'
import TextInput from '@/Components/TextInput'
export default function FormDocument(props) {
const { doc, doc_url }= props
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
>
<Head title="Document - Form" />
<div className="flex flex-col w-full px-6 lg:px-8 space-y-2">
<div className="card bg-base-100 w-full">
<div className="card-body">
<p className='font-bold text-2xl mb-4'>Dokumen</p>
<div className="overflow-x-auto">
<div>
<div>
<InputLabel forInput="no_doc" value="No Dokumen" />
<TextInput
type="text"
name="no_doc"
value={doc.no_doc}
className="mt-1 block w-full"
autoComplete={"false"}
readOnly={true}
/>
</div>
<div className='mt-4'>
<InputLabel forInput="type" value="Jenis" />
<TextInput
type="text"
name="no_doc"
value={doc.type.name}
className="mt-1 block w-full"
autoComplete={"false"}
readOnly={true}
/>
</div>
<div className='mt-4'>
<InputLabel forInput="company_name" value="Nama Perusahaan" />
<TextInput
type="text"
name="company_name"
value={doc.company_name}
className="mt-1 block w-full"
autoComplete={"false"}
readOnly={true}
/>
</div>
<div className='mt-4'>
<InputLabel forInput="first_person_name" value="Nama Pihak Pertama" />
<TextInput
type="text"
name="first_person_name"
value={doc.first_person_name}
className="mt-1 block w-full"
autoComplete={"false"}
readOnly={true}
/>
</div>
<div className='mt-4'>
<InputLabel forInput="second_person_name" value="Nama Pihak Kedua" />
<TextInput
type="text"
name="second_person_name"
value={doc.second_person_name}
className="mt-1 block w-full"
autoComplete={"false"}
readOnly={true}
/>
</div>
<div className='mt-4'>
<InputLabel forInput="start_date" value="Tanggal Mulai" />
<TextInput
type="date"
name="start_date"
value={doc.start_date}
className="mt-1 block w-full"
autoComplete={"false"}
readOnly={true}
/>
</div>
<div className='mt-4'>
<InputLabel forInput="end_date" value="Tanggal Berakhir" />
<TextInput
type="date"
name="end_date"
value={doc.end_date}
className="mt-1 block w-full"
autoComplete={"false"}
readOnly={true}
/>
</div>
<div className='mt-4'>
<InputLabel forInput="type" value="Deparment" />
<TextInput
type="text"
name="type"
value={doc.department.name}
className="mt-1 block w-full"
autoComplete={"false"}
readOnly={true}
/>
</div>
<div className='mt-4'>
<InputLabel forInput="pic_name" value="Nama PIC" />
<TextInput
type="text"
name="pic_name"
value={doc.pic_name}
className="mt-1 block w-full"
autoComplete={"false"}
readOnly={true}
/>
</div>
<div className='mt-4'>
<InputLabel forInput="email" value="Email" />
<TextInput
type="text"
name="email"
value={doc.email}
className="mt-1 block w-full"
autoComplete={"false"}
readOnly={true}
/>
</div>
<div className='mt-4'>
<InputLabel forInput="note" value="Catatan" />
<TextInput
type="textarea"
name="note"
value={doc.note}
className="mt-1 block w-full"
readOnly={true}
/>
</div>
<div className='mt-4'>
<InputLabel forInput="document" value="Dokumen" />
<a href={doc_url} className='btn btn-outline'>Download</a>
</div>
<div className='mt-4'>
<InputLabel forInput="status" value="Status" />
<DocStatusItem status={doc.status}/>
</div>
<div className="flex items-center justify-end mt-4">
<Link href={route('docs.index')} className="btn btn-outline">
Kembali
</Link>
</div>
</div>
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
)
}

@ -0,0 +1,9 @@
import { statuses } from "@/utils";
import React from "react";
export default function DocStatusItem({ status }) {
const stat = statuses.find(i => i.key == status)
return (
<p style={{color: stat.color}}>{stat.value}</p>
)
}

@ -0,0 +1,278 @@
import React, { useEffect, useRef } from 'react'
import { Link, Head, useForm } from '@inertiajs/inertia-react'
import { toast } from 'react-toastify'
import {statuses} from '@/utils'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import PrimaryButton from '@/Components/PrimaryButton'
import InputLabel from '@/Components/InputLabel'
import TextInput from '@/Components/TextInput'
import InputError from '@/Components/InputError'
import InputFile from '@/Components/InputFile'
export default function FormDocument(props) {
const { types, departments, doc }= props
const { data, setData, post, processing, errors, reset } = useForm({
no_doc: '',
email: '',
type_doc_id: '1',
department_id: '1',
company_name: '',
first_person_name: '',
second_person_name: '',
start_date: '',
end_date: '',
pic_name: '',
note: '',
document: null,
document_name: '',
status: 0,
});
const inputDocument = useRef()
useEffect(() => {
if(doc !== undefined) {
setData({
no_doc: doc.no_doc,
email: doc.email,
type_doc_id: doc.type_doc_id,
department_id: doc.department_id,
company_name: doc.company_name,
first_person_name: doc.first_person_name,
second_person_name: doc.second_person_name,
start_date: doc.start_date,
end_date: doc.end_date,
pic_name: doc.pic_name,
note: doc.note,
document: null,
document_name: doc.document,
status: doc.status,
})
}
}, [doc]);
const onHandleChange = (event) => {
setData(event.target.name, event.target.type === 'checkbox' ? event.target.checked : event.target.value);
};
const submit = (e) => {
e.preventDefault();
if(doc !== undefined) {
post(route('docs.update', doc), {
onError: () => toast.error('please recheck the data')
});
return
}
post(route('docs.store'), {
onError: () => toast.error('please recheck the data')
});
};
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
>
<Head title="Document - Form" />
<div className="flex flex-col w-full px-6 lg:px-8 space-y-2">
<div className="card bg-base-100 w-full">
<div className="card-body">
<p className='font-bold text-2xl mb-4'>Dokumen</p>
<div className="overflow-x-auto">
<form onSubmit={submit}>
<div>
<InputLabel forInput="no_doc" value="No Dokumen" />
<TextInput
type="text"
name="no_doc"
value={data.no_doc}
className="mt-1 block w-full"
autoComplete={"false"}
handleChange={onHandleChange}
isError={errors.no_doc}
/>
<InputError message={errors.no_doc}/>
</div>
<div className='mt-4'>
<InputLabel forInput="type" value="Jenis" />
<select
className="mt-1 select select-bordered w-full"
name="type_doc_id"
onChange={onHandleChange}
value={data.type_doc_id}
>
{types.map(type => (
<option key={type.id} value={type.id}>{type.name}</option>
))}
</select>
<InputError message={errors.type_doc_id}/>
</div>
<div className='mt-4'>
<InputLabel forInput="company_name" value="Nama Perusahaan" />
<TextInput
type="text"
name="company_name"
value={data.company_name}
className="mt-1 block w-full"
autoComplete={"false"}
handleChange={onHandleChange}
isError={errors.company_name}
/>
<InputError message={errors.company_name}/>
</div>
<div className='mt-4'>
<InputLabel forInput="first_person_name" value="Nama Pihak Pertama" />
<TextInput
type="text"
name="first_person_name"
value={data.first_person_name}
className="mt-1 block w-full"
autoComplete={"false"}
handleChange={onHandleChange}
isError={errors.first_person_name}
/>
<InputError message={errors.first_person_name}/>
</div>
<div className='mt-4'>
<InputLabel forInput="second_person_name" value="Nama Pihak Kedua" />
<TextInput
type="text"
name="second_person_name"
value={data.second_person_name}
className="mt-1 block w-full"
autoComplete={"false"}
handleChange={onHandleChange}
isError={errors.second_person_name}
/>
<InputError message={errors.second_person_name}/>
</div>
<div className='mt-4'>
<InputLabel forInput="start_date" value="Tanggal Mulai" />
<TextInput
type="date"
name="start_date"
value={data.start_date}
className="mt-1 block w-full"
autoComplete={"false"}
handleChange={onHandleChange}
isError={errors.start_date}
/>
<InputError message={errors.start_date}/>
</div>
<div className='mt-4'>
<InputLabel forInput="end_date" value="Tanggal Berakhir" />
<TextInput
type="date"
name="end_date"
value={data.end_date}
className="mt-1 block w-full"
autoComplete={"false"}
handleChange={onHandleChange}
isError={errors.end_date}
/>
<InputError message={errors.end_date}/>
</div>
<div className='mt-4'>
<InputLabel forInput="type" value="Deparment" />
<select
className="mt-1 select select-bordered w-full"
name="department_id"
onChange={onHandleChange}
value={data.department_id}
>
{departments.map(dep => (
<option key={dep.id} value={dep.id}>{dep.name}</option>
))}
</select>
<InputError message={errors.type_doc_id}/>
</div>
<div className='mt-4'>
<InputLabel forInput="pic_name" value="Nama PIC" />
<TextInput
type="text"
name="pic_name"
value={data.pic_name}
className="mt-1 block w-full"
autoComplete={"false"}
handleChange={onHandleChange}
isError={errors.pic_name}
/>
<InputError message={errors.pic_name}/>
</div>
<div className='mt-4'>
<InputLabel forInput="email" value="Email" />
<TextInput
type="text"
name="email"
value={data.email}
className="mt-1 block w-full"
autoComplete={"false"}
handleChange={onHandleChange}
isError={errors.email}
/>
<InputError message={errors.email}/>
</div>
<div className='mt-4'>
<InputLabel forInput="note" value="Catatan" />
<TextInput
type="textarea"
name="note"
value={data.note}
className="mt-1 block w-full"
handleChange={onHandleChange}
isError={errors.note}
/>
<InputError message={errors.note}/>
</div>
<div className='mt-4'>
<InputLabel forInput="document" value="Dokumen" />
<InputFile
file={data.document}
isError={errors.document}
inputRef={inputDocument}
handleChange={e => setData('document', e.target.files[0])}
/>
<InputError message={errors.document}/>
{doc !== undefined && (
<p className='text-sm'>file saved is found, reupload to replace</p>
)}
</div>
<div className='mt-4'>
<InputLabel forInput="status" value="Status" />
<select
className="mt-1 select select-bordered w-full"
name="status"
onChange={onHandleChange}
value={data.status}
>
{statuses.map(status => (
<option key={`status-${status.key}`} value={status.key}>{status.value}</option>
))}
</select>
<InputError message={errors.status}/>
</div>
<div className="flex items-center justify-between mt-4">
<PrimaryButton processing={processing}>
Simpan
</PrimaryButton>
<Link href={route('docs.index')} className="btn btn-outline">
Batal
</Link>
</div>
</form>
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
)
}

@ -0,0 +1,162 @@
import React, { useEffect, useState } from 'react'
import { usePrevious } from 'react-use'
import { Head, Link } from '@inertiajs/inertia-react'
import { Inertia } from '@inertiajs/inertia'
import { toast } from 'react-toastify'
import { useModalState } from '@/Hooks'
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import Pagination from '@/Components/Pagination'
import ModalConfirm from '@/Components/ModalConfirm'
import ModalFilter from './ModalFilter'
import DocStatusItem from './DocStatusItem'
import { IconFilter, IconMenu } from '@/Icons'
export default function Document(props) {
const { types, departments } = props
const { data: docs, links } = props.docs
const [search, setSearch] = useState({q: ''})
const preValue = usePrevious(search)
const confirmModal = useModalState(false)
const handleDelete = (doc) => {
confirmModal.setData(doc)
confirmModal.toggle()
}
const onDelete = () => {
const doc = confirmModal.data
if(doc != null) {
Inertia.delete(route('docs.destroy', doc), {
onSuccess: () => toast.success('The Data has been deleted'),
})
}
}
const filterModal = useModalState(false)
const handleFilter = (filter) => {
setSearch({
...search,
...filter
})
}
useEffect(() => {
if (preValue) {
Inertia.get(
route(route().current()),
search,
{
replace: true,
preserveState: true,
}
)
}
}, [search])
return (
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
>
<Head title="Document" />
<div className="flex flex-col w-full sm:px-6 lg:px-8 space-y-2">
<div className="card bg-base-100 w-full">
<div className="card-body">
<div className="flex flex-col md:flex-row w-full mb-4 justify-between space-y-1 md:space-y-0">
<Link
className="btn btn-neutral"
href={route('docs.create')}
>
Tambah
</Link>
<div className='flex flex-row'>
<div className="form-control w-full">
<input
type="text"
className="input input-bordered"
value={search.q}
onChange={(e) =>
handleFilter({q: e.target.value})
}
placeholder="Search"
/>
</div>
<div className='tooltip' data-tip="filter status etc">
<div className='btn btn-outline' onClick={() => filterModal.toggle()}>
<IconFilter/>
</div>
</div>
</div>
</div>
<div className="overflow-x-auto pb-52">
<table className="table w-full table-zebra">
<thead>
<tr>
<th>No</th>
<th>No. Dokumen</th>
<th>Nama PIC</th>
<th>Jenis</th>
<th>Deparment</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
{docs?.map((doc) => (
<tr key={doc.id}>
<th>{doc.no}</th>
<td>{doc.no_doc}</td>
<td>{doc.pic_name}</td>
<td>{doc.type.name}</td>
<td>{doc.department.name}</td>
<td><DocStatusItem status={doc.status}/></td>
<td className='text-right'>
<div className="dropdown dropdown-left">
<label tabIndex={0} className="btn btn-sm m-1 px-1"><IconMenu/></label>
<ul tabIndex={0} className="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-52">
<li>
<Link href={route('docs.show', doc)}>Detail</Link>
</li>
<li>
<div>Share</div>
</li>
<li>
<Link href={route('docs.edit', doc)}>Edit</Link>
</li>
<li onClick={() => handleDelete(doc)} className="bg-error ">
<div>Delete</div>
</li>
</ul>
</div>
</td>
</tr>
))}
</tbody>
</table>
<Pagination links={links} params={search}/>
</div>
</div>
</div>
</div>
<ModalConfirm
isOpen={confirmModal.isOpen}
toggle={confirmModal.toggle}
onConfirm={onDelete}
/>
<ModalFilter
isOpen={filterModal.isOpen}
toggle={filterModal.toggle}
filter={search}
types={types}
departments={departments}
handleSetFilter={handleFilter}
/>
</AuthenticatedLayout>
)
}

@ -0,0 +1,103 @@
import React, { useState } from 'react'
import { statuses } from '@/utils'
import InputLabel from '@/Components/InputLabel'
export default function ModalFilter(props) {
const { isOpen, toggle, handleSetFilter, types, departments, filter } = props
const [type, setType] = useState('')
const [dep, setDep] = useState('')
const [status, setStatus] = useState('')
const onClickFilter = () => {
toggle()
handleSetFilter({
...filter,
type_doc_id: type,
status: status,
department_id: dep
})
}
const onClickReset = () => {
toggle()
setType('')
setDep('')
setStatus('')
handleSetFilter({
...filter,
type_doc_id: '',
status: '',
department_id: ''
})
}
const onClickExport = () => {
// call export url
}
return (
<div
className="modal modal-bottom sm:modal-middle pb-10"
style={
isOpen
? {
opacity: 1,
pointerEvents: 'auto',
visibility: 'visible',
}
: {}
}
>
<div className="modal-box">
<label htmlFor="my-modal-3" className="btn btn-sm btn-circle absolute right-2 top-2" onClick={toggle}></label>
<div className='mt-4'>
<InputLabel forInput="type" value="Jenis" />
<select
className="mt-1 select select-bordered w-full"
name="type_doc_id"
onChange={(e) => setType(e.target.value)}
value={type}
>
<option key={`type.id`} value={``}> - </option>
{types.map(type => (
<option key={type.id} value={type.id}>{type.name}</option>
))}
</select>
</div>
<div className='mt-4'>
<InputLabel forInput="type" value="Deparment" />
<select
className="mt-1 select select-bordered w-full"
name="department_id"
onChange={(e) => setDep(e.target.value)}
value={dep}
>
<option key={`dep.id`} value={``}> - </option>
{departments.map(dep => (
<option key={dep.id} value={dep.id}>{dep.name}</option>
))}
</select>
</div>
<div className='mt-4'>
<InputLabel forInput="status" value="Status" />
<select
className="mt-1 select select-bordered w-full"
name="status"
onChange={(e) => setStatus(e.target.value)}
value={status}
>
<option key={`status.id`} value={``}> - </option>
{statuses.map(status => (
<option key={`status-${status.key}`} value={status.key}>{status.value}</option>
))}
</select>
</div>
<div className='flex justify-center mt-4 space-x-4'>
<div className='btn btn-outline' onClick={onClickFilter}>Filter</div>
<div className='btn btn-outline' onClick={onClickReset}>Reset</div>
<div className='btn btn-info btn-outline' onClick={onClickExport}>Export</div>
</div>
</div>
</div>
)
}

@ -19,23 +19,23 @@ export default function Users(props) {
const [user, setUser] = useState(null)
const formModal = useModalState(false)
const toggle = (user = null) => {
setUser(user)
formModal.toggle()
setUser(user)
formModal.toggle()
}
const confirmModal = useModalState(false)
const handleDelete = (user) => {
confirmModal.setData(user)
confirmModal.toggle()
confirmModal.setData(user)
confirmModal.toggle()
}
const onDelete = () => {
const user = confirmModal.data
if(user != null) {
Inertia.delete(route('users.destroy', user), {
onSuccess: () => toast.success('The Data has been deleted'),
})
}
const user = confirmModal.data
if(user != null) {
Inertia.delete(route('users.destroy', user), {
onSuccess: () => toast.success('The Data has been deleted'),
})
}
}
useEffect(() => {
@ -55,6 +55,7 @@ export default function Users(props) {
<AuthenticatedLayout
auth={props.auth}
errors={props.errors}
flash={props.flash}
>
<Head title="Users" />
<div className="flex flex-col w-full sm:px-6 lg:px-8 space-y-2">

@ -0,0 +1,17 @@
export const statuses = [
{
key: 0,
value: 'Aktif',
color: 'green'
},
{
key: 1,
value: 'Update',
color: 'rgb(229, 195, 24)'
},
{
key: 2,
value: 'Expired',
color: 'red'
}
]

@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" data-theme="corporate">
<!-- corporate -->
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

@ -1,5 +1,7 @@
<?php
use App\Http\Controllers\DocumentController;
use App\Http\Controllers\GeneralController;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
@ -20,18 +22,21 @@ Route::get('/', function () {
return redirect()->route('login');
});
Route::get('/docs', function () {
})->name('docs.index');
Route::middleware(['auth'])->group(function () {
Route::get('/dashboard', function () {
return Inertia::render('Dashboard');
})->name('dashboard');
Route::get('/dashboard',[GeneralController::class, 'index'])->name('dashboard');
Route::get('/users', [UserController::class, 'index'])->name('users.index');
Route::post('/users', [UserController::class, 'store'])->name('users.store');
Route::put('/users/{user}', [UserController::class, 'update'])->name('users.update');
Route::delete('/users/{user}', [UserController::class, 'destroy'])->name('users.destroy');
Route::get('/docs', [DocumentController::class, 'index'])->name('docs.index');
Route::get('/docs/create', [DocumentController::class, 'create'])->name('docs.create');
Route::post('/docs', [DocumentController::class, 'store'])->name('docs.store');
Route::delete('/docs/{doc}', [DocumentController::class, 'destroy'])->name('docs.destroy');
Route::get('/docs/{doc}', [DocumentController::class, 'edit'])->name('docs.edit');
Route::post('/docs/{doc}', [DocumentController::class, 'update'])->name('docs.update');
Route::get('/docs/{doc}/show', [DocumentController::class, 'show'])->name('docs.show');
});
require __DIR__.'/auth.php';

Loading…
Cancel
Save