├── .editorconfig ├── .env.example ├── .gitattributes ├── .gitignore ├── README.md ├── app ├── Http │ ├── Controllers │ │ ├── Apps │ │ │ ├── CategoryController.php │ │ │ ├── CustomerController.php │ │ │ ├── ProductController.php │ │ │ ├── SaleController.php │ │ │ └── TransactionController.php │ │ ├── Auth │ │ │ ├── AuthenticatedSessionController.php │ │ │ ├── ConfirmablePasswordController.php │ │ │ ├── EmailVerificationNotificationController.php │ │ │ ├── EmailVerificationPromptController.php │ │ │ ├── NewPasswordController.php │ │ │ ├── PasswordController.php │ │ │ ├── PasswordResetLinkController.php │ │ │ ├── RegisteredUserController.php │ │ │ └── VerifyEmailController.php │ │ ├── Controller.php │ │ ├── PermissionController.php │ │ ├── ProfileController.php │ │ ├── RoleController.php │ │ └── UserController.php │ ├── Middleware │ │ └── HandleInertiaRequests.php │ └── Requests │ │ ├── Auth │ │ └── LoginRequest.php │ │ └── ProfileUpdateRequest.php ├── Models │ ├── Cart.php │ ├── Category.php │ ├── Customer.php │ ├── Product.php │ ├── Profit.php │ ├── Transaction.php │ ├── TransactionDetail.php │ └── User.php └── Providers │ └── AppServiceProvider.php ├── artisan ├── bootstrap ├── app.php ├── cache │ └── .gitignore └── providers.php ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── cache.php ├── database.php ├── filesystems.php ├── logging.php ├── mail.php ├── permission.php ├── queue.php ├── services.php └── session.php ├── database ├── .gitignore ├── factories │ └── UserFactory.php ├── migrations │ ├── 0001_01_01_000000_create_users_table.php │ ├── 0001_01_01_000001_create_cache_table.php │ ├── 0001_01_01_000002_create_jobs_table.php │ ├── 2024_06_13_082620_create_permission_tables.php │ ├── 2024_06_13_091315_add_avatar_field_to_users_table.php │ ├── 2024_06_13_125039_create_customers_table.php │ ├── 2024_06_13_130507_create_categories_table.php │ ├── 2024_06_13_131744_create_products_table.php │ ├── 2024_06_13_132800_create_transactions_table.php │ ├── 2024_06_13_133940_create_transaction_details_table.php │ ├── 2024_06_13_133948_create_carts_table.php │ └── 2024_06_13_133955_create_profits_table.php └── seeders │ ├── DatabaseSeeder.php │ ├── PermissionSeeder.php │ ├── RoleSeeder.php │ └── UserSeeder.php ├── jsconfig.json ├── package-lock.json ├── package.json ├── phpunit.xml ├── postcss.config.js ├── public ├── .htaccess ├── assets │ ├── background │ │ ├── Hero-Banner.png │ │ ├── alqowy.svg │ │ └── benefit_illustration.png │ ├── icon │ │ ├── 3dcube.svg │ │ ├── Group 7-1.svg │ │ ├── Group 7-2.svg │ │ ├── Group 7.svg │ │ ├── Logo-1.svg │ │ ├── Logo.svg │ │ ├── Web Development 1-1.svg │ │ ├── Web Development 1-2.svg │ │ ├── Web Development 1-3.svg │ │ ├── Web Development 1-4.svg │ │ ├── Web Development 1.svg │ │ ├── add.svg │ │ ├── arrow-right.svg │ │ ├── avatar-group.png │ │ ├── award-outline.svg │ │ ├── award.svg │ │ ├── book.svg │ │ ├── brifecase-tick.svg │ │ ├── crown.svg │ │ ├── logo-52.svg │ │ ├── logo-54.svg │ │ ├── logo-55.svg │ │ ├── medal-star.svg │ │ ├── note-add.svg │ │ ├── note-favorite.svg │ │ ├── play-circle.svg │ │ ├── profile-2user.svg │ │ ├── star.svg │ │ ├── tick-circle.svg │ │ └── video-play.svg │ ├── logo │ │ ├── logo-black.svg │ │ └── logo.svg │ ├── photo │ │ ├── photo1.png │ │ ├── photo2.png │ │ ├── photo3.png │ │ ├── photo4.png │ │ └── photo5.png │ └── thumbnail │ │ ├── image-1.png │ │ ├── image-2.png │ │ ├── image-3.png │ │ ├── image.png │ │ ├── thumbnail-1.png │ │ ├── thumbnail-2.png │ │ └── thumbnail-3.png ├── favicon.ico ├── index.php └── robots.txt ├── resources ├── css │ └── app.css ├── js │ ├── Components │ │ ├── ApplicationLogo.jsx │ │ ├── Checkbox.jsx │ │ ├── DangerButton.jsx │ │ ├── Dashboard │ │ │ ├── AuthDropdown.jsx │ │ │ ├── Barcode.jsx │ │ │ ├── Button.jsx │ │ │ ├── Card.jsx │ │ │ ├── Checkbox.jsx │ │ │ ├── Header.jsx │ │ │ ├── Input.jsx │ │ │ ├── InputSelect.jsx │ │ │ ├── LinkItem.jsx │ │ │ ├── LinkItemDropdown.jsx │ │ │ ├── ListBox.jsx │ │ │ ├── Modal.jsx │ │ │ ├── Navbar.jsx │ │ │ ├── Notification.jsx │ │ │ ├── Pagination.jsx │ │ │ ├── Search.jsx │ │ │ ├── Sidebar.jsx │ │ │ ├── Table.jsx │ │ │ ├── TextArea.jsx │ │ │ └── Widget.jsx │ │ ├── Dropdown.jsx │ │ ├── InputError.jsx │ │ ├── InputLabel.jsx │ │ ├── Modal.jsx │ │ ├── NavLink.jsx │ │ ├── PrimaryButton.jsx │ │ ├── ResponsiveNavLink.jsx │ │ ├── SecondaryButton.jsx │ │ └── TextInput.jsx │ ├── Context │ │ └── ThemeSwitcherContext.jsx │ ├── Layouts │ │ ├── AuthenticatedLayout.jsx │ │ ├── DashboardLayout.jsx │ │ └── GuestLayout.jsx │ ├── Pages │ │ ├── Auth │ │ │ ├── ConfirmPassword.jsx │ │ │ ├── ForgotPassword.jsx │ │ │ ├── Login.jsx │ │ │ ├── Register.jsx │ │ │ ├── ResetPassword.jsx │ │ │ └── VerifyEmail.jsx │ │ ├── Dashboard.jsx │ │ ├── Dashboard │ │ │ ├── Categories │ │ │ │ ├── Create.jsx │ │ │ │ ├── Edit.jsx │ │ │ │ └── Index.jsx │ │ │ ├── Customers │ │ │ │ ├── Create.jsx │ │ │ │ ├── Edit.jsx │ │ │ │ └── Index.jsx │ │ │ ├── Index.jsx │ │ │ ├── Permissions │ │ │ │ └── Index.jsx │ │ │ ├── Products │ │ │ │ ├── Create.jsx │ │ │ │ ├── Edit.jsx │ │ │ │ └── Index.jsx │ │ │ ├── Roles │ │ │ │ └── Index.jsx │ │ │ ├── Transactions │ │ │ │ ├── Index.jsx │ │ │ │ └── Print.jsx │ │ │ └── Users │ │ │ │ ├── Create.jsx │ │ │ │ ├── Edit.jsx │ │ │ │ └── Index.jsx │ │ ├── Profile │ │ │ ├── Edit.jsx │ │ │ └── Partials │ │ │ │ ├── DeleteUserForm.jsx │ │ │ │ ├── UpdatePasswordForm.jsx │ │ │ │ └── UpdateProfileInformationForm.jsx │ │ └── Welcome.jsx │ ├── Utils │ │ ├── Menu.jsx │ │ └── Permission.jsx │ ├── app.jsx │ └── bootstrap.js └── views │ └── app.blade.php ├── routes ├── auth.php ├── console.php └── web.php ├── storage ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ ├── .gitignore │ │ └── data │ │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore ├── tailwind.config.js ├── tests ├── Feature │ ├── Auth │ │ ├── AuthenticationTest.php │ │ ├── EmailVerificationTest.php │ │ ├── PasswordConfirmationTest.php │ │ ├── PasswordResetTest.php │ │ ├── PasswordUpdateTest.php │ │ └── RegistrationTest.php │ ├── ExampleTest.php │ └── ProfileTest.php ├── TestCase.php └── Unit │ └── ExampleTest.php └── vite.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | 17 | [docker-compose.yml] 18 | indent_size = 4 19 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_TIMEZONE=UTC 6 | APP_URL=http://localhost 7 | 8 | APP_LOCALE=en 9 | APP_FALLBACK_LOCALE=en 10 | APP_FAKER_LOCALE=en_US 11 | 12 | APP_MAINTENANCE_DRIVER=file 13 | APP_MAINTENANCE_STORE=database 14 | 15 | BCRYPT_ROUNDS=12 16 | 17 | LOG_CHANNEL=stack 18 | LOG_STACK=single 19 | LOG_DEPRECATIONS_CHANNEL=null 20 | LOG_LEVEL=debug 21 | 22 | DB_CONNECTION=mysql 23 | DB_HOST=127.0.0.1 24 | DB_PORT=3306 25 | DB_DATABASE=point_of_sales 26 | DB_USERNAME=root 27 | DB_PASSWORD= 28 | 29 | SESSION_DRIVER=database 30 | SESSION_LIFETIME=120 31 | SESSION_ENCRYPT=false 32 | SESSION_PATH=/ 33 | SESSION_DOMAIN=null 34 | 35 | BROADCAST_CONNECTION=log 36 | FILESYSTEM_DISK=local 37 | QUEUE_CONNECTION=database 38 | 39 | CACHE_STORE=database 40 | CACHE_PREFIX= 41 | 42 | MEMCACHED_HOST=127.0.0.1 43 | 44 | REDIS_CLIENT=phpredis 45 | REDIS_HOST=127.0.0.1 46 | REDIS_PASSWORD=null 47 | REDIS_PORT=6379 48 | 49 | MAIL_MAILER=log 50 | MAIL_HOST=127.0.0.1 51 | MAIL_PORT=2525 52 | MAIL_USERNAME=null 53 | MAIL_PASSWORD=null 54 | MAIL_ENCRYPTION=null 55 | MAIL_FROM_ADDRESS="hello@example.com" 56 | MAIL_FROM_NAME="${APP_NAME}" 57 | 58 | AWS_ACCESS_KEY_ID= 59 | AWS_SECRET_ACCESS_KEY= 60 | AWS_DEFAULT_REGION=us-east-1 61 | AWS_BUCKET= 62 | AWS_USE_PATH_STYLE_ENDPOINT=false 63 | 64 | VITE_APP_NAME="${APP_NAME}" 65 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.blade.php diff=html 4 | *.css diff=css 5 | *.html diff=html 6 | *.md diff=markdown 7 | *.php diff=php 8 | 9 | /.github export-ignore 10 | CHANGELOG.md export-ignore 11 | .styleci.yml export-ignore 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /node_modules 3 | /public/build 4 | /public/hot 5 | /public/storage 6 | /storage/*.key 7 | /vendor 8 | .env 9 | .env.backup 10 | .env.production 11 | .phpactor.json 12 | .phpunit.result.cache 13 | Homestead.json 14 | Homestead.yaml 15 | auth.json 16 | npm-debug.log 17 | yarn-error.log 18 | /.fleet 19 | /.idea 20 | /.vscode 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aplikasi Kasir (Point of Sales) 2 | Aplikasi ini dapat digunakan untuk melakukan pencatatan transaksi jual beli pada sebuah warung atau toko. Secara template, aplikasi ini menggunakan resource dari https://github.com/Raf-Taufiqurrahman/RILT-Starter dengan beberapa modifikasi yang saya lakukan terhadap komponen-komponen untuk mendukung aplikasi kasir. 3 | 4 | ## Tech Stack 5 | 6 | - Laravel 11.x 7 | - Inertia 8 | - React 9 | - TailwindCSS 10 | - MySQL 11 | ## Authors 12 | 13 | - [Arya Dwi Putra](https://www.github.com/aryadwiputra) 14 | - [Rafi Taufiqurrahman](https://github.com/Raf-Taufiqurrahman) 15 | 16 | 17 | ## 📌 Fitur 18 | 19 | | No | Nama | Status | 20 | |-----|------------------------------------------------------------|------| 21 | | 1 | Authentikasi Admin. |Done| 22 | | 2 | Manajemen Pengguna. |Done| 23 | | 3 | Manajemen Hak Akses Pengguna. | Done| 24 | | 4 | Manajemen Role Pengguna |Done| 25 | | 5 | Manajemen Kategori. |Done| 26 | | 6 | Manajemen Produk. |Done| 27 | | 7 | Manajemen Pelanggan. |Done| 28 | | 8 | Print Invoice. |Done| 29 | | 9 | Laporan Penjualan. |On progress| 30 | | 10 | Laporan Keuntungan. |On progress| 31 | | 11 | Riwayat Order. |On progress| 32 | | 12 | Chart/Grafik Pendapatan. |On progress| 33 | 34 | ------------ 35 | ## 💻 Panduan Instalasi Project 36 | 37 | 1. **Clone Repository** 38 | ```bash 39 | git clone https://github.com/aryadwiputra/point-of-sales 40 | ``` 41 | 2. **Buka terminal, lalu ketik** 42 | ``` 43 | cd point-of-sales 44 | composer install 45 | npm install 46 | cp .env.example .env 47 | php artisan key:generate 48 | ``` 49 | 50 | 3. **Buka ```.env``` lalu ubah baris berikut sesuaikan dengan databasemu yang ingin dipakai** 51 | ``` 52 | DB_PORT=3306 53 | DB_DATABASE=laravel 54 | DB_USERNAME=root 55 | DB_PASSWORD= 56 | ``` 57 | 58 | 3. **Jalankan bash** 59 | ```bash 60 | php artisan config:cache 61 | php artisan storage:link 62 | php artisan route:clear 63 | ``` 64 | 65 | 4. **Jalankan migrations dan seeders** 66 | ``` 67 | php artisan migrate --seed 68 | ``` 69 | 5. **Jalankan nodejs** 70 | ``` 71 | npm run dev 72 | ``` 73 | 74 | 5. **Jalankan website** 75 | ```bash 76 | php artisan serve 77 | ``` 78 | 79 | ## Jika ada pertanyaan silahkan hubungi saya di email : 80 | 81 | ``` 82 | aryaadwptr@gmail.com 83 | ``` 84 | 85 | ## Request Fitur Baru dan Pelaporan Bug 86 | 87 | Anda dapat meminta fitur baru maupun melaporkan bug melalui menu **issues** yang sudah disediakan oleh GitHub (lihat menu di atas), posting issues baru dan kita akan berdiskusi disana. 88 | 89 | ## Berkontribusi 90 | 91 | Siapapun dapat berkontribusi pada proyek ini mulai dari pemrograman, pembuakan buku manual, sampai dengan mengenalkan produk ini kepada masyarakat Indonesia agar mengurangi kesenjangan pendidikan teknologi dengan cara membuat postingan issue di repository ini. 92 | -------------------------------------------------------------------------------- /app/Http/Controllers/Apps/CustomerController.php: -------------------------------------------------------------------------------- 1 | search, function ($customers) { 21 | $customers = $customers->where('name', 'like', '%' . request()->search . '%'); 22 | })->latest()->paginate(5); 23 | 24 | //return inertia 25 | return Inertia::render('Dashboard/Customers/Index', [ 26 | 'customers' => $customers, 27 | ]); 28 | } 29 | 30 | /** 31 | * Show the form for creating a new resource. 32 | * 33 | * @return \Illuminate\Http\Response 34 | */ 35 | public function create() 36 | { 37 | return Inertia::render('Dashboard/Customers/Create'); 38 | } 39 | 40 | /** 41 | * Store a newly created resource in storage. 42 | * 43 | * @param \Illuminate\Http\Request $request 44 | * @return \Illuminate\Http\Response 45 | */ 46 | public function store(Request $request) 47 | { 48 | /** 49 | * validate 50 | */ 51 | $request->validate([ 52 | 'name' => 'required', 53 | 'no_telp' => 'required|unique:customers', 54 | 'address' => 'required', 55 | ]); 56 | 57 | //create customer 58 | Customer::create([ 59 | 'name' => $request->name, 60 | 'no_telp' => $request->no_telp, 61 | 'address' => $request->address, 62 | ]); 63 | 64 | //redirect 65 | return to_route('customers.index'); 66 | } 67 | 68 | /** 69 | * Show the form for editing the specified resource. 70 | * 71 | * @param int $id 72 | * @return \Illuminate\Http\Response 73 | */ 74 | public function edit(Customer $customer) 75 | { 76 | return Inertia::render('Dashboard/Customers/Edit', [ 77 | 'customer' => $customer, 78 | ]); 79 | } 80 | 81 | /** 82 | * Update the specified resource in storage. 83 | * 84 | * @param \Illuminate\Http\Request $request 85 | * @param int $id 86 | * @return \Illuminate\Http\Response 87 | */ 88 | public function update(Request $request, Customer $customer) 89 | { 90 | /** 91 | * validate 92 | */ 93 | $request->validate([ 94 | 'name' => 'required', 95 | 'no_telp' => 'required|unique:customers,no_telp,' . $customer->id, 96 | 'address' => 'required', 97 | ]); 98 | 99 | //update customer 100 | $customer->update([ 101 | 'name' => $request->name, 102 | 'no_telp' => $request->no_telp, 103 | 'address' => $request->address, 104 | ]); 105 | 106 | //redirect 107 | return to_route('customers.index'); 108 | } 109 | 110 | /** 111 | * Remove the specified resource from storage. 112 | * 113 | * @param int $id 114 | * @return \Illuminate\Http\Response 115 | */ 116 | public function destroy($id) 117 | { 118 | //find customer by ID 119 | $customer = Customer::findOrFail($id); 120 | 121 | //delete customer 122 | $customer->delete(); 123 | 124 | //redirect 125 | return back(); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /app/Http/Controllers/Apps/SaleController.php: -------------------------------------------------------------------------------- 1 | Route::has('password.request'), 23 | 'status' => session('status'), 24 | ]); 25 | } 26 | 27 | /** 28 | * Handle an incoming authentication request. 29 | */ 30 | public function store(LoginRequest $request): RedirectResponse 31 | { 32 | $request->authenticate(); 33 | 34 | $request->session()->regenerate(); 35 | 36 | return redirect()->intended(route('dashboard', absolute: false)); 37 | } 38 | 39 | /** 40 | * Destroy an authenticated session. 41 | */ 42 | public function destroy(Request $request): RedirectResponse 43 | { 44 | Auth::guard('web')->logout(); 45 | 46 | $request->session()->invalidate(); 47 | 48 | $request->session()->regenerateToken(); 49 | 50 | return redirect('/'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/ConfirmablePasswordController.php: -------------------------------------------------------------------------------- 1 | validate([ 29 | 'email' => $request->user()->email, 30 | 'password' => $request->password, 31 | ])) { 32 | throw ValidationException::withMessages([ 33 | 'password' => __('auth.password'), 34 | ]); 35 | } 36 | 37 | $request->session()->put('auth.password_confirmed_at', time()); 38 | 39 | return redirect()->intended(route('dashboard', absolute: false)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/EmailVerificationNotificationController.php: -------------------------------------------------------------------------------- 1 | user()->hasVerifiedEmail()) { 17 | return redirect()->intended(route('dashboard', absolute: false)); 18 | } 19 | 20 | $request->user()->sendEmailVerificationNotification(); 21 | 22 | return back()->with('status', 'verification-link-sent'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/EmailVerificationPromptController.php: -------------------------------------------------------------------------------- 1 | user()->hasVerifiedEmail() 19 | ? redirect()->intended(route('dashboard', absolute: false)) 20 | : Inertia::render('Auth/VerifyEmail', ['status' => session('status')]); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/NewPasswordController.php: -------------------------------------------------------------------------------- 1 | $request->email, 26 | 'token' => $request->route('token'), 27 | ]); 28 | } 29 | 30 | /** 31 | * Handle an incoming new password request. 32 | * 33 | * @throws \Illuminate\Validation\ValidationException 34 | */ 35 | public function store(Request $request): RedirectResponse 36 | { 37 | $request->validate([ 38 | 'token' => 'required', 39 | 'email' => 'required|email', 40 | 'password' => ['required', 'confirmed', Rules\Password::defaults()], 41 | ]); 42 | 43 | // Here we will attempt to reset the user's password. If it is successful we 44 | // will update the password on an actual user model and persist it to the 45 | // database. Otherwise we will parse the error and return the response. 46 | $status = Password::reset( 47 | $request->only('email', 'password', 'password_confirmation', 'token'), 48 | function ($user) use ($request) { 49 | $user->forceFill([ 50 | 'password' => Hash::make($request->password), 51 | 'remember_token' => Str::random(60), 52 | ])->save(); 53 | 54 | event(new PasswordReset($user)); 55 | } 56 | ); 57 | 58 | // If the password was successfully reset, we will redirect the user back to 59 | // the application's home authenticated view. If there is an error we can 60 | // redirect them back to where they came from with their error message. 61 | if ($status == Password::PASSWORD_RESET) { 62 | return redirect()->route('login')->with('status', __($status)); 63 | } 64 | 65 | throw ValidationException::withMessages([ 66 | 'email' => [trans($status)], 67 | ]); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/PasswordController.php: -------------------------------------------------------------------------------- 1 | validate([ 19 | 'current_password' => ['required', 'current_password'], 20 | 'password' => ['required', Password::defaults(), 'confirmed'], 21 | ]); 22 | 23 | $request->user()->update([ 24 | 'password' => Hash::make($validated['password']), 25 | ]); 26 | 27 | return back(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/PasswordResetLinkController.php: -------------------------------------------------------------------------------- 1 | session('status'), 22 | ]); 23 | } 24 | 25 | /** 26 | * Handle an incoming password reset link request. 27 | * 28 | * @throws \Illuminate\Validation\ValidationException 29 | */ 30 | public function store(Request $request): RedirectResponse 31 | { 32 | $request->validate([ 33 | 'email' => 'required|email', 34 | ]); 35 | 36 | // We will send the password reset link to this user. Once we have attempted 37 | // to send the link, we will examine the response then see the message we 38 | // need to show to the user. Finally, we'll send out a proper response. 39 | $status = Password::sendResetLink( 40 | $request->only('email') 41 | ); 42 | 43 | if ($status == Password::RESET_LINK_SENT) { 44 | return back()->with('status', __($status)); 45 | } 46 | 47 | throw ValidationException::withMessages([ 48 | 'email' => [trans($status)], 49 | ]); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/RegisteredUserController.php: -------------------------------------------------------------------------------- 1 | validate([ 34 | 'name' => 'required|string|max:255', 35 | 'email' => 'required|string|lowercase|email|max:255|unique:'.User::class, 36 | 'password' => ['required', 'confirmed', Rules\Password::defaults()], 37 | ]); 38 | 39 | $user = User::create([ 40 | 'name' => $request->name, 41 | 'email' => $request->email, 42 | 'password' => Hash::make($request->password), 43 | ]); 44 | 45 | event(new Registered($user)); 46 | 47 | Auth::login($user); 48 | 49 | return redirect(route('dashboard', absolute: false)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/VerifyEmailController.php: -------------------------------------------------------------------------------- 1 | user()->hasVerifiedEmail()) { 18 | return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); 19 | } 20 | 21 | if ($request->user()->markEmailAsVerified()) { 22 | event(new Verified($request->user())); 23 | } 24 | 25 | return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | when(request()->search, fn($query) => $query->where('name', 'like', '%' . request()->search . '%')) 18 | ->select('id', 'name') 19 | ->latest() 20 | ->paginate(7) 21 | ->withQueryString(); 22 | 23 | // render view 24 | return Inertia::render('Dashboard/Permissions/Index', [ 25 | 'permissions' => $permissions 26 | ]); 27 | } 28 | 29 | /** 30 | * Show the form for creating a new resource. 31 | */ 32 | public function create() 33 | { 34 | // 35 | } 36 | 37 | /** 38 | * Store a newly created resource in storage. 39 | */ 40 | public function store(Request $request) 41 | { 42 | // 43 | } 44 | 45 | /** 46 | * Display the specified resource. 47 | */ 48 | public function show(string $id) 49 | { 50 | // 51 | } 52 | 53 | /** 54 | * Show the form for editing the specified resource. 55 | */ 56 | public function edit(string $id) 57 | { 58 | // 59 | } 60 | 61 | /** 62 | * Update the specified resource in storage. 63 | */ 64 | public function update(Request $request, string $id) 65 | { 66 | // 67 | } 68 | 69 | /** 70 | * Remove the specified resource from storage. 71 | */ 72 | public function destroy(string $id) 73 | { 74 | // 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/Http/Controllers/ProfileController.php: -------------------------------------------------------------------------------- 1 | $request->user() instanceof MustVerifyEmail, 23 | 'status' => session('status'), 24 | ]); 25 | } 26 | 27 | /** 28 | * Update the user's profile information. 29 | */ 30 | public function update(ProfileUpdateRequest $request): RedirectResponse 31 | { 32 | $request->user()->fill($request->validated()); 33 | 34 | if ($request->user()->isDirty('email')) { 35 | $request->user()->email_verified_at = null; 36 | } 37 | 38 | $request->user()->save(); 39 | 40 | return Redirect::route('profile.edit'); 41 | } 42 | 43 | /** 44 | * Delete the user's account. 45 | */ 46 | public function destroy(Request $request): RedirectResponse 47 | { 48 | $request->validate([ 49 | 'password' => ['required', 'current_password'], 50 | ]); 51 | 52 | $user = $request->user(); 53 | 54 | Auth::logout(); 55 | 56 | $user->delete(); 57 | 58 | $request->session()->invalidate(); 59 | $request->session()->regenerateToken(); 60 | 61 | return Redirect::to('/'); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/Http/Controllers/RoleController.php: -------------------------------------------------------------------------------- 1 | with('permissions') 22 | ->when(request()->search, fn($query) => $query->where('name', 'like', '%' . request()->search . '%')) 23 | ->select('id', 'name') 24 | ->latest() 25 | ->paginate(7) 26 | ->withQueryString(); 27 | 28 | // get all permission data 29 | $permissions = Permission::query() 30 | ->select('id', 'name') 31 | ->orderBy('name') 32 | ->get(); 33 | 34 | // render view 35 | return Inertia::render('Dashboard/Roles/Index', [ 36 | 'roles' => $roles, 37 | 'permissions' => $permissions 38 | ]); 39 | } 40 | 41 | /** 42 | * Store a newly created resource in storage. 43 | */ 44 | public function store(RoleRequest $request) 45 | { 46 | // create new role data 47 | $role = Role::create(['name' => $request->name]); 48 | 49 | // give permissions to role 50 | $role->givePermissionTo($request->selectedPermission); 51 | 52 | // render view 53 | return back(); 54 | } 55 | 56 | /** 57 | * Update the specified resource in storage. 58 | */ 59 | public function update(RoleRequest $request, Role $role) 60 | { 61 | // update role data 62 | $role->update(['name' => $request->name]); 63 | 64 | // sync role permissions 65 | $role->syncPermissions($request->selectedPermission); 66 | 67 | // render view 68 | return back(); 69 | } 70 | 71 | /** 72 | * Remove the specified resource from storage. 73 | */ 74 | public function destroy(Role $role) 75 | { 76 | // delete role data 77 | $role->delete(); 78 | 79 | // render view 80 | return back(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/Http/Controllers/UserController.php: -------------------------------------------------------------------------------- 1 | with('roles') 22 | ->when(request()->search, fn($query) => $query->where('name', 'like', '%' . request()->search . '%')) 23 | ->select('id', 'name', 'avatar', 'email') 24 | ->latest() 25 | ->paginate(7) 26 | ->withQueryString(); 27 | 28 | // render view 29 | return Inertia::render('Dashboard/Users/Index', [ 30 | 'users' => $users 31 | ]); 32 | } 33 | 34 | /** 35 | * Show the form for creating a new resource. 36 | */ 37 | public function create() 38 | { 39 | // get all role data 40 | $roles = Role::query() 41 | ->select('id', 'name') 42 | ->orderBy('name') 43 | ->get(); 44 | 45 | // render view 46 | return Inertia::render('Dashboard/Users/Create', [ 47 | 'roles' => $roles 48 | ]); 49 | } 50 | 51 | /** 52 | * Store a newly created resource in storage. 53 | */ 54 | public function store(UserRequest $request) 55 | { 56 | // create new user data 57 | $user = User::create([ 58 | 'name' => $request->name, 59 | 'email' => $request->email, 60 | 'password' => bcrypt($request->password), 61 | ]); 62 | 63 | // assign role to user 64 | $user->assignRole($request->selectedRoles); 65 | 66 | // render view 67 | return to_route('users.index'); 68 | } 69 | 70 | /** 71 | * Show the form for editing the specified resource. 72 | */ 73 | public function edit(User $user) 74 | { 75 | // get all role data 76 | $roles = Role::query() 77 | ->select('id', 'name') 78 | ->orderBy('name') 79 | ->get(); 80 | 81 | // load relationship 82 | $user->load(['roles' => fn($query) => $query->select('id', 'name'), 'roles.permissions' => fn($query) => $query->select('id', 'name')]); 83 | 84 | // render view 85 | return Inertia::render('Dashboard/Users/Edit', [ 86 | 'roles' => $roles, 87 | 'user' => $user 88 | ]); 89 | } 90 | 91 | /** 92 | * Update the specified resource in storage. 93 | */ 94 | public function update(UserRequest $request, User $user) 95 | { 96 | // check if user send request password 97 | if ($request->password) 98 | // update user data password 99 | $user->update([ 100 | 'password' => bcrypt($request->password), 101 | ]); 102 | 103 | // update user data name 104 | $user->update([ 105 | 'name' => $request->name, 106 | ]); 107 | 108 | // assign role to user 109 | $user->syncRoles($request->selectedRoles); 110 | 111 | // render view 112 | return to_route('users.index'); 113 | } 114 | 115 | /** 116 | * Remove the specified resource from storage. 117 | */ 118 | public function destroy($id) 119 | { 120 | $ids = explode(',', $id); 121 | 122 | if (count($ids) > 0) 123 | User::whereIn('id', $ids)->delete(); 124 | else 125 | User::findOrFail($id)->delete(); 126 | 127 | // render view 128 | return back(); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /app/Http/Middleware/HandleInertiaRequests.php: -------------------------------------------------------------------------------- 1 | 29 | */ 30 | public function share(Request $request): array 31 | { 32 | return [ 33 | ...parent::share($request), 34 | 'auth' => [ 35 | 'user' => $request->user(), 36 | 'permissions' => $request->user() ? $request->user()->getPermissions() : [], 37 | 'super' => $request->user() ? $request->user()->isSuperAdmin() : false, 38 | ], 39 | ]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Http/Requests/Auth/LoginRequest.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | public function rules(): array 28 | { 29 | return [ 30 | 'email' => ['required', 'string', 'email'], 31 | 'password' => ['required', 'string'], 32 | ]; 33 | } 34 | 35 | /** 36 | * Attempt to authenticate the request's credentials. 37 | * 38 | * @throws \Illuminate\Validation\ValidationException 39 | */ 40 | public function authenticate(): void 41 | { 42 | $this->ensureIsNotRateLimited(); 43 | 44 | if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) { 45 | RateLimiter::hit($this->throttleKey()); 46 | 47 | throw ValidationException::withMessages([ 48 | 'email' => trans('auth.failed'), 49 | ]); 50 | } 51 | 52 | RateLimiter::clear($this->throttleKey()); 53 | } 54 | 55 | /** 56 | * Ensure the login request is not rate limited. 57 | * 58 | * @throws \Illuminate\Validation\ValidationException 59 | */ 60 | public function ensureIsNotRateLimited(): void 61 | { 62 | if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) { 63 | return; 64 | } 65 | 66 | event(new Lockout($this)); 67 | 68 | $seconds = RateLimiter::availableIn($this->throttleKey()); 69 | 70 | throw ValidationException::withMessages([ 71 | 'email' => trans('auth.throttle', [ 72 | 'seconds' => $seconds, 73 | 'minutes' => ceil($seconds / 60), 74 | ]), 75 | ]); 76 | } 77 | 78 | /** 79 | * Get the rate limiting throttle key for the request. 80 | */ 81 | public function throttleKey(): string 82 | { 83 | return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip()); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/Http/Requests/ProfileUpdateRequest.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | public function rules(): array 17 | { 18 | return [ 19 | 'name' => ['required', 'string', 'max:255'], 20 | 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)], 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Models/Cart.php: -------------------------------------------------------------------------------- 1 | belongsTo(Product::class); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Models/Category.php: -------------------------------------------------------------------------------- 1 | hasMany(Product::class); 30 | } 31 | 32 | /** 33 | * image 34 | * 35 | * @return Attribute 36 | */ 37 | protected function image(): Attribute 38 | { 39 | return Attribute::make( 40 | get: fn ($value) => asset('/storage/category/' . $value), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Models/Customer.php: -------------------------------------------------------------------------------- 1 | belongsTo(Category::class); 30 | } 31 | 32 | /** 33 | * image 34 | * 35 | * @return Attribute 36 | */ 37 | protected function image(): Attribute 38 | { 39 | return Attribute::make( 40 | get: fn ($value) => asset('/storage/products/' . $value), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Models/Profit.php: -------------------------------------------------------------------------------- 1 | belongsTo(Transaction::class); 31 | } 32 | 33 | /** 34 | * createdAt 35 | * 36 | * @return Attribute 37 | */ 38 | protected function createdAt(): Attribute 39 | { 40 | return Attribute::make( 41 | get: fn ($value) => Carbon::parse($value)->format('d-M-Y H:i:s'), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Models/Transaction.php: -------------------------------------------------------------------------------- 1 | hasMany(TransactionDetail::class); 31 | } 32 | 33 | /** 34 | * customer 35 | * 36 | * @return void 37 | */ 38 | public function customer() 39 | { 40 | return $this->belongsTo(Customer::class); 41 | } 42 | 43 | /** 44 | * cashier 45 | * 46 | * @return void 47 | */ 48 | public function cashier() 49 | { 50 | return $this->belongsTo(User::class, 'cashier_id'); 51 | } 52 | 53 | /** 54 | * profits 55 | * 56 | * @return void 57 | */ 58 | public function profits() 59 | { 60 | return $this->hasMany(Profit::class); 61 | } 62 | 63 | /** 64 | * createdAt 65 | * 66 | * @return Attribute 67 | */ 68 | protected function createdAt(): Attribute 69 | { 70 | return Attribute::make( 71 | get: fn ($value) => Carbon::parse($value)->format('d-M-Y H:i:s'), 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/Models/TransactionDetail.php: -------------------------------------------------------------------------------- 1 | belongsTo(Transaction::class); 29 | } 30 | 31 | /** 32 | * product 33 | * 34 | * @return void 35 | */ 36 | public function product() 37 | { 38 | return $this->belongsTo(Product::class); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Models/User.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | protected $fillable = [ 21 | 'name', 22 | 'email', 23 | 'password', 24 | ]; 25 | 26 | /** 27 | * The attributes that should be hidden for serialization. 28 | * 29 | * @var array 30 | */ 31 | protected $hidden = [ 32 | 'password', 33 | 'remember_token', 34 | ]; 35 | 36 | /** 37 | * Get the attributes that should be cast. 38 | * 39 | * @return array 40 | */ 41 | protected function casts(): array 42 | { 43 | return [ 44 | 'email_verified_at' => 'datetime', 45 | 'password' => 'hashed', 46 | ]; 47 | } 48 | 49 | /** 50 | * get all permissions users 51 | */ 52 | public function getPermissions() 53 | { 54 | return $this->getAllPermissions()->mapWithKeys(function ($permission) { 55 | return [ 56 | $permission['name'] => true 57 | ]; 58 | }); 59 | } 60 | 61 | /** 62 | * check role isSuperAdmin 63 | */ 64 | public function isSuperAdmin() 65 | { 66 | return $this->hasRole('super-admin'); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | handleCommand(new ArgvInput); 14 | 15 | exit($status); 16 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | withRouting( 9 | web: __DIR__.'/../routes/web.php', 10 | commands: __DIR__.'/../routes/console.php', 11 | health: '/up', 12 | ) 13 | ->withMiddleware(function (Middleware $middleware) { 14 | $middleware->web(append: [ 15 | \App\Http\Middleware\HandleInertiaRequests::class, 16 | \Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class, 17 | ]); 18 | 19 | // 20 | }) 21 | ->withExceptions(function (Exceptions $exceptions) { 22 | // 23 | })->create(); 24 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /bootstrap/providers.php: -------------------------------------------------------------------------------- 1 | env('CACHE_STORE', 'database'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Cache Stores 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the cache "stores" for your application as 26 | | well as their drivers. You may even define multiple stores for the 27 | | same cache driver to group types of items stored in your caches. 28 | | 29 | | Supported drivers: "array", "database", "file", "memcached", 30 | | "redis", "dynamodb", "octane", "null" 31 | | 32 | */ 33 | 34 | 'stores' => [ 35 | 36 | 'array' => [ 37 | 'driver' => 'array', 38 | 'serialize' => false, 39 | ], 40 | 41 | 'database' => [ 42 | 'driver' => 'database', 43 | 'table' => env('DB_CACHE_TABLE', 'cache'), 44 | 'connection' => env('DB_CACHE_CONNECTION'), 45 | 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'), 46 | ], 47 | 48 | 'file' => [ 49 | 'driver' => 'file', 50 | 'path' => storage_path('framework/cache/data'), 51 | 'lock_path' => storage_path('framework/cache/data'), 52 | ], 53 | 54 | 'memcached' => [ 55 | 'driver' => 'memcached', 56 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 57 | 'sasl' => [ 58 | env('MEMCACHED_USERNAME'), 59 | env('MEMCACHED_PASSWORD'), 60 | ], 61 | 'options' => [ 62 | // Memcached::OPT_CONNECT_TIMEOUT => 2000, 63 | ], 64 | 'servers' => [ 65 | [ 66 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 67 | 'port' => env('MEMCACHED_PORT', 11211), 68 | 'weight' => 100, 69 | ], 70 | ], 71 | ], 72 | 73 | 'redis' => [ 74 | 'driver' => 'redis', 75 | 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'), 76 | 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'), 77 | ], 78 | 79 | 'dynamodb' => [ 80 | 'driver' => 'dynamodb', 81 | 'key' => env('AWS_ACCESS_KEY_ID'), 82 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 83 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 84 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), 85 | 'endpoint' => env('DYNAMODB_ENDPOINT'), 86 | ], 87 | 88 | 'octane' => [ 89 | 'driver' => 'octane', 90 | ], 91 | 92 | ], 93 | 94 | /* 95 | |-------------------------------------------------------------------------- 96 | | Cache Key Prefix 97 | |-------------------------------------------------------------------------- 98 | | 99 | | When utilizing the APC, database, memcached, Redis, and DynamoDB cache 100 | | stores, there might be other applications using the same cache. For 101 | | that reason, you may prefix every cache key to avoid collisions. 102 | | 103 | */ 104 | 105 | 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'), 106 | 107 | ]; 108 | -------------------------------------------------------------------------------- /config/filesystems.php: -------------------------------------------------------------------------------- 1 | env('FILESYSTEM_DISK', 'local'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Filesystem Disks 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Below you may configure as many filesystem disks as necessary, and you 24 | | may even configure multiple disks for the same driver. Examples for 25 | | most supported storage drivers are configured here for reference. 26 | | 27 | | Supported drivers: "local", "ftp", "sftp", "s3" 28 | | 29 | */ 30 | 31 | 'disks' => [ 32 | 33 | 'local' => [ 34 | 'driver' => 'local', 35 | 'root' => storage_path('app'), 36 | 'throw' => false, 37 | ], 38 | 39 | 'public' => [ 40 | 'driver' => 'local', 41 | 'root' => storage_path('app/public'), 42 | 'url' => env('APP_URL').'/storage', 43 | 'visibility' => 'public', 44 | 'throw' => false, 45 | ], 46 | 47 | 's3' => [ 48 | 'driver' => 's3', 49 | 'key' => env('AWS_ACCESS_KEY_ID'), 50 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 51 | 'region' => env('AWS_DEFAULT_REGION'), 52 | 'bucket' => env('AWS_BUCKET'), 53 | 'url' => env('AWS_URL'), 54 | 'endpoint' => env('AWS_ENDPOINT'), 55 | 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 56 | 'throw' => false, 57 | ], 58 | 59 | ], 60 | 61 | /* 62 | |-------------------------------------------------------------------------- 63 | | Symbolic Links 64 | |-------------------------------------------------------------------------- 65 | | 66 | | Here you may configure the symbolic links that will be created when the 67 | | `storage:link` Artisan command is executed. The array keys should be 68 | | the locations of the links and the values should be their targets. 69 | | 70 | */ 71 | 72 | 'links' => [ 73 | public_path('storage') => storage_path('app/public'), 74 | ], 75 | 76 | ]; 77 | -------------------------------------------------------------------------------- /config/mail.php: -------------------------------------------------------------------------------- 1 | env('MAIL_MAILER', 'log'), 18 | 19 | /* 20 | |-------------------------------------------------------------------------- 21 | | Mailer Configurations 22 | |-------------------------------------------------------------------------- 23 | | 24 | | Here you may configure all of the mailers used by your application plus 25 | | their respective settings. Several examples have been configured for 26 | | you and you are free to add your own as your application requires. 27 | | 28 | | Laravel supports a variety of mail "transport" drivers that can be used 29 | | when delivering an email. You may specify which one you're using for 30 | | your mailers below. You may also add additional mailers if needed. 31 | | 32 | | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2", 33 | | "postmark", "resend", "log", "array", 34 | | "failover", "roundrobin" 35 | | 36 | */ 37 | 38 | 'mailers' => [ 39 | 40 | 'smtp' => [ 41 | 'transport' => 'smtp', 42 | 'url' => env('MAIL_URL'), 43 | 'host' => env('MAIL_HOST', '127.0.0.1'), 44 | 'port' => env('MAIL_PORT', 2525), 45 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'), 46 | 'username' => env('MAIL_USERNAME'), 47 | 'password' => env('MAIL_PASSWORD'), 48 | 'timeout' => null, 49 | 'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url(env('APP_URL', 'http://localhost'), PHP_URL_HOST)), 50 | ], 51 | 52 | 'ses' => [ 53 | 'transport' => 'ses', 54 | ], 55 | 56 | 'postmark' => [ 57 | 'transport' => 'postmark', 58 | // 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'), 59 | // 'client' => [ 60 | // 'timeout' => 5, 61 | // ], 62 | ], 63 | 64 | 'resend' => [ 65 | 'transport' => 'resend', 66 | ], 67 | 68 | 'sendmail' => [ 69 | 'transport' => 'sendmail', 70 | 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), 71 | ], 72 | 73 | 'log' => [ 74 | 'transport' => 'log', 75 | 'channel' => env('MAIL_LOG_CHANNEL'), 76 | ], 77 | 78 | 'array' => [ 79 | 'transport' => 'array', 80 | ], 81 | 82 | 'failover' => [ 83 | 'transport' => 'failover', 84 | 'mailers' => [ 85 | 'smtp', 86 | 'log', 87 | ], 88 | ], 89 | 90 | 'roundrobin' => [ 91 | 'transport' => 'roundrobin', 92 | 'mailers' => [ 93 | 'ses', 94 | 'postmark', 95 | ], 96 | ], 97 | 98 | ], 99 | 100 | /* 101 | |-------------------------------------------------------------------------- 102 | | Global "From" Address 103 | |-------------------------------------------------------------------------- 104 | | 105 | | You may wish for all emails sent by your application to be sent from 106 | | the same address. Here you may specify a name and address that is 107 | | used globally for all emails that are sent by your application. 108 | | 109 | */ 110 | 111 | 'from' => [ 112 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), 113 | 'name' => env('MAIL_FROM_NAME', 'Example'), 114 | ], 115 | 116 | ]; 117 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'token' => env('POSTMARK_TOKEN'), 19 | ], 20 | 21 | 'ses' => [ 22 | 'key' => env('AWS_ACCESS_KEY_ID'), 23 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 24 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 25 | ], 26 | 27 | 'resend' => [ 28 | 'key' => env('RESEND_KEY'), 29 | ], 30 | 31 | 'slack' => [ 32 | 'notifications' => [ 33 | 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), 34 | 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), 35 | ], 36 | ], 37 | 38 | ]; 39 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class UserFactory extends Factory 13 | { 14 | /** 15 | * The current password being used by the factory. 16 | */ 17 | protected static ?string $password; 18 | 19 | /** 20 | * Define the model's default state. 21 | * 22 | * @return array 23 | */ 24 | public function definition(): array 25 | { 26 | return [ 27 | 'name' => fake()->name(), 28 | 'email' => fake()->unique()->safeEmail(), 29 | 'email_verified_at' => now(), 30 | 'password' => static::$password ??= Hash::make('password'), 31 | 'remember_token' => Str::random(10), 32 | ]; 33 | } 34 | 35 | /** 36 | * Indicate that the model's email address should be unverified. 37 | */ 38 | public function unverified(): static 39 | { 40 | return $this->state(fn (array $attributes) => [ 41 | 'email_verified_at' => null, 42 | ]); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name'); 17 | $table->string('email')->unique(); 18 | $table->timestamp('email_verified_at')->nullable(); 19 | $table->string('password'); 20 | $table->rememberToken(); 21 | $table->timestamps(); 22 | }); 23 | 24 | Schema::create('password_reset_tokens', function (Blueprint $table) { 25 | $table->string('email')->primary(); 26 | $table->string('token'); 27 | $table->timestamp('created_at')->nullable(); 28 | }); 29 | 30 | Schema::create('sessions', function (Blueprint $table) { 31 | $table->string('id')->primary(); 32 | $table->foreignId('user_id')->nullable()->index(); 33 | $table->string('ip_address', 45)->nullable(); 34 | $table->text('user_agent')->nullable(); 35 | $table->longText('payload'); 36 | $table->integer('last_activity')->index(); 37 | }); 38 | } 39 | 40 | /** 41 | * Reverse the migrations. 42 | */ 43 | public function down(): void 44 | { 45 | Schema::dropIfExists('users'); 46 | Schema::dropIfExists('password_reset_tokens'); 47 | Schema::dropIfExists('sessions'); 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000001_create_cache_table.php: -------------------------------------------------------------------------------- 1 | string('key')->primary(); 16 | $table->mediumText('value'); 17 | $table->integer('expiration'); 18 | }); 19 | 20 | Schema::create('cache_locks', function (Blueprint $table) { 21 | $table->string('key')->primary(); 22 | $table->string('owner'); 23 | $table->integer('expiration'); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('cache'); 33 | Schema::dropIfExists('cache_locks'); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000002_create_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('queue')->index(); 17 | $table->longText('payload'); 18 | $table->unsignedTinyInteger('attempts'); 19 | $table->unsignedInteger('reserved_at')->nullable(); 20 | $table->unsignedInteger('available_at'); 21 | $table->unsignedInteger('created_at'); 22 | }); 23 | 24 | Schema::create('job_batches', function (Blueprint $table) { 25 | $table->string('id')->primary(); 26 | $table->string('name'); 27 | $table->integer('total_jobs'); 28 | $table->integer('pending_jobs'); 29 | $table->integer('failed_jobs'); 30 | $table->longText('failed_job_ids'); 31 | $table->mediumText('options')->nullable(); 32 | $table->integer('cancelled_at')->nullable(); 33 | $table->integer('created_at'); 34 | $table->integer('finished_at')->nullable(); 35 | }); 36 | 37 | Schema::create('failed_jobs', function (Blueprint $table) { 38 | $table->id(); 39 | $table->string('uuid')->unique(); 40 | $table->text('connection'); 41 | $table->text('queue'); 42 | $table->longText('payload'); 43 | $table->longText('exception'); 44 | $table->timestamp('failed_at')->useCurrent(); 45 | }); 46 | } 47 | 48 | /** 49 | * Reverse the migrations. 50 | */ 51 | public function down(): void 52 | { 53 | Schema::dropIfExists('jobs'); 54 | Schema::dropIfExists('job_batches'); 55 | Schema::dropIfExists('failed_jobs'); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /database/migrations/2024_06_13_091315_add_avatar_field_to_users_table.php: -------------------------------------------------------------------------------- 1 | string('avatar')->nullable(); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('users', function (Blueprint $table) { 25 | $table->dropColumn('avatar'); 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2024_06_13_125039_create_customers_table.php: -------------------------------------------------------------------------------- 1 | id(); 17 | $table->string('name'); 18 | $table->bigInteger('no_telp'); 19 | $table->text('address'); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::dropIfExists('customers'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /database/migrations/2024_06_13_130507_create_categories_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('image'); 19 | $table->string('name'); 20 | $table->text('description'); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('categories'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /database/migrations/2024_06_13_131744_create_products_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->unsignedBigInteger('category_id'); 19 | $table->string('image'); 20 | $table->string('barcode')->unique(); 21 | $table->string('title'); 22 | $table->text('description'); 23 | $table->bigInteger('buy_price'); 24 | $table->bigInteger('sell_price'); 25 | $table->integer('stock'); 26 | $table->timestamps(); 27 | 28 | //relationship categories 29 | $table->foreign('category_id')->references('id')->on('categories'); 30 | }); 31 | } 32 | 33 | /** 34 | * Reverse the migrations. 35 | * 36 | * @return void 37 | */ 38 | public function down() 39 | { 40 | Schema::dropIfExists('products'); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /database/migrations/2024_06_13_132800_create_transactions_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->unsignedBigInteger('cashier_id'); 19 | $table->unsignedBigInteger('customer_id')->nullable(); 20 | $table->string('invoice'); 21 | $table->bigInteger('cash'); 22 | $table->bigInteger('change'); 23 | $table->bigInteger('discount'); 24 | $table->bigInteger('grand_total'); 25 | $table->timestamps(); 26 | 27 | //relationship users 28 | $table->foreign('cashier_id')->references('id')->on('users'); 29 | 30 | //relationship customers 31 | $table->foreign('customer_id')->references('id')->on('customers'); 32 | }); 33 | } 34 | 35 | /** 36 | * Reverse the migrations. 37 | * 38 | * @return void 39 | */ 40 | public function down() 41 | { 42 | Schema::dropIfExists('transactions'); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /database/migrations/2024_06_13_133940_create_transaction_details_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->unsignedBigInteger('transaction_id'); 19 | $table->unsignedBigInteger('product_id'); 20 | $table->integer('qty'); 21 | $table->bigInteger('price'); 22 | $table->timestamps(); 23 | 24 | //relationship transactions 25 | $table->foreign('transaction_id')->references('id')->on('transactions'); 26 | 27 | //relationship products 28 | $table->foreign('product_id')->references('id')->on('products'); 29 | }); 30 | } 31 | 32 | /** 33 | * Reverse the migrations. 34 | * 35 | * @return void 36 | */ 37 | public function down() 38 | { 39 | Schema::dropIfExists('transaction_details'); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /database/migrations/2024_06_13_133948_create_carts_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->unsignedBigInteger('cashier_id'); 19 | $table->unsignedBigInteger('product_id'); 20 | $table->integer('qty'); 21 | $table->bigInteger('price'); 22 | $table->timestamps(); 23 | 24 | //relationship users 25 | $table->foreign('cashier_id')->references('id')->on('users'); 26 | 27 | //relationship products 28 | $table->foreign('product_id')->references('id')->on('products'); 29 | }); 30 | } 31 | 32 | /** 33 | * Reverse the migrations. 34 | * 35 | * @return void 36 | */ 37 | public function down() 38 | { 39 | Schema::dropIfExists('carts'); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /database/migrations/2024_06_13_133955_create_profits_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->unsignedBigInteger('transaction_id'); 19 | $table->bigInteger('total'); 20 | $table->timestamps(); 21 | 22 | //relationship transactions 23 | $table->foreign('transaction_id')->references('id')->on('transactions'); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('profits'); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call([ 17 | PermissionSeeder::class, 18 | RoleSeeder::class, 19 | UserSeeder::class, 20 | ]); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /database/seeders/PermissionSeeder.php: -------------------------------------------------------------------------------- 1 | 'dashboard-access']); 18 | 19 | // users permissions 20 | Permission::create(['name' => 'users-access']); 21 | Permission::create(['name' => 'users-create']); 22 | Permission::create(['name' => 'users-update']); 23 | Permission::create(['name' => 'users-delete']); 24 | 25 | // roles permissions 26 | Permission::create(['name' => 'roles-access']); 27 | Permission::create(['name' => 'roles-create']); 28 | Permission::create(['name' => 'roles-update']); 29 | Permission::create(['name' => 'roles-delete']); 30 | 31 | // permissions permissions 32 | Permission::create(['name' => 'permissions-access']); 33 | Permission::create(['name' => 'permissions-create']); 34 | Permission::create(['name' => 'permissions-update']); 35 | Permission::create(['name' => 'permissions-delete']); 36 | 37 | //permission categories 38 | Permission::create(['name' => 'categories-access']); 39 | Permission::create(['name' => 'categories-create']); 40 | Permission::create(['name' => 'categories-edit']); 41 | Permission::create(['name' => 'categories-delete']); 42 | 43 | //permission products 44 | Permission::create(['name' => 'products-access']); 45 | Permission::create(['name' => 'products-create']); 46 | Permission::create(['name' => 'products-edit']); 47 | Permission::create(['name' => 'products-delete']); 48 | 49 | //permission customers 50 | Permission::create(['name' => 'customers-access']); 51 | Permission::create(['name' => 'customers-create']); 52 | Permission::create(['name' => 'customers-edit']); 53 | Permission::create(['name' => 'customers-delete']); 54 | 55 | //permission transactions 56 | Permission::create(['name' => 'transactions-access']); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /database/seeders/RoleSeeder.php: -------------------------------------------------------------------------------- 1 | createRoleWithPermissions('users-access', '%users%'); 19 | $this->createRoleWithPermissions('roles-access', '%roles%'); 20 | $this->createRoleWithPermissions('permission-access', '%permissions%'); 21 | $this->createRoleWithPermissions('categories-access', '%categories%'); 22 | $this->createRoleWithPermissions('products-access', '%products%'); 23 | $this->createRoleWithPermissions('customers-access', '%customers%'); 24 | $this->createRoleWithPermissions('transactions-access', '%transactions%'); 25 | 26 | Role::create(['name' => 'super-admin']); 27 | } 28 | 29 | private function createRoleWithPermissions($roleName, $permissionNamePattern) 30 | { 31 | $permissions = Permission::where('name', 'like', $permissionNamePattern)->get(); 32 | $role = Role::create(['name' => $roleName]); 33 | $role->givePermissionTo($permissions); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /database/seeders/UserSeeder.php: -------------------------------------------------------------------------------- 1 | 'Arya Dwi Putra', 20 | 'email' => 'arya@gmail.com', 21 | 'password' => bcrypt('password'), 22 | ]); 23 | 24 | // get admin role 25 | $role = Role::where('name', 'super-admin')->first(); 26 | 27 | // get all permissions 28 | $permissions = Permission::all(); 29 | 30 | // assign role to user 31 | $user->syncPermissions($permissions); 32 | 33 | // assign a role to user 34 | $user->assignRole($role); 35 | 36 | $cashier = User::create([ 37 | 'name' => 'Cashier', 38 | 'email' => 'cashier@gmail.com', 39 | 'password' => bcrypt('password'), 40 | ]); 41 | 42 | $transactionsPermission = Permission::where('name', 'transactions-access')->first(); 43 | 44 | $cashier->syncPermissions($transactionsPermission); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": ["resources/js/*"], 6 | "ziggy-js": ["./vendor/tightenco/ziggy"] 7 | } 8 | }, 9 | "exclude": ["node_modules", "public"] 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build" 7 | }, 8 | "devDependencies": { 9 | "@headlessui/react": "^1.4.2", 10 | "@inertiajs/react": "^1.0.0", 11 | "@tailwindcss/forms": "^0.5.3", 12 | "@vitejs/plugin-react": "^4.2.0", 13 | "autoprefixer": "^10.4.12", 14 | "axios": "^1.6.4", 15 | "laravel-vite-plugin": "^1.0", 16 | "postcss": "^8.4.31", 17 | "react": "^18.2.0", 18 | "react-dom": "^18.2.0", 19 | "tailwindcss": "^3.2.1", 20 | "vite": "^5.0" 21 | }, 22 | "dependencies": { 23 | "@tabler/icons-react": "^3.5.0", 24 | "jsbarcode": "^3.11.6", 25 | "react-hot-toast": "^2.4.1", 26 | "sweetalert2": "^11.11.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | tests/Unit 10 | 11 | 12 | tests/Feature 13 | 14 | 15 | 16 | 17 | app 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Send Requests To Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /public/assets/background/Hero-Banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/background/Hero-Banner.png -------------------------------------------------------------------------------- /public/assets/background/alqowy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/assets/background/benefit_illustration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/background/benefit_illustration.png -------------------------------------------------------------------------------- /public/assets/icon/3dcube.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/assets/icon/Group 7-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/assets/icon/Group 7-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/assets/icon/Group 7.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/assets/icon/add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/assets/icon/arrow-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /public/assets/icon/avatar-group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/icon/avatar-group.png -------------------------------------------------------------------------------- /public/assets/icon/award-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/assets/icon/award.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/assets/icon/book.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/assets/icon/brifecase-tick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/assets/icon/crown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/assets/icon/medal-star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/assets/icon/note-add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/assets/icon/note-favorite.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/assets/icon/play-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/assets/icon/profile-2user.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/assets/icon/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/assets/icon/tick-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/assets/icon/video-play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/assets/photo/photo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/photo/photo1.png -------------------------------------------------------------------------------- /public/assets/photo/photo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/photo/photo2.png -------------------------------------------------------------------------------- /public/assets/photo/photo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/photo/photo3.png -------------------------------------------------------------------------------- /public/assets/photo/photo4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/photo/photo4.png -------------------------------------------------------------------------------- /public/assets/photo/photo5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/photo/photo5.png -------------------------------------------------------------------------------- /public/assets/thumbnail/image-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/thumbnail/image-1.png -------------------------------------------------------------------------------- /public/assets/thumbnail/image-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/thumbnail/image-2.png -------------------------------------------------------------------------------- /public/assets/thumbnail/image-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/thumbnail/image-3.png -------------------------------------------------------------------------------- /public/assets/thumbnail/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/thumbnail/image.png -------------------------------------------------------------------------------- /public/assets/thumbnail/thumbnail-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/thumbnail/thumbnail-1.png -------------------------------------------------------------------------------- /public/assets/thumbnail/thumbnail-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/thumbnail/thumbnail-2.png -------------------------------------------------------------------------------- /public/assets/thumbnail/thumbnail-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/thumbnail/thumbnail-3.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/favicon.ico -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | handleRequest(Request::capture()); 18 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /resources/css/app.css: -------------------------------------------------------------------------------- 1 | /* @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; */ 4 | 5 | @import 'tailwindcss/base'; 6 | @import 'tailwindcss/components'; 7 | @import 'tailwindcss/utilities'; 8 | 9 | -------------------------------------------------------------------------------- /resources/js/Components/ApplicationLogo.jsx: -------------------------------------------------------------------------------- 1 | export default function ApplicationLogo(props) { 2 | return ( 3 | 4 | 5 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /resources/js/Components/Checkbox.jsx: -------------------------------------------------------------------------------- 1 | export default function Checkbox({ className = '', ...props }) { 2 | return ( 3 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /resources/js/Components/DangerButton.jsx: -------------------------------------------------------------------------------- 1 | export default function DangerButton({ className = '', disabled, children, ...props }) { 2 | return ( 3 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /resources/js/Components/Dashboard/Barcode.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react'; 2 | import JsBarcode from 'jsbarcode'; 3 | 4 | const Barcode = ({ 5 | value, 6 | format, 7 | width, 8 | height, 9 | displayValue = true, 10 | text, 11 | fontOptions, 12 | font, 13 | textAlign, 14 | textPosition, 15 | textMargin, 16 | fontSize, 17 | background, 18 | lineColor, 19 | margin, 20 | marginTop, 21 | marginBottom, 22 | marginLeft, 23 | marginRight, 24 | flat, 25 | ean128, 26 | elementTag = 'svg' 27 | }) => { 28 | const barcodeRef = useRef(null); 29 | 30 | useEffect(() => { 31 | const settings = { 32 | format, 33 | width, 34 | height, 35 | displayValue, 36 | text, 37 | fontOptions, 38 | font, 39 | textAlign, 40 | textPosition, 41 | textMargin, 42 | fontSize, 43 | background, 44 | lineColor, 45 | margin, 46 | marginTop, 47 | marginBottom, 48 | marginLeft, 49 | marginRight, 50 | flat, 51 | ean128, 52 | valid: function (valid) { 53 | // Handle valid state if needed 54 | }, 55 | }; 56 | 57 | removeUndefinedProps(settings); 58 | 59 | JsBarcode(barcodeRef.current, value, settings); 60 | }, [value, format, width, height, displayValue, text, fontOptions, font, textAlign, textPosition, textMargin, fontSize, background, lineColor, margin, marginTop, marginBottom, marginLeft, marginRight, flat, ean128]); 61 | 62 | return React.createElement(elementTag, { ref: barcodeRef, id: "barcodegen" }); 63 | }; 64 | 65 | // Helper function to remove undefined properties from an object 66 | function removeUndefinedProps(obj) { 67 | Object.keys(obj).forEach(key => obj[key] === undefined ? delete obj[key] : {}); 68 | } 69 | 70 | export default Barcode; 71 | -------------------------------------------------------------------------------- /resources/js/Components/Dashboard/Button.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from '@inertiajs/react' 2 | import React from 'react' 3 | import { useForm } from '@inertiajs/react'; 4 | import Swal from 'sweetalert2' 5 | 6 | export default function Button({ className, icon, label, type, href, added, url, id, ...props }) { 7 | 8 | const { delete: destroy } = useForm(); 9 | 10 | const deleteData = async (url) => { 11 | Swal.fire({ 12 | title: 'Apakah kamu yakin ingin menghapus data ini ?', 13 | text: "Data yang dihapus tidak dapat dikembalikan!", 14 | icon: 'warning', 15 | showCancelButton: true, 16 | confirmButtonColor: '#3085d6', 17 | cancelButtonColor: '#d33', 18 | confirmButtonText: 'Ya, tolong hapus!', 19 | cancelButtonText: 'Tidak' 20 | }).then((result) => { 21 | if (result.isConfirmed) { 22 | destroy(url) 23 | 24 | Swal.fire({ 25 | title: 'Success!', 26 | text: 'Data berhasil dihapus!', 27 | icon: 'success', 28 | showConfirmButton: false, 29 | timer: 1500 30 | }) 31 | } 32 | }) 33 | } 34 | 35 | return ( 36 | <> 37 | {type === 'link' && 38 | 39 | {icon} {label} 40 | 41 | } 42 | {type === 'button' && 43 | 46 | } 47 | {type === 'submit' && 48 | 51 | } 52 | {type === 'delete' && 53 | 56 | } 57 | {type === 'modal' && 58 | 61 | } 62 | {type === 'edit' && 63 | 64 | {icon} 65 | 66 | } 67 | {type === 'bulk' && 68 | 71 | } 72 | 73 | ) 74 | } 75 | -------------------------------------------------------------------------------- /resources/js/Components/Dashboard/Card.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function Card({ icon, title, children, footer, className, form }) { 4 | return ( 5 | <> 6 |
7 |
8 |
9 | {title} 10 |
11 |
12 |
13 | {children} 14 |
15 |
16 | {footer} 17 |
18 |
19 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /resources/js/Components/Dashboard/Checkbox.jsx: -------------------------------------------------------------------------------- 1 | export default function Checkbox({ label, errors, ...props }) { 2 | return ( 3 |
4 |
5 | 10 | 11 | 12 | {errors && ( 13 | {errors} 14 | )} 15 |
16 |
17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /resources/js/Components/Dashboard/Header.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function Header({ children, title, subtitle }) { 4 | return ( 5 |
6 |
7 |
{title}
8 |
{subtitle}
9 |
10 | {children} 11 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /resources/js/Components/Dashboard/Input.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function Input({ label, type, className, errors, ...props }) { 4 | return ( 5 |
6 | 7 | 12 | {errors && ( 13 | {errors} 14 | )} 15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /resources/js/Components/Dashboard/InputSelect.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Listbox } from '@headlessui/react' 3 | import { IconChevronDown, IconCircle, IconCircleFilled } from '@tabler/icons-react' 4 | 5 | export default function InputSelect({ selected, data, setSelected, label, errors, placeholder, multiple = false, searchable = false, displayKey = 'name' }) { 6 | const [search, setSearch] = useState('') 7 | 8 | const filteredData = data.filter(item => 9 | item[displayKey]?.toLowerCase().includes(search.toLowerCase()) 10 | ) 11 | 12 | return ( 13 |
14 | 15 | 16 | 17 | {multiple ? ( 18 | selected.length > 0 ? selected.map(item => item[displayKey]).join(', ') : placeholder 19 | ) : ( 20 | selected ? selected[displayKey] : placeholder 21 | )} 22 | 23 | 24 | 25 | {searchable && ( 26 | setSearch(e.target.value)} 30 | placeholder="Search..." 31 | className="w-full px-3 py-1.5 mb-2 text-sm border rounded-md bg-white text-gray-700 border-gray-200 focus:outline-none focus:border-gray-300 dark:bg-gray-900 dark:text-gray-300 dark:border-gray-800 dark:focus:border-gray-700" 32 | /> 33 | )} 34 | {filteredData.map((item) => ( 35 | 36 | {({ selected }) => ( 37 |
39 | {selected ? : } 40 | {item[displayKey]} 41 |
42 | )} 43 |
44 | ))} 45 |
46 |
47 | {errors && ( 48 | {errors} 49 | )} 50 |
51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /resources/js/Components/Dashboard/LinkItem.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link, usePage } from '@inertiajs/react' 3 | export default function LinkItem({ href, icon, link, access, title, sidebarOpen, ...props }) { 4 | // destruct url from usepage 5 | const { url } = usePage(); 6 | 7 | // destruct auth from usepage props 8 | const { auth } = usePage().props; 9 | 10 | return ( 11 | <> 12 | { 13 | auth.super === true ? 14 | sidebarOpen ? 15 | 20 | {icon} {title} 21 | 22 | : 23 | 28 | {icon} 29 | 30 | : 31 | access === true && 32 | sidebarOpen ? 33 | 38 | {icon} {title} 39 | 40 | : 41 | 46 | {icon} 47 | 48 | } 49 | 50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /resources/js/Components/Dashboard/ListBox.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Listbox } from '@headlessui/react' 3 | import { IconChevronDown, IconCircle, IconCircleFilled } from '@tabler/icons-react' 4 | export default function ListBox({ selected, data, setSelected, label, errors }) { 5 | 6 | const preview = selected.length ? 7 | selected.length >= 4 ? `jumlah hak akses terpilih ${selected.length}` : 8 | selected.map((item) => item.name).join(', ') 9 | : 10 | 'Pilh Hak Akses' 11 | 12 | return ( 13 |
14 | 15 | 16 | 17 | {preview} 18 | 19 | 20 | 21 | {data.map((item) => ( 22 | 23 | {({ selected }) => ( 24 |
26 | {selected ? : } 27 | {item.name} 28 |
29 | )} 30 |
31 | ))} 32 |
33 |
34 | {errors && ( 35 | {errors} 36 | )} 37 |
38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /resources/js/Components/Dashboard/Modal.jsx: -------------------------------------------------------------------------------- 1 | import { Fragment } from 'react'; 2 | import { Dialog, Transition } from '@headlessui/react'; 3 | 4 | export default function Modal({ children, title, show = false, maxWidth = '2xl', closeable = true, onClose = () => { } }) { 5 | const close = () => { 6 | if (closeable) { 7 | onClose(); 8 | } 9 | }; 10 | 11 | const maxWidthClass = { 12 | sm: 'sm:max-w-sm', 13 | md: 'sm:max-w-md', 14 | lg: 'sm:max-w-lg', 15 | xl: 'sm:max-w-xl', 16 | '2xl': 'sm:max-w-2xl', 17 | }[maxWidth]; 18 | 19 | return ( 20 | 21 | 27 | 36 |
37 | 38 | 39 | 48 | 51 | 52 | {title} 53 | 54 |
55 | {children} 56 |
57 |
58 |
59 |
60 |
61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /resources/js/Components/Dashboard/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { usePage } from '@inertiajs/react'; 3 | import { IconAlignLeft, IconMoon, IconSun } from '@tabler/icons-react' 4 | import AuthDropdown from '@/Components/Dashboard/AuthDropdown'; 5 | import Menu from '@/Utils/Menu'; 6 | import Notification from '@/Components/Dashboard/Notification'; 7 | 8 | export default function Navbar({ toggleSidebar, themeSwitcher, darkMode }) { 9 | // destruct auth from props 10 | const { auth } = usePage().props; 11 | 12 | // get menu from utils 13 | const menuNavigation = Menu(); 14 | 15 | // recreate array from menu navigations 16 | const links = menuNavigation.flatMap((item) => item.details); 17 | const filter_sublinks = links.filter((item) => item.hasOwnProperty('subdetails')); 18 | const sublinks = filter_sublinks.flatMap((item) => item.subdetails); 19 | 20 | // define state isMobile 21 | const [isMobile, setIsMobile] = useState(false); 22 | 23 | // define useEffect 24 | useEffect(() => { 25 | // define handle resize window 26 | const handleResize = () => { 27 | setIsMobile(window.innerWidth <= 768); 28 | }; 29 | 30 | // define event listener 31 | window.addEventListener('resize', handleResize); 32 | 33 | // call handle resize window 34 | handleResize(); 35 | 36 | // remove event listener 37 | return () => { 38 | window.removeEventListener('resize', handleResize); 39 | }; 40 | }) 41 | 42 | return ( 43 |
44 |
45 | 48 |
49 | {/* {links.map((link, i) => ( 50 | link.hasOwnProperty('subdetails') ? 51 | sublinks.map((sublink, x) => sublink.active === true && {sublink.title}) 52 | : 53 | link.active === true && {link.title} 54 | ))} */} 55 | {links.map((link, i) => ( 56 | link.hasOwnProperty('subdetails') ? 57 | sublinks.map((sublink, x) => sublink.active === true && {sublink.title}) 58 | : 59 | link.active === true && {link.title} 60 | ))} 61 |
62 |
63 |
64 |
65 |
66 | 69 | 70 |
71 |
72 | 73 |
74 |
75 | ) 76 | } 77 | -------------------------------------------------------------------------------- /resources/js/Components/Dashboard/Pagination.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from '@inertiajs/react'; 3 | import { IconChevronRight, IconChevronLeft } from '@tabler/icons-react'; 4 | export default function Pagination({ links }) { 5 | 6 | const style = 'p-1 text-sm border rounded-md bg-white text-gray-500 hover:bg-gray-100 dark:bg-gray-950 dark:text-gray-400 dark:hover:bg-gray-900 dark:border-gray-900' 7 | 8 | return ( 9 | <> 10 |
    11 | {links.map((item, i) => { 12 | return item.url != null ? ( 13 | item.label.includes('Previous') ? ( 14 | 15 | 16 | 17 | ) : item.label.includes('Next') ? ( 18 | 19 | 20 | 21 | ) : ( 22 | 23 | {item.label} 24 | 25 | ) 26 | ) : null; 27 | })} 28 |
29 | 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /resources/js/Components/Dashboard/Search.jsx: -------------------------------------------------------------------------------- 1 | import { useForm } from '@inertiajs/react'; 2 | import { IconSearch } from '@tabler/icons-react'; 3 | import React from 'react' 4 | export default function Search({ url, placeholder }) { 5 | 6 | // define use form inertia 7 | const { data, setData, get } = useForm({ 8 | search: '', 9 | }) 10 | 11 | // define method searchData 12 | const searchData = (e) => { 13 | e.preventDefault(); 14 | 15 | get(`${url}?search=${data.search}`) 16 | } 17 | 18 | return ( 19 |
20 |
21 | setData('search', e.target.value)} 25 | className='py-2 px-4 pr-11 block w-full rounded-lg text-sm border focus:outline-none focus:ring-0 focus:ring-gray-400 text-gray-700 bg-white border-gray-200 focus:border-gray-200 dark:focus:ring-gray-500 dark:focus:border-gray-800 dark:text-gray-200 dark:bg-gray-950 dark:border-gray-900' 26 | placeholder={placeholder} /> 27 |
28 | 29 |
30 |
31 |
32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /resources/js/Components/Dashboard/Table.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Card = ({ icon, title, className, children }) => { 4 | return ( 5 | <> 6 |
7 |
8 | {title} 9 |
10 |
11 |
12 | {children} 13 |
14 | 15 | 16 | ) 17 | } 18 | 19 | const Table = ({ children }) => { 20 | return ( 21 |
22 | 23 | {children} 24 |
25 |
26 | ); 27 | }; 28 | 29 | const Thead = ({ className, children }) => { 30 | return ( 31 | {children} 32 | ); 33 | }; 34 | 35 | const Tbody = ({ className, children }) => { 36 | return ( 37 | 38 | {children} 39 | 40 | ); 41 | }; 42 | 43 | const Td = ({ className, children }) => { 44 | return ( 45 | 48 | {children} 49 | 50 | ); 51 | }; 52 | 53 | const Th = ({ className, children }) => { 54 | return ( 55 | 59 | {children} 60 | 61 | ); 62 | }; 63 | 64 | const Empty = ({ colSpan, message, children }) => { 65 | return ( 66 | 67 | 68 |
69 |
70 | {children} 71 |
72 | {message} 73 |
74 |
75 |
76 | 77 | 78 | ) 79 | } 80 | 81 | Table.Card = Card; 82 | Table.Thead = Thead; 83 | Table.Tbody = Tbody; 84 | Table.Td = Td; 85 | Table.Th = Th; 86 | Table.Empty = Empty; 87 | 88 | export default Table; 89 | -------------------------------------------------------------------------------- /resources/js/Components/Dashboard/TextArea.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function Textarea({ label, type, className, errors, ...props }) { 4 | return ( 5 |
6 | 7 |