├── database ├── .gitignore ├── seeders │ ├── ProductSeeder.php │ ├── PurchaseSeeder.php │ ├── SaleSeeder.php │ ├── ExpenseSeeder.php │ ├── ExpenseTypeSeeder.php │ ├── DatabaseSeeder.php │ └── UserSeeder.php ├── factories │ ├── ExpenseTypeFactory.php │ ├── ExpenseFactory.php │ ├── SaleFactory.php │ ├── PurchaseFactory.php │ ├── ProductFactory.php │ └── UserFactory.php └── migrations │ ├── 2024_11_23_073409_create_expense_types_table.php │ ├── 2024_09_01_214923_create_language_lines_table.php │ ├── 2024_11_24_073133_create_expenses_table.php │ ├── 2024_10_22_073133_create_products_table.php │ ├── 0001_01_01_000001_create_cache_table.php │ ├── 2024_10_22_073133_create_sales_table.php │ ├── 2024_10_22_073133_create_purchases_table.php │ ├── 2024_11_26_092534_create_transaction_returns_table.php │ ├── 0001_01_01_000002_create_jobs_table.php │ └── 0001_01_01_000000_create_users_table.php ├── bootstrap ├── cache │ └── .gitignore ├── providers.php └── app.php ├── storage ├── logs │ └── .gitignore ├── app │ ├── public │ │ └── .gitignore │ └── .gitignore └── framework │ ├── testing │ └── .gitignore │ ├── views │ └── .gitignore │ ├── cache │ ├── data │ │ └── .gitignore │ └── .gitignore │ ├── sessions │ └── .gitignore │ └── .gitignore ├── public ├── robots.txt ├── favicon.ico ├── index.php ├── .htaccess └── favicon.svg ├── resources ├── js │ ├── types │ │ ├── vite-env.d.ts │ │ └── global.d.ts │ ├── bootstrap.ts │ ├── lib │ │ └── utils.ts │ ├── utils │ │ ├── cn.ts │ │ ├── trans.ts │ │ └── helpers.ts │ ├── components │ │ ├── input-error.tsx │ │ ├── ui │ │ │ ├── collapsible.tsx │ │ │ ├── input-error.tsx │ │ │ ├── Input-label.tsx │ │ │ ├── danger-button.tsx │ │ │ ├── nav-link.tsx │ │ │ ├── primary-button.tsx │ │ │ ├── label.tsx │ │ │ ├── secondary-button.tsx │ │ │ ├── responsive-nav-link.tsx │ │ │ ├── toaster.tsx │ │ │ ├── progress.tsx │ │ │ ├── text-input.tsx │ │ │ ├── separator.tsx │ │ │ ├── sonner.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── textarea.tsx │ │ │ ├── badge.tsx │ │ │ ├── switch.tsx │ │ │ ├── popover.tsx │ │ │ ├── tooltip.tsx │ │ │ ├── avatar.tsx │ │ │ ├── alert.tsx │ │ │ ├── button.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── card.tsx │ │ │ ├── tabs.tsx │ │ │ ├── accordion.tsx │ │ │ ├── password-input.tsx │ │ │ ├── input.tsx │ │ │ └── modal.tsx │ │ ├── input-label.tsx │ │ ├── no-data.tsx │ │ ├── danger-button.tsx │ │ ├── nav-link.tsx │ │ ├── primary-button.tsx │ │ ├── secondary-button.tsx │ │ ├── responsive-nav-link.tsx │ │ ├── text_input.tsx │ │ ├── gender-select.tsx │ │ ├── mode-toggle.tsx │ │ ├── modal.tsx │ │ └── language-toggle.tsx │ ├── Pages │ │ ├── Auth │ │ │ ├── ConfirmPassword.tsx │ │ │ ├── Register.tsx │ │ │ ├── ResetPassword.tsx │ │ │ ├── ForgotPassword.tsx │ │ │ ├── VerifyEmail.tsx │ │ │ └── Login.tsx │ │ ├── Admin │ │ │ ├── Dashboard.tsx │ │ │ ├── Account │ │ │ │ └── Profile.tsx │ │ │ └── Users │ │ │ │ └── Index.tsx │ │ ├── Expenses │ │ │ └── Edit.tsx │ │ ├── Sales │ │ │ └── Edit.tsx │ │ ├── Dashboard.tsx │ │ └── Profile │ │ │ └── Edit.tsx │ ├── layouts │ │ ├── auth-layout.tsx │ │ └── admin-layout.tsx │ ├── hooks │ │ ├── use-store.ts │ │ ├── use-media-query.ts │ │ ├── use-sidebar-togle.ts │ │ └── use-dir.ts │ ├── Layouts │ │ └── GuestLayout.tsx │ ├── providers │ │ ├── language-provider.tsx │ │ └── theme-provider.tsx │ ├── features │ │ ├── admin │ │ │ ├── components │ │ │ │ ├── admin-table.tsx │ │ │ │ ├── navbar-actions.tsx │ │ │ │ ├── sidebar-toggle.tsx │ │ │ │ ├── sheet-menu.tsx │ │ │ │ ├── navbar.tsx │ │ │ │ └── sidebar.tsx │ │ │ └── users │ │ │ │ ├── users-filters.ts │ │ │ │ └── components │ │ │ │ └── users-table-columns.tsx │ │ └── auth │ │ │ ├── verify-email-form.tsx │ │ │ ├── forgot-password-form.tsx │ │ │ └── confirm-password-form.tsx │ ├── ssr.tsx │ └── app.tsx ├── views │ └── app.blade.php └── css │ └── app.css ├── postcss.config.js ├── app ├── Enums │ ├── Gender.php │ ├── PurchaseStatus.php │ └── Provider.php ├── Http │ ├── Controllers │ │ ├── Controller.php │ │ ├── Admin │ │ │ ├── DashboardController.php │ │ │ ├── ProfileController.php │ │ │ └── UserController.php │ │ ├── Auth │ │ │ ├── EmailVerificationPromptController.php │ │ │ ├── EmailVerificationNotificationController.php │ │ │ ├── PasswordController.php │ │ │ ├── VerifyEmailController.php │ │ │ ├── ConfirmablePasswordController.php │ │ │ ├── AuthenticatedSessionController.php │ │ │ ├── PasswordResetLinkController.php │ │ │ ├── RegisteredUserController.php │ │ │ ├── GoogleAuthController.php │ │ │ └── NewPasswordController.php │ │ └── ProfileController.php │ ├── Requests │ │ ├── Admin │ │ │ └── User │ │ │ │ ├── UserExportRequest.php │ │ │ │ └── UserRequest.php │ │ ├── ProfileUpdateRequest.php │ │ └── Auth │ │ │ └── LoginRequest.php │ └── Middleware │ │ ├── Localization.php │ │ └── HandleInertiaRequests.php ├── Models │ ├── Role.php │ ├── Permission.php │ ├── ExpenseType.php │ ├── Product.php │ ├── Expense.php │ ├── Sale.php │ ├── Purchase.php │ ├── LanguageLine.php │ └── TransactionReturn.php ├── Exports │ └── UsersExport.php ├── Providers │ ├── NativeAppServiceProvider.php │ └── AppServiceProvider.php └── Services │ └── Admin │ └── UserService.php ├── tests ├── TestCase.php ├── Unit │ └── ExampleTest.php └── Feature │ ├── ExampleTest.php │ ├── Auth │ ├── RegistrationTest.php │ ├── PasswordConfirmationTest.php │ ├── AuthenticationTest.php │ ├── PasswordUpdateTest.php │ ├── EmailVerificationTest.php │ └── PasswordResetTest.php │ └── ProfileTest.php ├── .gitattributes ├── routes ├── console.php ├── admin.php └── auth.php ├── .editorconfig ├── _ide_helper_models.php ├── .gitignore ├── vite.config.js ├── artisan ├── components.json ├── tsconfig.json ├── config ├── laratrust_seeder.php ├── translation-loader.php ├── services.php └── filesystems.php ├── phpunit.xml ├── .env.example └── package.json /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /resources/js/types/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HassanDev13/basit/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /bootstrap/providers.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 8 | })->purpose('Display an inspiring quote')->hourly(); 9 | -------------------------------------------------------------------------------- /resources/js/types/global.d.ts: -------------------------------------------------------------------------------- 1 | import { AxiosInstance } from 'axios'; 2 | import { route as ziggyRoute } from 'ziggy-js'; 3 | 4 | declare global { 5 | interface Window { 6 | axios: AxiosInstance; 7 | } 8 | 9 | var route: typeof ziggyRoute; 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/Enums/Provider.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /_ide_helper_models.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/Exports/UsersExport.php: -------------------------------------------------------------------------------- 1 | & { message?: string }) { 4 | return message ? ( 5 |

6 | {message} 7 |

8 | ) : null; 9 | } 10 | -------------------------------------------------------------------------------- /resources/js/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"; 2 | 3 | const Collapsible = CollapsiblePrimitive.Root; 4 | 5 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger; 6 | 7 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent; 8 | 9 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }; 10 | -------------------------------------------------------------------------------- /resources/js/components/ui/input-error.tsx: -------------------------------------------------------------------------------- 1 | import { HTMLAttributes } from 'react'; 2 | 3 | export default function InputError({ message, className = '', ...props }: HTMLAttributes & { message?: string }) { 4 | return message ? ( 5 |

6 | {message} 7 |

8 | ) : null; 9 | } 10 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/DashboardController.php: -------------------------------------------------------------------------------- 1 | hasMany(Expense::class); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | handleCommand(new ArgvInput); 14 | 15 | exit($status); 16 | -------------------------------------------------------------------------------- /resources/js/Pages/Auth/ConfirmPassword.tsx: -------------------------------------------------------------------------------- 1 | import AuthLayout from "@/layouts/auth-layout"; 2 | import { Head, useForm } from "@inertiajs/react"; 3 | import { FormEventHandler } from "react"; 4 | 5 | export default function ConfirmPassword() { 6 | return ( 7 | 8 | 9 | 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /resources/js/layouts/auth-layout.tsx: -------------------------------------------------------------------------------- 1 | import ApplicationLogo from "@/components/application-logo"; 2 | import { Link } from "@inertiajs/react"; 3 | import { PropsWithChildren } from "react"; 4 | 5 | export default function Guest({ children }: PropsWithChildren) { 6 | return ( 7 |
8 | {children} 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /resources/js/components/input-label.tsx: -------------------------------------------------------------------------------- 1 | import { LabelHTMLAttributes } from 'react'; 2 | 3 | export default function InputLabel({ value, className = '', children, ...props }: LabelHTMLAttributes & { value?: string }) { 4 | return ( 5 | 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /resources/js/layouts/admin-layout.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React, { PropsWithChildren } from "react"; 3 | type AdminLayoutProps = { 4 | name: string; 5 | } & PropsWithChildren; 6 | function AdminLayout({ children, name }: AdminLayoutProps) { 7 | return ( 8 |
9 | {children} 10 | 11 |
12 | ); 13 | } 14 | 15 | export default AdminLayout; 16 | -------------------------------------------------------------------------------- /resources/js/Pages/Admin/Dashboard.tsx: -------------------------------------------------------------------------------- 1 | import AdminLayout from "@/layouts/admin-layout"; 2 | import { t } from "@/utils/trans"; 3 | import React from "react"; 4 | 5 | function Dashboard({ translations }: any) { 6 | console.log(t("no")); 7 | return
Dashboard
; 8 | } 9 | 10 | Dashboard.layout = (page: React.ReactNode) => ( 11 | 12 | ); 13 | export default Dashboard; 14 | -------------------------------------------------------------------------------- /resources/js/Pages/Expenses/Edit.tsx: -------------------------------------------------------------------------------- 1 | 2 | import Guest from "@/layouts/auth-layout"; 3 | import { Head } from "@inertiajs/react"; 4 | 5 | export default function Edit() { 6 | return ( 7 | 8 | 9 |
10 | 11 |
12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /resources/js/Pages/Sales/Edit.tsx: -------------------------------------------------------------------------------- 1 | 2 | import Guest from "@/layouts/auth-layout"; 3 | import { Head } from "@inertiajs/react"; 4 | 5 | export default function Edit() { 6 | return ( 7 | 8 | 9 |
10 | 11 |
12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /resources/js/components/ui/Input-label.tsx: -------------------------------------------------------------------------------- 1 | import { LabelHTMLAttributes } from 'react'; 2 | 3 | export default function InputLabel({ value, className = '', children, ...props }: LabelHTMLAttributes & { value?: string }) { 4 | return ( 5 | 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /resources/js/hooks/use-store.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | 3 | export const useStore = ( 4 | store: (callback: (state: T) => unknown) => unknown, 5 | callback: (state: T) => F 6 | ) => { 7 | const result = store(callback) as F; 8 | const [data, setData] = useState(); 9 | 10 | useEffect(() => { 11 | setData(result); 12 | }, [result]); 13 | 14 | return data; 15 | }; 16 | -------------------------------------------------------------------------------- /database/seeders/ProductSeeder.php: -------------------------------------------------------------------------------- 1 | count(10)->create(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /database/seeders/PurchaseSeeder.php: -------------------------------------------------------------------------------- 1 | count(10)->create(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /database/seeders/SaleSeeder.php: -------------------------------------------------------------------------------- 1 | count(20) 18 | ->create(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Feature/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 16 | 17 | $response->assertStatus(200); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "resources/css/app.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/utils/cn" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /database/seeders/ExpenseSeeder.php: -------------------------------------------------------------------------------- 1 | count(25) 18 | ->create(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /resources/js/components/no-data.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from "@/providers/theme-provider"; 2 | import { Database } from "lucide-react"; 3 | import { SVGAttributes } from "react"; 4 | 5 | export default function NoData(props: SVGAttributes) { 6 | return ( 7 |
8 | 9 |

لا توجد بيانات

10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /app/Http/Requests/Admin/User/UserExportRequest.php: -------------------------------------------------------------------------------- 1 | [Rule::in(['xlsx', 'pdf'])], 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | handleRequest(Request::capture()); 18 | -------------------------------------------------------------------------------- /resources/js/Pages/Auth/Register.tsx: -------------------------------------------------------------------------------- 1 | import RegisterForm from "@/features/auth/register-form"; 2 | import AuthLayout from "@/layouts/auth-layout"; 3 | import { Head } from "@inertiajs/react"; 4 | 5 | export default function Register() { 6 | return ( 7 | 8 | 9 |
10 | 11 | 12 |
13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /resources/js/hooks/use-media-query.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export function useMediaQuery(query: string) { 4 | const [value, setValue] = React.useState(false); 5 | 6 | React.useEffect(() => { 7 | function onChange(event: MediaQueryListEvent) { 8 | setValue(event.matches); 9 | } 10 | 11 | const result = matchMedia(query); 12 | result.addEventListener("change", onChange); 13 | setValue(result.matches); 14 | 15 | return () => result.removeEventListener("change", onChange); 16 | }, [query]); 17 | 18 | return value; 19 | } 20 | -------------------------------------------------------------------------------- /resources/js/hooks/use-sidebar-togle.ts: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | import { persist, createJSONStorage } from "zustand/middleware"; 3 | 4 | interface useSidebarToggleStore { 5 | isOpen: boolean; 6 | setIsOpen: () => void; 7 | } 8 | 9 | export const useSidebarToggle = create( 10 | persist( 11 | (set, get) => ({ 12 | isOpen: true, 13 | setIsOpen: () => { 14 | set({ isOpen: !get().isOpen }); 15 | }, 16 | }), 17 | { 18 | name: "sidebarOpen", 19 | storage: createJSONStorage(() => localStorage), 20 | } 21 | ) 22 | ); 23 | -------------------------------------------------------------------------------- /resources/js/Pages/Auth/ResetPassword.tsx: -------------------------------------------------------------------------------- 1 | import ResetPasswordForm from "@/features/auth/reset-password-form"; 2 | import AuthLayout from "@/layouts/auth-layout"; 3 | import { Head } from "@inertiajs/react"; 4 | 5 | export default function ResetPassword({ 6 | token, 7 | email, 8 | }: { 9 | token: string; 10 | email: string; 11 | }) { 12 | return ( 13 | 14 | 15 |
16 | 17 |
18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "jsx": "react-jsx", 8 | "strict": true, 9 | "isolatedModules": true, 10 | "target": "ESNext", 11 | "esModuleInterop": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "paths": { 15 | "@/*": ["./resources/js/*"], 16 | "ziggy-js": ["./vendor/tightenco/ziggy"] 17 | } 18 | }, 19 | "include": ["resources/js/**/*.ts", "resources/js/**/*.tsx", "resources/js/**/*.d.ts"] 20 | } 21 | -------------------------------------------------------------------------------- /app/Http/Middleware/Localization.php: -------------------------------------------------------------------------------- 1 | has('locale')) { 21 | App::setLocale(session('locale')); 22 | } 23 | 24 | return $next($request); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /resources/js/utils/trans.ts: -------------------------------------------------------------------------------- 1 | import { PageProps } from "@/types"; 2 | import { usePage } from "@inertiajs/react"; 3 | 4 | type PropsWithTranslations = { 5 | translations: Record; 6 | } & PageProps; 7 | export function t(key: string, replace: Record = {}): string { 8 | const { translations } = usePage().props; 9 | let translation = translations[key] ? translations[key] : key; 10 | 11 | Object.keys(replace).forEach(function (replaceKey) { 12 | translation = translation.replace( 13 | ":" + replaceKey, 14 | replace[replaceKey] 15 | ); 16 | }); 17 | 18 | return translation; 19 | } 20 | -------------------------------------------------------------------------------- /app/Providers/NativeAppServiceProvider.php: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /resources/js/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | export function formatBytes( 2 | bytes: number, 3 | opts: { 4 | decimals?: number; 5 | sizeType?: "accurate" | "normal"; 6 | } = {} 7 | ) { 8 | const { decimals = 0, sizeType = "normal" } = opts; 9 | 10 | const sizes = ["Bytes", "KB", "MB", "GB", "TB"]; 11 | const accurateSizes = ["Bytes", "KiB", "MiB", "GiB", "TiB"]; 12 | if (bytes === 0) return "0 Byte"; 13 | const i = Math.floor(Math.log(bytes) / Math.log(1024)); 14 | return `${(bytes / Math.pow(1024, i)).toFixed(decimals)} ${ 15 | sizeType === "accurate" 16 | ? accurateSizes[i] ?? "Bytest" 17 | : sizes[i] ?? "Bytes" 18 | }`; 19 | } 20 | -------------------------------------------------------------------------------- /resources/js/hooks/use-dir.ts: -------------------------------------------------------------------------------- 1 | //get dir based on local 2 | 3 | import { PageProps } from "@/types"; 4 | import { usePage } from "@inertiajs/react"; 5 | import { useEffect, useState } from "react"; 6 | const RTL_LANGUAGES = [ 7 | "ar", 8 | "fa", 9 | "he", 10 | "ur", 11 | "ps", 12 | "dv", 13 | "ku", 14 | "sd", 15 | "ug", 16 | "yi", 17 | ]; 18 | 19 | export const useDir = () => { 20 | const [direction, setDirection] = useState<"ltr" | "rtl">("ltr"); 21 | const { locale } = usePage().props; 22 | useEffect(() => { 23 | setDirection(RTL_LANGUAGES.includes(locale) ? "rtl" : "ltr"); 24 | }, [locale]); 25 | 26 | return direction; 27 | }; 28 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | hasRole('superadministrator'); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /database/factories/ExpenseTypeFactory.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class ExpenseTypeFactory extends Factory 12 | { 13 | protected $model = ExpenseType::class; 14 | 15 | public function definition() 16 | { 17 | return [ 18 | 'name' => $this->faker->word, // Random name for expense type 19 | ]; 20 | } 21 | 22 | public function withName(string $name) 23 | { 24 | return $this->state([ 25 | 'name' => $name, 26 | ]); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /resources/js/Pages/Admin/Account/Profile.tsx: -------------------------------------------------------------------------------- 1 | import InformationForm from "@/features/admin/profile/components/information-form"; 2 | import ProfileCard from "@/features/admin/profile/components/profile-card"; 3 | import SecurityForm from "@/features/admin/profile/components/security-form"; 4 | import AdminLayout from "@/layouts/admin-layout"; 5 | import React from "react"; 6 | 7 | function Profile() { 8 | return ( 9 |
10 | 11 | 12 | 13 |
14 | ); 15 | } 16 | Profile.layout = (page: React.ReactNode) => ( 17 | 18 | ); 19 | export default Profile; 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /database/seeders/ExpenseTypeSeeder.php: -------------------------------------------------------------------------------- 1 | create(['name' => 'إيجار']); // Rent 17 | ExpenseType::factory()->create(['name' => 'مرافق']); // Utilities 18 | ExpenseType::factory()->create(['name' => 'رواتب']); // Salaries 19 | ExpenseType::factory()->create(['name' => 'لوازم']); // Supplies 20 | ExpenseType::factory()->create(['name' => 'متنوع']); // Miscellaneous 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /resources/js/Pages/Auth/ForgotPassword.tsx: -------------------------------------------------------------------------------- 1 | import ForgotPasswordForm from "@/features/auth/forgot-password-form"; 2 | import AuthLayout from "@/layouts/auth-layout"; 3 | import { Head } from "@inertiajs/react"; 4 | 5 | export default function ForgotPassword({ status }: { status?: string }) { 6 | return ( 7 | 8 | 9 | 10 | {status && ( 11 |
12 | {status} 13 |
14 | )} 15 |
16 | 17 |
18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /database/factories/ExpenseFactory.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class ExpenseFactory extends Factory 13 | { 14 | protected $model = Expense::class; 15 | 16 | public function definition() 17 | { 18 | return [ 19 | 'expense_type_id' => ExpenseType::factory(), // Create a new expense type for each expense 20 | 'user_id' => 1, 21 | 'amount' => $this->faker->randomFloat(2, 1, 100), // Amount between 1 and 100 22 | 'expense_date' => $this->faker->date(), 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /database/migrations/2024_11_23_073409_create_expense_types_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name'); 17 | $table->timestamps(); 18 | $table->softDeletes(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | */ 25 | public function down(): void 26 | { 27 | Schema::dropIfExists('expense_types'); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /resources/js/Layouts/GuestLayout.tsx: -------------------------------------------------------------------------------- 1 | import ApplicationLogo from '@/components/application-logo'; 2 | import { Link } from '@inertiajs/react'; 3 | import { PropsWithChildren } from 'react'; 4 | 5 | export default function Guest({ children }: PropsWithChildren) { 6 | return ( 7 |
8 |
9 | 10 | 11 | 12 |
13 | 14 |
15 | {children} 16 |
17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /resources/js/providers/language-provider.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/utils/cn"; 2 | import React, { PropsWithChildren, useEffect, useRef } from "react"; 3 | 4 | type LanguageProviderProps = PropsWithChildren<{ locale: string }>; 5 | 6 | function LanguageProvider({ children, locale }: LanguageProviderProps) { 7 | const ref = useRef(null); 8 | 9 | useEffect(() => { 10 | if (ref.current) { 11 | ref.current.dir = locale === "ar" ? "rtl" : "ltr"; 12 | } 13 | }, [locale]); 14 | 15 | return ( 16 |
21 | {children} 22 |
23 | ); 24 | } 25 | 26 | export default LanguageProvider; 27 | -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | create(); 17 | $this->call([ 18 | LaratrustSeeder::class, 19 | UserSeeder::class, 20 | LanguageLinesSeeder::class, 21 | 22 | ExpenseTypeSeeder::class, 23 | // ProductSeeder::class, 24 | // SaleSeeder::class, 25 | // PurchaseSeeder::class, 26 | // ExpenseSeeder::class, 27 | ]); 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /config/laratrust_seeder.php: -------------------------------------------------------------------------------- 1 | false, 8 | 9 | /** 10 | * Control if all the laratrust tables should be truncated before running the seeder. 11 | */ 12 | 'truncate_tables' => true, 13 | 14 | 'roles_structure' => [ 15 | 'superadministrator' => [ 16 | 'users' => 'c,r,u,d', 17 | ], 18 | 'administrator' => [ 19 | 'users' => 'c,r,u,d', 20 | ], 21 | 'user' => [ 22 | 'profile' => 'r,u', 23 | ], 24 | 25 | ], 26 | 27 | 'permissions_map' => [ 28 | 'c' => 'create', 29 | 'r' => 'read', 30 | 'u' => 'update', 31 | 'd' => 'delete', 32 | ], 33 | ]; 34 | -------------------------------------------------------------------------------- /app/Services/Admin/UserService.php: -------------------------------------------------------------------------------- 1 | where('gender', $gender); 19 | } 20 | if (!empty($role) && $role !== 'all') { 21 | $queryBuilder->query(function (Builder $builder) use ($role) { 22 | $builder->whereHasRole($role); 23 | }); 24 | } 25 | return $queryBuilder->paginate($limit); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /resources/js/components/danger-button.tsx: -------------------------------------------------------------------------------- 1 | import { ButtonHTMLAttributes } from 'react'; 2 | 3 | export default function DangerButton({ className = '', disabled, children, ...props }: ButtonHTMLAttributes) { 4 | return ( 5 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /database/factories/SaleFactory.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class SaleFactory extends Factory 13 | { 14 | protected $model = Sale::class; 15 | 16 | public function definition() 17 | { 18 | return [ 19 | 'product_id' => Product::factory(), // Create a new product for each sale 20 | 'user_id' => 1, 21 | 'quantity' => $this->faker->numberBetween(1, 10), // Quantity between 1 and 10 22 | 'final_price' => $this->faker->randomFloat(2, 1, 100), // Final price 23 | 'sale_date' => $this->faker->date(), 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /resources/js/components/ui/danger-button.tsx: -------------------------------------------------------------------------------- 1 | import { ButtonHTMLAttributes } from 'react'; 2 | 3 | export default function DangerButton({ className = '', disabled, children, ...props }: ButtonHTMLAttributes) { 4 | return ( 5 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /config/translation-loader.php: -------------------------------------------------------------------------------- 1 | [ 11 | Spatie\TranslationLoader\TranslationLoaders\Db::class, 12 | ], 13 | 14 | /* 15 | * This is the model used by the Db Translation loader. You can put any model here 16 | * that extends Spatie\TranslationLoader\LanguageLine. 17 | */ 18 | 'model' => App\Models\LanguageLine::class, 19 | 20 | /* 21 | * This is the translation manager which overrides the default Laravel `translation.loader` 22 | */ 23 | 'translation_manager' => Spatie\TranslationLoader\TranslationLoaderManager::class, 24 | 25 | ]; 26 | -------------------------------------------------------------------------------- /database/factories/PurchaseFactory.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class PurchaseFactory extends Factory 13 | { 14 | protected $model = Purchase::class; 15 | 16 | public function definition() 17 | { 18 | return [ 19 | 'product_id' => Product::factory(), // Create a new product for each purchase 20 | 'user_id' => 1, 21 | 'quantity' => $this->faker->numberBetween(1, 10), // Quantity between 1 and 10 22 | 'cost' => $this->faker->randomFloat(2, 1, 50), // Cost 23 | 'purchase_date' => $this->faker->date(), 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /resources/js/components/nav-link.tsx: -------------------------------------------------------------------------------- 1 | import { Link, InertiaLinkProps } from '@inertiajs/react'; 2 | 3 | export default function NavLink({ active = false, className = '', children, ...props }: InertiaLinkProps & { active: boolean }) { 4 | return ( 5 | 15 | {children} 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /resources/js/components/primary-button.tsx: -------------------------------------------------------------------------------- 1 | import { ButtonHTMLAttributes } from 'react'; 2 | 3 | export default function PrimaryButton({ className = '', disabled, children, ...props }: ButtonHTMLAttributes) { 4 | return ( 5 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /resources/js/components/ui/nav-link.tsx: -------------------------------------------------------------------------------- 1 | import { Link, InertiaLinkProps } from '@inertiajs/react'; 2 | 3 | export default function NavLink({ active = false, className = '', children, ...props }: InertiaLinkProps & { active: boolean }) { 4 | return ( 5 | 15 | {children} 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /resources/js/components/ui/primary-button.tsx: -------------------------------------------------------------------------------- 1 | import { ButtonHTMLAttributes } from 'react'; 2 | 3 | export default function PrimaryButton({ className = '', disabled, children, ...props }: ButtonHTMLAttributes) { 4 | return ( 5 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /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 | \App\Http\Middleware\Localization::class, 17 | \Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class, 18 | ]); 19 | 20 | // 21 | }) 22 | ->withExceptions(function (Exceptions $exceptions) { 23 | // 24 | })->create(); 25 | -------------------------------------------------------------------------------- /resources/js/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as LabelPrimitive from "@radix-ui/react-label"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | 5 | import { cn } from "@/utils/cn"; 6 | 7 | const labelVariants = cva( 8 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 9 | ); 10 | 11 | const Label = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef & 14 | VariantProps 15 | >(({ className, ...props }, ref) => ( 16 | 21 | )); 22 | Label.displayName = LabelPrimitive.Root.displayName; 23 | 24 | export { Label }; 25 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /database/factories/ProductFactory.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class ProductFactory extends Factory 12 | { 13 | protected $model = Product::class; 14 | /** 15 | * Define the model's default state. 16 | * 17 | * @return array 18 | */ 19 | public function definition(): array 20 | { 21 | return [ 22 | 'name' => $this->faker->word, 23 | 'user_id' => 1, 24 | 'price' => $this->faker->randomFloat(2, 1, 100), // Price between 1 and 100 25 | 'cost' => $this->faker->randomFloat(2, 1, 50), 26 | 'quantity' => $this->faker->numberBetween(1, 100), 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /resources/js/components/secondary-button.tsx: -------------------------------------------------------------------------------- 1 | import { ButtonHTMLAttributes } from 'react'; 2 | 3 | export default function SecondaryButton({ type = 'button', className = '', disabled, children, ...props }: ButtonHTMLAttributes) { 4 | return ( 5 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /resources/js/components/ui/secondary-button.tsx: -------------------------------------------------------------------------------- 1 | import { ButtonHTMLAttributes } from 'react'; 2 | 3 | export default function SecondaryButton({ type = 'button', className = '', disabled, children, ...props }: ButtonHTMLAttributes) { 4 | return ( 5 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /resources/js/Pages/Auth/VerifyEmail.tsx: -------------------------------------------------------------------------------- 1 | import VerifyEmailForm from "@/features/auth/verify-email-form"; 2 | import AuthLayout from "@/layouts/auth-layout"; 3 | import { Head } from "@inertiajs/react"; 4 | 5 | export default function VerifyEmail({ status }: { status?: string }) { 6 | return ( 7 | 8 | 9 | 10 | {status === "verification-link-sent" && ( 11 |
12 | A new verification link has been sent to the email address 13 | you provided during registration. 14 |
15 | )} 16 |
17 | 18 |
19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /tests/Feature/Auth/RegistrationTest.php: -------------------------------------------------------------------------------- 1 | get('/register'); 15 | 16 | $response->assertStatus(200); 17 | } 18 | 19 | public function test_new_users_can_register(): void 20 | { 21 | $response = $this->post('/register', [ 22 | 'name' => 'Test User', 23 | 'email' => 'test@example.com', 24 | 'password' => 'password', 25 | 'password_confirmation' => 'password', 26 | ]); 27 | 28 | $this->assertAuthenticated(); 29 | $response->assertRedirect(route('dashboard', absolute: false)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /resources/js/Pages/Dashboard.tsx: -------------------------------------------------------------------------------- 1 | import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'; 2 | import { Head } from '@inertiajs/react'; 3 | import { PageProps } from '@/types'; 4 | 5 | export default function Dashboard({ auth }: PageProps) { 6 | return ( 7 | Dashboard} 10 | > 11 | 12 | 13 |
14 |
15 |
16 |
You're logged in!
17 |
18 |
19 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /app/Http/Requests/Admin/User/UserRequest.php: -------------------------------------------------------------------------------- 1 | toArray(); 20 | $availableGenders = array_merge(['all'], array_column(Gender::cases(), 'value')); 21 | $availableRoles = array_merge(['all'], $availableRoles); 22 | return [ 23 | 'role' => [Rule::in($availableRoles)], 24 | 'gender' => [Rule::in($availableGenders)], 25 | 'search' => ['string', 'max:255'], 26 | 'limit' => ['integer', 'min:1'], 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /resources/js/components/responsive-nav-link.tsx: -------------------------------------------------------------------------------- 1 | import { Link, InertiaLinkProps } from '@inertiajs/react'; 2 | 3 | export default function ResponsiveNavLink({ active = false, className = '', children, ...props }: InertiaLinkProps & { active?: boolean }) { 4 | return ( 5 | 13 | {children} 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /resources/js/components/ui/responsive-nav-link.tsx: -------------------------------------------------------------------------------- 1 | import { Link, InertiaLinkProps } from '@inertiajs/react'; 2 | 3 | export default function ResponsiveNavLink({ active = false, className = '', children, ...props }: InertiaLinkProps & { active?: boolean }) { 4 | return ( 5 | 13 | {children} 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /resources/js/features/admin/components/admin-table.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Card, 3 | CardContent, 4 | CardFooter, 5 | CardHeader, 6 | } from "@/components/ui/card"; 7 | import { ReactNode } from "react"; 8 | 9 | type AdminTableProps = { 10 | children: ReactNode; 11 | }; 12 | 13 | const AdminTable = ({ children }: AdminTableProps) => { 14 | return {children}; 15 | }; 16 | 17 | AdminTable.Header = ({ children }: { children: ReactNode }) => ( 18 | {children} 19 | ); 20 | 21 | AdminTable.Content = ({ children }: { children: ReactNode }) => ( 22 | {children} 23 | ); 24 | 25 | AdminTable.Footer = ({ children }: { children: ReactNode }) => ( 26 | {children} 27 | ); 28 | 29 | export default AdminTable; 30 | -------------------------------------------------------------------------------- /database/migrations/2024_09_01_214923_create_language_lines_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->string('group')->index(); 19 | $table->string('key'); 20 | $table->json('text'); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('language_lines'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /resources/js/components/ui/toaster.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { 4 | Toast, 5 | ToastClose, 6 | ToastDescription, 7 | ToastProvider, 8 | ToastTitle, 9 | ToastViewport, 10 | } from "@/components/ui/toast" 11 | import { useToast } from "@/hooks/use-toast" 12 | 13 | export function Toaster() { 14 | const { toasts } = useToast() 15 | 16 | return ( 17 | 18 | {toasts.map(function ({ id, title, description, action, ...props }) { 19 | return ( 20 | 21 |
22 | {title && {title}} 23 | {description && ( 24 | {description} 25 | )} 26 |
27 | {action} 28 | 29 |
30 | ) 31 | })} 32 | 33 |
34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /resources/js/components/ui/progress.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ProgressPrimitive from "@radix-ui/react-progress"; 3 | 4 | import { cn } from "@/utils/cn"; 5 | 6 | const Progress = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >(({ className, value, ...props }, ref) => ( 10 | 18 | 22 | 23 | )); 24 | Progress.displayName = ProgressPrimitive.Root.displayName; 25 | 26 | export { Progress }; 27 | -------------------------------------------------------------------------------- /resources/js/features/admin/users/users-filters.ts: -------------------------------------------------------------------------------- 1 | import { Filter } from "@/types"; 2 | 3 | export const FILTERS: Filter[] = [ 4 | { 5 | name: "gender", 6 | options: [ 7 | { 8 | label: "All", 9 | value: "all", 10 | }, 11 | { 12 | label: "Male", 13 | value: "male", 14 | }, 15 | { 16 | label: "Female", 17 | value: "female", 18 | }, 19 | ], 20 | }, 21 | { 22 | name: "role", 23 | options: [ 24 | { 25 | label: "All", 26 | value: "all", 27 | }, 28 | { 29 | label: "Admin", 30 | value: "administrator", 31 | }, 32 | { 33 | label: "Super Admin", 34 | value: "superadministrator", 35 | }, 36 | ], 37 | }, 38 | ]; 39 | -------------------------------------------------------------------------------- /resources/js/components/text_input.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef, useEffect, useImperativeHandle, useRef, InputHTMLAttributes } from 'react'; 2 | 3 | export default forwardRef(function TextInput( 4 | { type = 'text', className = '', isFocused = false, ...props }: InputHTMLAttributes & { isFocused?: boolean }, 5 | ref 6 | ) { 7 | const localRef = useRef(null); 8 | 9 | useImperativeHandle(ref, () => ({ 10 | focus: () => localRef.current?.focus(), 11 | })); 12 | 13 | useEffect(() => { 14 | if (isFocused) { 15 | localRef.current?.focus(); 16 | } 17 | }, []); 18 | 19 | return ( 20 | 29 | ); 30 | }); 31 | -------------------------------------------------------------------------------- /resources/js/components/ui/text-input.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef, useEffect, useImperativeHandle, useRef, InputHTMLAttributes } from 'react'; 2 | 3 | export default forwardRef(function TextInput( 4 | { type = 'text', className = '', isFocused = false, ...props }: InputHTMLAttributes & { isFocused?: boolean }, 5 | ref 6 | ) { 7 | const localRef = useRef(null); 8 | 9 | useImperativeHandle(ref, () => ({ 10 | focus: () => localRef.current?.focus(), 11 | })); 12 | 13 | useEffect(() => { 14 | if (isFocused) { 15 | localRef.current?.focus(); 16 | } 17 | }, []); 18 | 19 | return ( 20 | 29 | ); 30 | }); 31 | -------------------------------------------------------------------------------- /app/Models/Product.php: -------------------------------------------------------------------------------- 1 | hasMany(Sale::class); 18 | } 19 | 20 | public function purchases() 21 | { 22 | return $this->hasMany(Purchase::class); 23 | } 24 | 25 | public function user() 26 | { 27 | return $this->belongsTo(User::class); 28 | } 29 | 30 | public function scopeFilter($query, $filters) 31 | { 32 | if ($filters['search'] ?? false) { 33 | $query->where('id', 'like', '%' . $filters['search'] . '%') 34 | ->orWhere('name', 'like', '%' . $filters['search'] . '%'); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /database/migrations/2024_11_24_073133_create_expenses_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('user_id')->constrained()->onDelete('cascade'); // Foreign key to users table 17 | $table->foreignId('expense_type_id')->constrained('expense_types'); 18 | $table->decimal('amount', 8, 2); 19 | $table->date('expense_date'); 20 | $table->timestamps(); 21 | $table->softDeletes(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('expenses'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /database/migrations/2024_10_22_073133_create_products_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('user_id')->constrained()->onDelete('cascade'); // Foreign key to users table 17 | $table->string('name'); 18 | $table->decimal('price', 8, 2); 19 | $table->decimal('cost', 8, 2); 20 | $table->integer('quantity'); 21 | $table->timestamps(); 22 | $table->softDeletes(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('products'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /resources/js/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as SeparatorPrimitive from "@radix-ui/react-separator"; 3 | 4 | import { cn } from "@/utils/cn"; 5 | 6 | const Separator = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >( 10 | ( 11 | { className, orientation = "horizontal", decorative = true, ...props }, 12 | ref 13 | ) => ( 14 | 27 | ) 28 | ); 29 | Separator.displayName = SeparatorPrimitive.Root.displayName; 30 | 31 | export { Separator }; 32 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/Http/Requests/ProfileUpdateRequest.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | public function rules(): array 17 | { 18 | return [ 19 | 'username' => ['required', 'string', 'max:255'], 20 | 'first_name' => ['required', 'string', 'max:255'], 21 | 'last_name' => ['required', 'string', 'max:255'], 22 | 'phone' => ['required', 'string', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)], 23 | 'gender' => ['required', 'string', 'max:255'], 24 | 'bio' => ['nullable', 'string', 'max:255'], 25 | 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)], 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /resources/js/components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from "next-themes"; 2 | import { Toaster as Sonner } from "sonner"; 3 | 4 | type ToasterProps = React.ComponentProps; 5 | 6 | const Toaster = ({ ...props }: ToasterProps) => { 7 | const { theme = "system" } = useTheme(); 8 | 9 | return ( 10 | 25 | ); 26 | }; 27 | 28 | export { Toaster }; 29 | -------------------------------------------------------------------------------- /database/migrations/2024_10_22_073133_create_sales_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('user_id')->constrained()->onDelete('cascade'); // Foreign key to users table 17 | $table->foreignId('product_id')->constrained('products'); 18 | $table->integer('quantity'); 19 | $table->decimal('final_price', 8, 2); 20 | $table->enum('status', ['approved', 'canceled'])->default('approved'); 21 | $table->date('sale_date'); 22 | $table->timestamps(); 23 | $table->softDeletes(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('sales'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /database/migrations/2024_10_22_073133_create_purchases_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('user_id')->constrained()->onDelete('cascade'); // Foreign key to users table 17 | 18 | $table->foreignId('product_id')->constrained('products'); 19 | $table->integer('quantity'); 20 | $table->decimal('cost', 8, 2); 21 | $table->date('purchase_date'); 22 | $table->enum('status', ['approved', 'canceled'])->default('approved'); 23 | $table->timestamps(); 24 | $table->softDeletes(); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | */ 31 | public function down(): void 32 | { 33 | Schema::dropIfExists('purchases'); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /resources/js/features/admin/components/navbar-actions.tsx: -------------------------------------------------------------------------------- 1 | import LanguageToggle from "@/components/language-toggle"; 2 | import { ModeToggle } from "@/components/mode-toggle"; 3 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; 4 | import { Button } from "@/components/ui/button"; 5 | import { t } from "@/utils/trans"; 6 | import { Search } from "lucide-react"; 7 | 8 | function NavbarActions() { 9 | return ( 10 |
11 | 18 | 19 | 20 | 21 | 22 | CN 23 | 24 |
25 | ); 26 | } 27 | 28 | export default NavbarActions; 29 | -------------------------------------------------------------------------------- /resources/js/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; 3 | import { CheckIcon } from "@radix-ui/react-icons"; 4 | 5 | import { cn } from "@/utils/cn"; 6 | 7 | const Checkbox = React.forwardRef< 8 | React.ElementRef, 9 | React.ComponentPropsWithoutRef 10 | >(({ className, ...props }, ref) => ( 11 | 19 | 22 | 23 | 24 | 25 | )); 26 | Checkbox.displayName = CheckboxPrimitive.Root.displayName; 27 | 28 | export { Checkbox }; 29 | -------------------------------------------------------------------------------- /resources/js/ssr.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOMServer from 'react-dom/server'; 2 | import { createInertiaApp } from '@inertiajs/react'; 3 | import createServer from '@inertiajs/react/server'; 4 | import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; 5 | import { route } from '../../vendor/tightenco/ziggy'; 6 | import { RouteName } from 'ziggy-js'; 7 | 8 | const appName = import.meta.env.VITE_APP_NAME || 'Laravel'; 9 | 10 | createServer((page) => 11 | createInertiaApp({ 12 | page, 13 | render: ReactDOMServer.renderToString, 14 | title: (title) => `${title} - ${appName}`, 15 | resolve: (name) => resolvePageComponent(`./Pages/${name}.tsx`, import.meta.glob('./Pages/**/*.tsx')), 16 | setup: ({ App, props }) => { 17 | // @ts-expect-error 18 | global.route = (name, params, absolute) => 19 | route(name, params as any, absolute, { 20 | // @ts-expect-error 21 | ...page.props.ziggy, 22 | // @ts-expect-error 23 | location: new URL(page.props.ziggy.location), 24 | }); 25 | 26 | return ; 27 | }, 28 | }) 29 | ); 30 | -------------------------------------------------------------------------------- /resources/js/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes { 7 | error?: string; 8 | } 9 | 10 | const Textarea = React.forwardRef( 11 | ({ className, error, ...props }, ref) => { 12 | return ( 13 |
14 |