Class-20 Learning management-system

Class-20 Learning management-system

ক্লাস নং-২০

আজকে থেকে শুরু হতে যাচ্ছে আমাদের এখন পর্যন্ত বড় ধরনের প্রজেক্ট ।এই প্রজেক্টি হতে যাচ্ছে একটি লার্নিং ম্যানেজমেন্ট সিস্টেম প্রজেক্ট এবং এর সাথে অনেক ধরনের functionality থাকবে আমরা সেগুলো লিস্ট আকারে দেখব এবং সবার প্রথমে আমাদের project functionality add করে database design করব।

আমাদের প্রজেক্টের ডেটাবেজ এর সাথে পরে হয়তো আরো কিছু প্রয়োজনে add করা হবে। app.dbdesigner.net/designer/schema/0-untitl..

এবার আসি আমাদের প্রজেক্ট তৈরির কাজে ।

প্রথমেই আপনি আগের মত একটা লারাভেল প্রজেক্ট তৈরি করে নেবেন।

যদিও আমাদের ভিডিওতে খুব সুন্দর ভাবে বলা আছে কিন্তু আপনি যদি নতুন হয়ে থাকেন এবং বুঝতে সমস্যা হয়ে থাকে তাহলে আপনি আমাদের blog.shikhun.net এ laravel ক্লাসের বিস্তারিত জানতে পারবেন।

এরপর প্রয়োজনীয় laravel extention গুলো install করে নেবেন।

এটা সম্পর্কে আরো বিস্তারিত আর্টিকেল আছে blog.shikhun.net এ কিভাবে laravel spatie permisson কাজ করে।

leads
=====
              $table->id();
            $table->string('name')->nullable();
            $table->string('phone');
            $table->string('email')->nullable();
            $table->timestamps();

courses
=======
 $table->id();
            $table->string('name');
            $table->string('description');
            $table->string('image');
            $table->unsignedBigInteger('user_id');
            $table->timestamps();
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
curriculam
==========
 $table->id();
            $table->string('name');
            $table->unsignedBigInteger('course_id');
            $table->timestamps();

            $table->foreign('course_id')->references('id')->on('courses')->onDelete('cascade');
exam
======
 $table->id();
            $table->string('name');
            $table->unsignedBigInteger('curriculum_id');
            $table->text('description');
            $table->timestamps();

            $table->foreign('curriculum_id')->references('id')->on('curriculums')->onDelete('cascade');
invoices
========
  $table->id();
            $table->dateTime('due_date');
            $table->dateTime('paid_date');
            $table->unsignedBigInteger('user_id');
            $table->timestamps();

            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
invoice item
=============
 $table->id();
            $table->string('name');
            $table->float('price');
            $table->unsignedBigInteger('quantity');
            $table->unsignedBigInteger('invoice_id');
            $table->timestamps();

            $table->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
attendance
==========
  $table->id();
            $table->unsignedBigInteger('curriculum_id');
            $table->unsignedBigInteger('user_id');
            $table->timestamps();

            $table->foreign('curriculum_id')->references('id')->on('curriculums')->onDelete('cascade');
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
homeworks
=========
  $table->id();
            $table->string('name');
            $table->string('description');
            $table->unsignedBigInteger('curriculum_id')->default(0);
            $table->unsignedBigInteger('exam_id')->default(0);
            $table->unsignedBigInteger('user_id');
            $table->string('link');
            $table->timestamps();

            $table->foreign('curriculum_id')->references('id')->on('curriculums')->onDelete('cascade');
            $table->foreign('exam_id')->references('id')->on('exams')->onDelete('cascade');
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
notes
=====
  $table->id();
            $table->text('description');
            $table->unsignedBigInteger('curriculum_id')->nullable();
            $table->unsignedBigInteger('exam_id')->nullable();
            $table->unsignedBigInteger('user_id')->nullable();
            $table->unsignedBigInteger('lead_id')->nullable();
            $table->timestamps();

            $table->foreign('curriculum_id')->references('id')->on('curriculums')->onDelete('cascade');
            $table->foreign('exam_id')->references('id')->on('exams')->onDelete('cascade');
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
            $table->foreign('lead_id')->references('id')->on('leads')->onDelete('cascade');

ব্যাস আমাদের মাইগ্রেশন ফাইল রেডি।

এবার আসি আমাদের model গুলো তে আমাদের প্রয়োজনীয় রিলেশন গুলো আমারা এখান থেকে ডিফাইন করে দেব।

Class-tutorial part-2

User model
==========
 public function invoices() {
        return $this->hasMany(Invoice::class);
    }


Lead model
==========
    public function notes() {
        return $this->hasMany(Note::class);
    }

Invoice model
=============
    public function items() {
        return $this->hasMany(InvoiceItem::class);
    }

exam model
=============
public function homeworks() {
        return $this->hasMany(Homework::class);
    }


Curriculum model
=================
    public function homeworks() {
        return $this->hasMany(Homework::class);
    }

    public function attendances() {
        return $this->hasMany(Attendance::class);
    }

Course model
=============
    public function curriculumns() {
        return $this->hasMany(Curriculum::class);
    }

এখানে আমাদের মডেল্গুলোতে কোন টেবিলের সাথে আমাদের কোন টেবিলের রিলেশন সেগুলো ডিফাইন করে দিলাম ।

এবং এখন আমাদের এই প্রজেক্টের জন্য কিছু ডামি ডেটা প্রয়োজন তাই আমরা database seeder এবং factory use করব । তবে তার আগে আমাদের একটা migration চালিয়ে নিতে হবে যাতে করে আমাদের ডেটাবেজ টেবিলে ভাল ভাবে তৈরি হয়েছে কি না ভাল ভাবে বোঝা যাবে এবং যদি কোন error থাকে তাহলে আমরা সেগুলো solve করে নেব।

-প্রথমেই আমাদের ইউজার তৈরি করতে হবে এবং আমরা সেই ইউজার কে রোল সেট করব যে সে কি কি অপারেশন চালাতে পারবে।

ইউজার এর ক্ষেত্রে আমাদেরকে কয়েকটি রোল দিতে হবে এবং কে কোন টা access করতে পারবে সেগুলো সুন্দর ভাবে define করতে হবে।তো একটা একটা রোলের জন্য single ভাবে ইউজার তৈরি করে সেটাতে আবার রোল এসাইন করলে বেশ সময় সাপেক্ষ এবং আমাদের কোড বেশি হয়ে যায় যা practice but not good for application.

তাই আমরা এখানে একটা dynamic function তৈরি করব এবং সেখানে প্যারামিটার নিয়ে বিভিন্ন রোল এসাইন করব।

 $defaultPermissions = ['lead-management', 'create-admin'];
        foreach($defaultPermissions as $permission) {
            Permission::create(['name' => $permission]);
        }

private function create_user_with_role($type, $name, $email) {
        $role = Role::create([
            'name' => $type
        ]);

        $user = User::create([
            'name' => $name,
            'email' => $email,
            'password' => bcrypt('123')
        ]);

        if($type == 'Super Admin') {
            $role->givePermissionTo(Permission::all());
        } elseif($type == 'Leads') {
            $role->givePermissionTo('lead-management');
        }

        $user->assignRole($role);

        return $user;
    }




      $this->create_user_with_role('Super Admin', 'Super Admin', 'super-admin@lms.test');
        $this->create_user_with_role('Communication', 'Communication Team', 'communication@lms.test');
        $teacher = $this->create_user_with_role('Teacher', 'Teacher', 'teacher@lms.test');
        $this->create_user_with_role('Leads', 'Leads', 'leads@lms.test');

আমরা এখানে একটা private function make করলাম এবং সেখানে ।

  • -Super Admin

  • -Communication

  • -Teacher -Leads

বিভিন্ন রোলের ইউজারগুলো তৈরি করলাম।এবং private function এর মধ্যে আমরা কন্ডিশন দিয়ে দিলাম যদি এটা super-admin হয় তাহলে তার সব কিছু access করার পারমিশন থাকবে এবং যদি lead হয় তাহলে সে lead-management করতে পারবে।

ইউজার এর এই পার্টটুকু ক্লাস-৪ এর অংশ আগেই দেওয়া হলো migration চালানোর সময় এরর আসতে পারে তাই আপনি চাইলে শুধুমাত্র একটা ইউজার তৈরি করে মাইগ্রেশন চালাতে পারেন।

এবার আসেন আমার আপনার কনফিউশন টপিক role&permisson

  • --মনে রাখবেন প্রত্যেকটা অপারেশন হলো পারমিশন।

  • --অপারেশন গুলোর সমষ্টি হলো একটা রোল।

  • --এবং একটা রোল কে একটা ইউজার এর সাথে এসাইন করা যায়।

Class-tutorial part-3

-এবারে আমাদের প্রজেক্টে আরেকটা extention ব্যাবহার করা হবে এবং সেটা হলো livewire এই extention টা আমাদের ডেটাগুলোকে খুব সহজেই dynamic করার কাজে ব্যাবহার করা হয় । বিস্তারিত জানতে ভিসিটঃhttps://laravel-livewire.com/docs/2.x/quickstart

-প্রথমেই আমাদেরকে livewire কে install করে নিতে হবে।

 -composer require livewire/livewire

এবার আমরা যেখানে যেখানে ব্যাবহার করব সেখানে style টা বসাতে হবে ।যেহেতু এই প্রজেক্ট টা আমাদের frontend এউ কাজ করতে হবে তাই এই style টা বসাতে হবে। resources/views/layouts/app.blade.php file এর header এর আগে।

@livewireStyles

script টা body এর আগে বসাতে হবে।

@livewireScripts

এবার আমাদের livewire কে ব্যাবহার করতে হবে ।livewire কে ব্যাবহার করার জন্য আমাদের components make করতে হবে যেহেতু এটা components based কাজ করে।

php artisan make:livewire counter

এই কমান্ড টি রান করলে আপনার resources/veiews এর মধ্যে এবং app/http এর মধ্যে যথাক্রমে ২টা ফোল্ডার তৈরি হবে ।যেগুলোকে খুব সহজেই dynamaic করতে পারবেন।

Class-tutorial part-4

এবার আসি আমাদের factory & seeder এ যেহেতু আমরা migration করে ফেলেছি কিন্তু আমাদের কোন ডেটা নেই তাই আমাদের এখন factory & seeder তৈরি করে ডেটা দিতে হবে।

php artisan make:factory LeadFactory --model=Lead

এই কমান্ড টি রান করলে আপনার একটা factory এবং একটা মডেল তৈরি হবে ।

এবার আমাদের LeadFactory তে আমাদের যা ,যা লাগবে সেই অনুযায়ী কোড লিখব।

return [
            'name' => $this->faker->name,
            'email' => $this->faker->email,
            'phone' => $this->faker->phoneNumber,
        ];

এবার CurriculumFactory তৈরি করতে হবে এবং আমাদের factory তে Data assign করতে হবে।

php artisan make:factory CurriculumFactory --model=Curriculum
//CurriculumFactory
 return [
            'name' => $this->faker->sentence,
            'course_id' => 1
        ];

এবং আমাদের এবার ডেটাবেজ সিডার এ গিয়ে ডেটা জেনারেট করতে হবে।
  // create leads
      Lead::factory(100)->create();


  // create Curriculum
    Curriculum::factory(10)->create();

Class-tutorial part-5

প্রথমেই আমাদের একটা resource করে controller make করতে হবে তো এজন্য আমরা LeadController কে করব।যেহেতু এই নামে আমাদের একটা controlle আছে তাই আমরা এটাকে ডিলিট করে ।resourse দিয়ে আরেকটা controleer make করব।

php artisan make:controller LeadController --resource

এবার আমাদের web.php file এ রাউট তৈরি করতে হবে । এবং lead গুলো দেখার জন্য আমাদের ভিউ ফোল্ডার এর মধ্যে মডেলের নামের সাথে মিল রেখে একটা ফোল্ডার তৈরি করতে হবে

এবং index blade তৈরি করতে হবে এই ফাইলের মধ্যে আমাদের lead গুলো দেখাবো।

এবার আমাদের controller এর মধ্যে index function এ ভিউ টা দেখিয়ে দিতে হবে।

এবার dynamic করার পালা যেহেতু আমরা livewire install করেছি তাই এটা livewire দিয়ে dynamic করব।এবং সেজন্য আমাদের livewire components make করতে হবে।

php artisan livewire:make LeadIndex

কমান্ডটি রান করলে আমাদের controller and view তৈরি হবে এবং এখন আমরা এটাকে ব্যাবহার করতে পারব। এবার আমাদের যে livewire এর Controller টা রয়েছে সেখানে আমাদের ভিউ ফাইলে দেখানোর জন্য কোড গুলো লেখে ফেলতে পারি।

public function render()
    {

        $leads = Lead::paginate(10);

        return view('livewire.lead-index',[
            'leads' =>$leads,
        ]);
    }

    public function leadDelete($id) {
        //permission_check('lead-management');

        $lead = Lead::findOrFail($id);
        $lead->delete();

        flash()->addSuccess('Lead deleted successfully');
    }

এবার আমরা আমাদের vue এর জন্য যে ফাইল টা তৈরি হয়েছে সেখানে গিয়ে আমাদের leads গুলো দেখাতে পারি।

<table class="w-full table-auto">
        <tr>
            <th class="border px-4 py-2 text-left">Id </th>
            <th class="border px-4 py-2 text-left">Name </th>
            <th class="border px-4 py-2 text-left">Email </th>
            <th class="border px-4 py-2 text-left">Phone</th>
            <th class="border px-4 py-2">Registered</th>
            <th class="border px-4 py-2">Action</th>
        </tr>

        @foreach ($leads as $lead)
            <tr >
                <td class="border px-4 py-2">{{ $lead->id }}</td>
                <td class="border px-4 py-2">{{ $lead->name }}</td>
                <td class="border px-4 py-2">{{ $lead->email }}</td>
                <td class="border px-4 py-2">{{ $lead->phone }}</td>
                <td class="border px-4 py-2 text-center">{{ date('F,j,Y',strtotime($lead->created_at)) }}</td>
                <td class="border px-4 py-2 text-center">
                   <div class="flex items-center justify-center">
                    <a href="{{ route('lead.edit',$lead->id) }}">
                        @include('components.icons.edit')
                       </a>

                       <a class="px-2" href="{{ route('lead.show',$lead->id) }}">
                        @include('components.icons.eye')
                       </a>

                       <form onsubmit="return confirm('Are you sure?');" wire:submit.prevent="leadDelete({{ $lead->id }})">
                           <button type="submit">
                                   @include('components.icons.trash')
                           </button>
                       </form>
                   </div>
                </td>
            </tr>
        @endforeach
    </table>
    <div class="mt-4">
        {{ $leads->links()}}
    </div>

এখানে আরেকটা বিষয় livewrie এর এই ভিউ ফাইল টা কিন্তু কেবলমাত্র একটা Div এর মধ্যেই কাজ করে থাকে।

Class-tutorial part-6

আজকের ক্লাসে আমরা মূলত পারমিশন নিয়ে কাজ করব। তো প্রথমেই আমরা যেহেতে lead নিয়ে কাজ করছি তাই lead এর permisson টা আগে সেট করব।

এখানে আমাদের LeadController এ গিয়ে আগে user কে নিয়ে পারমিশন চেক করতে হবে।

 public function index( FlasherInterface $flasher)
    {

 $user = Auth::user();
        $check = $user->hasPermissionTo('lead-management');

        if($check){
            $flasher->addWarning('You are not authorized to access this page!');
            return redirect('dashboard');
        }
}

//This code for database seeder user private function
if($type == 'Super Admin') {
            $role->givePermissionTo(Permission::all());
        } elseif($type == 'Leads') {
            $role->givePermissionTo('lead-management');
        }

যেগুলো আমরা আগেই বলেছি। এছাড়াও আমরা frontend থেকেও permisson দিয়ে user কে ব্যাক করাতে পারি।

for frontend
=============
@can ('lead-management')
                     <x-nav-link :href="route('lead.index')" :active="request()->routeIs('lead.index')">
                         {{ __('Leads') }}
                     </x-nav-link>
                   @endcan

Class-tutorial part-7

আজকের ক্লাসে প্রথমেই আমরা permisson এর বিষয়েই কাজ করব ।তো গত ক্লাসের মতে আমরা আমাদের prmission check করার জন্য lead-controller এর মধ্যে index function এ চেক করেছিলাম। যেহেতু এটা আমাদের অনেক যায়গা থেকে চেক করা লাগবে তাই আমরা অন্য একটা ফাইল তৈরি করে সেটাকে সব যায়গায় একসেস করব।

তো এরজন্য প্রথমেই আমরা একটা ফাইল তৈরি করে নেই।apps/Helpers/helpers.php তো আমাদের ফোল্ডার এর মধ্যে ফাইল তৈরি করে আমাদের composer.json file এর মধ্যে autoload এর ওখানে

        "files":[
            "app/Helpers/helpers.php"
        ],

এবং এর পর আমাদের json file টা কে আবার autoload করতে হবে। এরজন্য আমাদের composer dump-autoload এই কমান্ড টি রান করলে আবার ফাইলগুলো autoload হবে।

এবার আমাদের helpers.php file এর মধ্যে function টি লিখতে পারে।

function permission_check($permission) {
    if(!Auth::user()->hasPermissionTo($permission)) {
        flash()->addWarning('You are not authorized to access this page');
        return redirect()->back();
    }
}

এবার আমরা এই function টিকে খুব সহজ ভাবেই call করতে পারব সব যায়গা থেকে।

তো আমরা আমাদের যে LeaderController আছে সেখানে index file এর মধ্যে আমরা এই function টা কল করতে পারি। permission_check('lead-management'); তাহলে আমাদের অন্য রোলের ইউজার গুলো সেটাকে আর access করতে পারবে না এবং তাদেরকে warning দিয়ে দেবে।

এবার আসি আমাদের edit নিয়ে ।আমরা আসলে এই প্রজেক্ট টা dynamicallly করতেছি livewie দিয়ে তাই আমাদের বেশ কিছু জিনিষ জানতে হবে livewire আমাদের প্রথমেই livewire দিয়ে components and view make করে নিতে হবে।

hp artisan livewire:make LeadEdit

আমাদের ভিউ তে এবং controller এর মধ্যে ২ টা ফাইল তৈরি হয়েছে ।

lead-edit.blade.php file এর মধ্যে আমাদের ডেটা এডিটের  প্রয়োজনীয় কোড লিখব।
<div>
<form wire:submit.prevent="submitForm" class="mb-6">
    <div class="flex -mx-4 mb-4">
        <div class="flex-1 px-4">
            <label for="" class="lms-label">Name</label>
            <input wire:model="name" type="text" class="lms-input">

            @error('name')
            <div class="text-red-500 text-sm mt-2">{{ $message }}</div>
            @enderror
        </div>

        <div class="flex-1 px-4">
            <label for="" class="lms-label">Email</label>
            <input wire:model="email" type="email" class="lms-input">

            @error('email')
            <div class="text-red-500 text-sm mt-2">{{ $message }}</div>
            @enderror
        </div>
        <div class="flex-1 px-4">
            <label for="" class="lms-label">Phone</label>
            <input wire:model="phone" type="tel" class="lms-input">

            @error('phone')
            <div class="text-red-500 text-sm mt-2">{{ $message }}</div>
            @enderror
        </div>
    @include('components.wire-loading-btn')
</form>

<h3 class="font-bold text-lg mb-4">Notes</h3>
    @foreach($notes as $note)
        <div class="mb-4 border border-gray-100 p-4">{{$note->description}}</div>
    @endforeach


    <h4 class="font-bold mb-2">Add new note</h4>
    <form wire:submit.prevent="addNote">
        <div class="mb-4">
            <textarea wire:model.lazy="note" class="lms-input" placeholder="Type note"></textarea>
        </div>
        <button class="lms-btn" type="submit">Save</button>
    </form>

</div>

এবং controller file এর মধ্যে প্রয়োজনীয় কোড গুলো লিখতে হবে।


public $lead_id;
    public $name;
    public $email;
    public $phone;

    public $note;

    public function mount() {
        $lead = Lead::findOrFail($this->lead_id);
        $this->lead_id = $lead->id;
        $this->name = $lead->name;
        $this->email = $lead->email;
        $this->phone = $lead->phone;
    }

    public function render()
    {
        $lead = Lead::findOrfail($this->lead_id);
         return view('livewire.lead-edit', [
            'notes' => $lead->notes
        ]);
    }

    protected $rules = [
        'email' => 'email',
        'phone' => 'required',
    ];

    public function submitForm() {
        sleep(5);

        $lead = Lead::findOrFail($this->lead_id);

        $this->validate();

        $lead->name = $this->name;
        $lead->email = $this->email;
        $lead->phone = $this->phone;
        $lead->save();

        flash()->addSuccess('Lead updated successfully');
    }

এটা livewire documentation এ অনেক সুন্দর ভাবে বলা আছে কিভাবে আমরা ডেটা শো করাতে পারি।

এবং আমরা যে ফাইলে আমাদের এই livewire কে লোড করাবো সেখানে আইডি পাঠিয়ে দেব ডায়নামিক ভাবে access করার জন্য।

 <livewire:lead-edit  :lead_id="$lead_id"/>

এবং আমরা উপরোক্ত বাটন এবং স্পিনার ফাইল টা আমরা একটা components এর মধ্য থেকে ইনপুট করেছি ।

<div wire:loading.flex class="items-center">
    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 animate-spin">
        <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12a7.5 7.5 0 0015 0m-15 0a7.5 7.5 0 1115 0m-15 0H3m16.5 0H21m-1.5 0H12m-8.457 3.077l1.41-.513m14.095-5.13l1.41-.513M5.106 17.785l1.15-.964m11.49-9.642l1.149-.964M7.501 19.795l.75-1.3m7.5-12.99l.75-1.3m-6.063 16.658l.26-1.477m2.605-14.772l.26-1.477m0 17.726l-.26-1.477M10.698 4.614l-.26-1.477M16.5 19.794l-.75-1.299M7.5 4.205L12 12m6.894 5.785l-1.149-.964M6.256 7.178l-1.15-.964m15.352 8.864l-1.41-.513M4.954 9.435l-1.41-.514M12.002 12l-3.75 6.495" />
    </svg>

    <span class="pl-2">Loading ...</span>
</div>

<button wire:loading.remove type="submit" class="lms-btn">Submit</button>

আশা করি বুঝতে পেরেছেন। সব কিছুই step by step code সহ দেওয়া আছে আপনি চাইলেই এটা দিয়ে প্রজেক্ট তৈরি করে ফেলতে পারবেন। আর আমাদের tutorial তো আছেই।

Happy Learning

Rakibul Islam