saving clup

الجمعية الشهرية باستخدام لارافيل

لو عايز تعمل برنامج لتصميم جمعية شهرية باستخدام Laravel، الفكرة هنا إنك بتعمل تطبيق ويب بحيث يكون لكل واحد من الاعضاء اكثر من دور ومبلغ شهري يدفعه والدور يشيل اكثر من عضو . الجمعية دي تعتبر مفهوم واسع، بتلاقيها في ثقافات كتير وأحيانًا بمعاني مختلفة، زي الصندوق العائلي، أو زي جمعيات الأصدقاء اللي كل شهر حد منهم بياخد “النوبة” بتاعته. Laravel هيسهل عليك الموضوع جدًا لإنه بيوفر الأدوات اللي هتساعدك تدير قاعدة البيانات، وتعمل روابط بين الأعضاء والجمعيات والأدوار بشكل منظم. وكمان تقدر تتحكم في المبالغ الشهرية لكل عضو ولكل دور، ودا بيساعد الجمعية إنها تشتغل بشكل منظم وسهل الفهم لكل الناس المشتركة فيها.

الجمعية الشهرية باستخدام لارافيل

المتطلبات

تكلمنا في السابق عن الادوات المستخدمة مثل فيجوال ستوديو كود كمحرر لكتابة الكود و composer لتثبيت لارافيل ونود جي اس لرياكت وهنا نستخدم رياكت للاستفادة من قوتة في الواجهه الامامية و عدم التحميل الكامل للصفحة اثناء ادخال بيانات جديدة

تثبيت laravel

				
					composer create-project laravel/laravel Associations
				
			

بعد التثبيت الدخول علي فولدر المشروع بالكود التالي

				
					cd Associations
				
			

تثبيت Breeze

				
					composer require laravel/breeze --dev
				
			

تثبيت React

				
					php artisan breeze:install react
				
			

ضبط اعدادات ملف env.

				
					DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=association_db
DB_USERNAME=root
DB_PASSWORD=
				
			

قمنا بانشاء قاعدة بيانات باسم association_db ونقوم بترحيلها باستخدام migrate

				
					php artisan migrate
				
			

سيطلب منك انشاء قاعدة بيانات جديدة باسم association_db 

بعد الموافقة علي انشاء قاعدة البيانات نكون قد اتممنا الجزء الاول وهو authentication بهذا الشكل

اضافة animate

				
					npm install animate.css --save
				
			

بعد ذلك نضع الكود التالي في صفحة app.jsx لاستيراد animation

				
					import 'animate.css';
				
			

انشاء جدول الجمعيات Associations

سوف نقوم بانشاء موديل وكنترولر وميجريشن للجمعيات باتباع التالي

				
					php artisan make:model Association -mrc
				
			

المايجريشن migrate

سوف يكون الجدول الخاص بالجمعيات عبارة عن اسم الجمعية وعدد الاشهر واجمالي المبلغ الشهري علي اعتبار ان الدور كل شهر من database اختيار migrations ثم اختيار جدول associations ويكون بالشكل التالي

				
					public function up(): void
    {
        Schema::create('associations', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->integer('total_months');
            $table->decimal('role_price', 10, 2);
            $table->timestamps();
        });
    }
				
			

ثم ترحيل الجدول لقواعد البيانات باستخدام الامر التالي

				
					php artisan migrate
				
			

الموديل Model

من app اختيار Models ثم Association واضافة الكود التالي

				
					class Association extends Model
{
    protected $fillable = ['name', 'total_months', 'role_price'];
}
				
			

كونترولر Controller

من app اختيار http ثم controller ثم AssociationController واضافة الكود التالي للصفحة بحيث يتم استيراد الموديل واضافة index و store 

				
					namespace App\Http\Controllers;

use App\Models\Association;
use Illuminate\Http\Request;
use Inertia\Inertia;

class AssociationController extends Controller
{
    
    public function index()
    {
        $associations = Association::all();

        return Inertia::render('Associations/Index', [
            'associations' => $associations
        ]);
    }
    
 
    public function store(Request $request)
    {
        $validated = $request->validate([
            'name' => 'required|string|max:255',
            'total_months' => 'required|integer|min:1',
            'role_price' => 'required|numeric|min:0',
        ]);

        $group = Association::create([
            'name' => $validated['name'],
            'total_months' => $validated['total_months'],
            'role_price' => $validated['role_price'],
        ]);

        return redirect()->route('associations.index');
    }

				
			

التوجهات (الراوت) Routes

من routes نذهب الي web واضافة الكود

				
					//namespace
use App\Http\Controllers\AssociationController;   

// code
Route::resource('associations', AssociationController::class)
    ->middleware(['auth', 'verified']);

				
			

الواجهه الامامية لصفحة الجمعيات Associations/Index

من داخل resources\js\Pages نقوم بانشاء Associations وداخلة ملف Index.jsx ولصق الكود التالي

				
					
import React, { useState } from 'react';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import InputError from '@/Components/InputError';
import PrimaryButton from '@/Components/PrimaryButton';
import { useForm, Head, Link } from '@inertiajs/react';

export default function Associations({ auth, associations }) {

    const { data, setData, post, processing, reset, errors } = useForm({
        name: '',
        total_months: '',
        role_price: ''
    });

    const handleSubmit = (e) => {
        e.preventDefault();
        post(route('associations.store'), { onSuccess: () => reset() });
    };

    return (
        <AuthenticatedLayout user={auth.user}>
            <div style={{ direction: 'rtl' }}>
            <Head title="Association" />
            <div className="max-w-2xl mx-auto p-4 sm:p-6 lg:p-8 animate__animated animate__fadeIn">
                        <form onSubmit={handleSubmit}>
                            <input
                                type="text"
                                value={data.name}
                                onChange={e => setData('name', e.target.value)}
                                placeholder="اضافة جمعية جديدة"
                                className="custom-input w-full border border-gray-300 p-2 rounded"
                            />

                            <input
                                type="number"
                                value={data.total_months}
                                onChange={e => setData('total_months', e.target.value)}
                                placeholder="اجمالي الاشهر"
                                className="custom-input w-full border border-gray-300 p-2 rounded mt-4"
                            />

                            <input
                                type="number"
                                value={data.role_price}
                                onChange={e => setData('role_price', e.target.value)}
                                placeholder="مبلغ الدور"
                                className="custom-input w-full border border-gray-300 p-2 rounded mt-4"
                            />

                            <InputError message={errors.message} className="mt-2" />
                            <PrimaryButton className="mt-4" disabled={processing}>Add Associations</PrimaryButton>
                        </form>
                    </div>

                    <div className="flex flex-wrap justify-center mt-4">
                        {associations.map((association, index) => (
                            <div
                                className={`animate__animated animate__fadeInUp m-12 transition-transform duration-300 hover:scale-105`}
                                style={{ animationDelay: `${index * 0.1}s` }}
                                key={association.id}
                            >
                                <div className="bg-white shadow-md rounded-lg p-8 flex flex-row relative w-72 hover:bg-yellow-100 hover:text-blue-700">
                                    <div className="flex-grow text-right pr-4">
                                        <div className="text-2xl font-bold">
                                        <Link href="" className="text-blue-600 hover:underline">
                                            {association.name}
                                        </Link>
                                        </div>
                                        <div className="text-gray-600">{association.total_months} شهر</div>
                                        <div className="text-gray-600">{association.role_price} في الشهر</div>
                                    </div>
                                    <div className="absolute bottom-2 left-2">
                                        
                                    </div>
                                </div>
                            </div>
                        ))}
                    </div>

            </div>
            </AuthenticatedLayout>
        );
}
				
			

لمشاهدة ما تم تنفيذة استخدم الامر التالي ثم قم بادخال بيانات للتاكد من ان الكود سليم

				
					npm run dev
				
			

يجب اضافة اسم الصفحة لل navbar لسهولة التصفح وبالتالي يمكنك اضافة باقي الصفحات باتباعك نفس الطريقة

نذهب الي مجلد Layouts ومن داخلة نختار AuthenticatedLayout.jsx  ثم نضيف الكود التالي بعد كود dashboard وتعديل الروت الخاص بالصفحة بالشكل التالي

				
					<div className="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
                                <NavLink
                                    href={route('dashboard')}
                                    active={route().current('dashboard')}
                                >
                                    Dashboard
                                </NavLink>
                                <NavLink
                                    href={route('associations.index')}
                                    active={route().current('associations.index')}
                                >
                                    Association
                                </NavLink>
                            </div>
				
			

قمنا باضافة navlink خاص بصفحة association بعد صفحة dashboard وتعديل الروت الخاص بة وسنقوم ايضا باضافتة مرة اخري في نفس الصفحة لكن ResponsiveNavLink

				
					<div className="space-y-1 pb-3 pt-2">
                        <ResponsiveNavLink
                            href={route('dashboard')}
                            active={route().current('dashboard')}
                        >
                            Dashboard
                        </ResponsiveNavLink>
                        <ResponsiveNavLink
                            href={route('associations.index')}
                            active={route().current('associations.index')}
                        >
                            Association
                        </ResponsiveNavLink>
                    </div>

				
			

انشاء جدول الادوار Roles

				
					php artisan make:model Role -mrc
				
			

المايجريشن migrate

سيرتبط جدول الادوار بجدول الجمعيات حيث ان لكل جمعية اكثر من دور بالتالي سوف يتم انشاء الادوار الخاصة بكل جمعية بمجرد تسجيل الجمعية واختيار عدد الادوار وسيتم احتساب اول شهر في الجمعية من تاريخ تسجيل البيان. لنري ذلك

				
					public function up(): void
    {
        Schema::create('roles', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('start_date')->nullable();
            $table->foreignId('association_id')->constrained()->onDelete('cascade');
            $table->timestamps();
        });
    }
				
			
				
					// للترحيل 
php artisan migrate
				
			

الموديل Model

				
					// role model
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    protected $fillable = [
        'name', 
        'association_id',
        'start_date',
    ];
    
    public function associations()
    {
        return $this->belongsTo(Association::class);
    }
}
				
			

ويجب اضافة الكود التالي في موديل Association للعلاقة بين الجمعية والادوار

				
					// association model
public function roles()
    {
        return $this->hasMany(Role::class);
    }
				
			

كونترولر Controller

سوف نضيف الي index في RoleController كل تفاصيل الادوار الخاصة بجمعية معينة بالشكل الاتي

				
					 
use App\Models\Association;
use Inertia\Inertia;

// code
 public function index()
    {
        $associations = Association::with(['roles'])->get();
        
        return Inertia::render('Roles/Index', [
            'associations' => $associations,
        ]);
    }
				
			

بعد ذلك نقوم باضافة الادوار الي store في AssociationController الذي يقوم باضافة الادوار الي الجمعية بعد ادخال بيانات الجمعية لتصبح بالشكل التالي

				
					// استدعاء
use Carbon\Carbon;
use Illuminate\Http\RedirectResponse;
use App\Models\Role;

// code
public function store(Request $request): RedirectResponse
{
    $validated = $request->validate([
        'name' => 'required|string|max:255',
        'total_months' => 'required|integer|min:1',
        'role_price' => 'required|numeric|min:0',
    ]);

    $association = Association::create([
        'name' => $validated['name'],
        'total_months' => $validated['total_months'],
        'role_price' => $validated['role_price'],
    ]);

    // لإضافة الأدوار عند إنشاء جمعية جديدة مع تواريخ البداية لكل دور
    $role_names = [
        'الأول', 'الثاني', 'الثالث', 'الرابع', 'الخامس', 'السادس', 'السابع', 'الثامن', 'التاسع', 'العاشر',
        'الحادي عشر', 'الثاني عشر', 'الثالث عشر', 'الرابع عشر', 'الخامس عشر', 'السادس عشر', 'السابع عشر',
        'الثامن عشر', 'التاسع عشر', 'العشرون'
    ];

    // تاريخ بداية الأدوار
    $startDate = Carbon::now()->startOfMonth(); // البداية من بداية الشهر الحالي

    for ($i = 0; $i < $validated['total_months']; $i++) {
        // زيادة عدد الأشهر لكل دور
        $roleDate = $startDate->copy()->addMonths($i)->format('m-Y');

        Role::create([
            'name' => $role_names[$i] ?? 'الدور ' . ($i + 1),
            'association_id' => $association->id,
            'start_date' => $roleDate, // حفظ الشهر والسنة بدون تكرار
        ]);
    }
    return redirect(route('associations.index'));
}
				
			

التوجهات (الراوت) Routes

				
					//namespace
use App\Http\Controllers\RoleController;
//code
Route::resource('roles', RoleController::class)
     ->middleware(['auth', 'verified']);

				
			

الواجهه الامامية لصفحة الادوار Roles/Index

				
					//resources\js\Pages\Roles\Index.jsx
import React, { useState } from 'react';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head, useForm } from '@inertiajs/react';

export default function Roles({ auth, associations }) {
    
    return (
        <AuthenticatedLayout user={auth.user}>
            <div style={{ direction: 'rtl' }}>
            <Head title="Roles" />
            <div className="flex flex-wrap justify-center mt-4">
                    {associations.map((association, index) => (
                        <div
                            className="animate__animated animate__fadeIn"
                            style={{ animationDelay: `${index * 0.6}s` }}
                            key={association.id}
                        >
                            <div className="text-center">
                                <h2 className="text-3xl font-bold mb-4 text-blue-700">{association.name}</h2>
                                <p>عدد الأشهر: {association.total_months}</p>
                                <p>مبلغ الدور: {association.role_price}</p>
                                
                                <h3 className="mt-4 text-lg font-bold">أدوار الجمعية</h3>
                                <div className="flex flex-wrap justify-center mt-4">
                                        {association.roles.map((role, roleIndex) => (
                                            <div
                                                className="animate__animated animate__fadeInUp m-4 transition-transform duration-300 hover:scale-105 cursor-pointer"
                                                style={{ animationDelay: `${roleIndex * 0.3}s` }}
                                                key={role.id}
                                                onClick={() => openModal(association, role)}
                                            >
                                                <div className="bg-white shadow-md rounded-lg p-4 flex flex-row relative w-80 hover:bg-yellow-100 hover:text-blue-700">
                                                <div className="flex-grow text-right pr-2">
                                                <div className="flex justify-between">
                                                        <div className="text-1xl font-bold">{role.name}</div>
                                                        <div className="text-1xl font-bold">{role.start_date}</div>
                                                    </div>
                                                       
                                                    </div>
                                                </div>
                                            </div>
                                        ))}
                                    </div>
                            </div>
                        </div>
                    ))}
                </div>
            </div>
        </AuthenticatedLayout>
    );
}

				
			

بعد ذلك اضافة navlink خاص بصفحة roles مثل الصفحة السابقة

				
					npm run dev
				
			

بهذا قد قمنا بتنفيذ الجزء الخاص باضافة الجمعية وعدد الادوار بها مع احتساب اول شهر فيها من حين ادخال البيانات ونظرا لطول المقال تم تقسيمه الي جزئين 

الجزء الثاني

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

Scroll to Top