Advertisement
[Responsive Banner Ad - 728x90]
#CRM #HRM #Attendance #SaaS #PHP #Laravel #React #.NET

πŸš€ Build Complete CRM HRM Attendance System 2026: Multi-Tech Stack Guide

Step-by-step guide to build a professional attendance management portal with PHP Laravel, React, .NET, jQuery, and Bootstrap. Includes database design, multi-tenancy, and toast notifications.

45 min read Intermediate Updated for 2026 4+ Tech Stacks

Blogs Team

Development Experts β€’ 2026 Edition

Advertisement
[Responsive Medium Rectangle Ad - 300x250]

1 System Overview: What We're Building

A complete CRM HRM Attendance System that combines employee management, attendance tracking, leave management, and reporting in one platform. Available in multiple tech stacks.

πŸ”‘ Key Features

  • Employee check-in/out with photo
  • Geo-fencing location validation
  • Leave request & approval workflow
  • Real-time attendance dashboard
  • Automated payroll reports
  • Multi-tenant SaaS architecture
  • Toast notifications
  • Role-based access control

Perfect for: HR departments, remote teams,中小 businesses, and SaaS startups wanting to build their own attendance solution.

πŸ“Š Tech Stack Options

Backend:

PHP Laravel, .NET Core, Node.js

Frontend:

React, jQuery, Bootstrap 5

Database:

MySQL, PostgreSQL, SQL Server

Hosting:

Shared, VPS, Cloud (AWS/Azure)

2 Database Design: MySQL Schema

πŸ“ Core Tables

-- Users table
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    employee_id VARCHAR(50) UNIQUE NOT NULL,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    department_id INT,
    role ENUM('admin', 'manager', 'employee') DEFAULT 'employee',
    status ENUM('active', 'inactive') DEFAULT 'active',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Departments
CREATE TABLE departments (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    manager_id INT
);

-- Attendance
CREATE TABLE attendance (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    date DATE NOT NULL,
    check_in DATETIME NOT NULL,
    check_out DATETIME,
    status ENUM('present', 'absent', 'late', 'half-day'),
    working_hours DECIMAL(5,2),
    check_in_photo VARCHAR(255),
    check_in_location VARCHAR(255),
    FOREIGN KEY (user_id) REFERENCES users(id)
);

-- Leave requests
CREATE TABLE leave_requests (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    leave_type ENUM('annual', 'sick', 'personal') NOT NULL,
    start_date DATE NOT NULL,
    end_date DATE NOT NULL,
    reason TEXT,
    status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

πŸ”— Relationships

  • users.department_id β†’ departments.id
  • attendance.user_id β†’ users.id
  • leave_requests.user_id β†’ users.id
  • departments.manager_id β†’ users.id

πŸ“Š Sample Query

-- Get today's attendance summary
SELECT 
    d.name as department,
    COUNT(u.id) as total,
    SUM(CASE WHEN a.status = 'present' THEN 1 END) as present,
    SUM(CASE WHEN a.status = 'late' THEN 1 END) as late,
    SUM(CASE WHEN a.status = 'absent' THEN 1 END) as absent
FROM departments d
LEFT JOIN users u ON u.department_id = d.id
LEFT JOIN attendance a ON a.user_id = u.id 
    AND a.date = CURDATE()
GROUP BY d.id;
Advertisement
[Responsive Leaderboard Ad - 728x90]

3 PHP Laravel Implementation

πŸ“¦ Installation

# Create Laravel project
composer create-project laravel/laravel attendance-system

# Install required packages
composer require laravel/sanctum
composer require spatie/laravel-permission
composer require maatwebsite/excel

# Generate models and migrations
php artisan make:model Attendance -m
php artisan make:model LeaveRequest -m
php artisan make:controller API/AttendanceController

πŸ“ Attendance Model

<?php
// app/Models/Attendance.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Attendance extends Model
{
    protected $fillable = [
        'user_id', 'date', 'check_in', 'check_out',
        'status', 'working_hours', 'check_in_photo',
        'check_in_location', 'check_in_ip'
    ];

    protected $casts = [
        'date' => 'date',
        'check_in' => 'datetime',
        'check_out' => 'datetime'
    ];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function scopeToday($query)
    {
        return $query->whereDate('date', today());
    }
}

🎯 Check-in Controller

<?php
// app/Http/Controllers/API/AttendanceController.php

namespace App\Http\Controllers\API;

use App\Models\Attendance;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Carbon\Carbon;

class AttendanceController extends Controller
{
    public function checkIn(Request $request)
    {
        $request->validate([
            'photo' => 'required|image|max:2048',
            'latitude' => 'required|numeric',
            'longitude' => 'required|numeric'
        ]);

        // Check if already checked in today
        $existing = Attendance::where('user_id', auth()->id())
            ->whereDate('date', Carbon::today())
            ->first();

        if ($existing) {
            return response()->json([
                'message' => 'Already checked in today'
            ], 400);
        }

        // Save photo
        $photoPath = $request->file('photo')
            ->store('attendance/' . auth()->id(), 'public');

        // Create attendance record
        $attendance = Attendance::create([
            'user_id' => auth()->id(),
            'date' => Carbon::today(),
            'check_in' => Carbon::now(),
            'status' => $this->determineStatus(),
            'check_in_photo' => $photoPath,
            'check_in_location' => json_encode([
                'lat' => $request->latitude,
                'lng' => $request->longitude
            ]),
            'check_in_ip' => $request->ip()
        ]);

        return response()->json([
            'message' => 'Check-in successful',
            'data' => $attendance
        ]);
    }

    private function determineStatus()
    {
        $officeStart = Carbon::createFromTime(9, 0, 0); // 9 AM
        $now = Carbon::now();
        
        if ($now->lte($officeStart)) {
            return 'present';
        } elseif ($now->lte($officeStart->copy()->addMinutes(15))) {
            return 'present'; // Grace period
        } else {
            return 'late';
        }
    }
}
Advertisement
[Responsive Large Rectangle Ad - 336x280]

4 React.js Frontend

βš›οΈ Check-in Component

// components/CheckIn.jsx
import React, { useState, useRef } from 'react';
import axios from 'axios';
import Webcam from 'react-webcam';

function CheckIn() {
    const [loading, setLoading] = useState(false);
    const [location, setLocation] = useState(null);
    const webcamRef = useRef(null);

    const getLocation = () => {
        if (navigator.geolocation) {
            navigator.geolocation.getCurrentPosition(
                (position) => {
                    setLocation({
                        lat: position.coords.latitude,
                        lng: position.coords.longitude
                    });
                },
                (error) => {
                    alert('Please enable location access');
                }
            );
        }
    };

    const capturePhoto = () => {
        const imageSrc = webcamRef.current.getScreenshot();
        return dataURLtoFile(imageSrc, 'checkin.jpg');
    };

    const dataURLtoFile = (dataurl, filename) => {
        let arr = dataurl.split(','),
            mime = arr[0].match(/:(.*?);/)[1],
            bstr = atob(arr[1]),
            n = bstr.length,
            u8arr = new Uint8Array(n);
            
        while (n--) {
            u8arr[n] = bstr.charCodeAt(n);
        }
        return new File([u8arr], filename, { type: mime });
    };

    const handleCheckIn = async () => {
        setLoading(true);
        
        const photo = capturePhoto();
        const formData = new FormData();
        formData.append('photo', photo);
        formData.append('latitude', location.lat);
        formData.append('longitude', location.lng);

        try {
            const response = await axios.post('/api/attendance/checkin', 
                formData, {
                    headers: {
                        'Content-Type': 'multipart/form-data'
                    }
                }
            );
            alert('Check-in successful!');
        } catch (error) {
            alert(error.response?.data?.message || 'Check-in failed');
        } finally {
            setLoading(false);
        }
    };

    return (
        <div className="card p-4">
            <h3>Attendance Check-in</h3>
            
            <Webcam
                ref={webcamRef}
                screenshotFormat="image/jpeg"
                className="w-100 mb-3 rounded"
            />
            
            <button 
                onClick={getLocation}
                className="btn btn-info mb-3"
                disabled={location}
            >
                {location ? 'Location Captured βœ“' : 'Get Location'}
            </button>
            
            <button
                onClick={handleCheckIn}
                className="btn btn-primary btn-lg"
                disabled={!location || loading}
            >
                {loading ? 'Processing...' : 'Check In'}
            </button>
        </div>
    );
}

export default CheckIn;

πŸ“Š Dashboard Component

// components/Dashboard.jsx
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import {
    Chart as ChartJS,
    CategoryScale,
    LinearScale,
    BarElement,
    Title,
    Tooltip,
    Legend
} from 'chart.js';
import { Bar } from 'react-chartjs-2';

ChartJS.register(
    CategoryScale,
    LinearScale,
    BarElement,
    Title,
    Tooltip,
    Legend
);

function Dashboard() {
    const [summary, setSummary] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        fetchSummary();
    }, []);

    const fetchSummary = async () => {
        try {
            const response = await axios.get('/api/attendance/summary');
            setSummary(response.data);
        } catch (error) {
            console.error('Failed to fetch summary', error);
        } finally {
            setLoading(false);
        }
    };

    if (loading) return <div>Loading...</div>;

    const chartData = {
        labels: summary?.departments.map(d => d.name),
        datasets: [
            {
                label: 'Present',
                data: summary?.departments.map(d => d.present),
                backgroundColor: '#10b981'
            },
            {
                label: 'Late',
                data: summary?.departments.map(d => d.late),
                backgroundColor: '#f59e0b'
            },
            {
                label: 'Absent',
                data: summary?.departments.map(d => d.absent),
                backgroundColor: '#ef4444'
            }
        ]
    };

    const options = {
        responsive: true,
        plugins: {
            legend: {
                position: 'top'
            },
            title: {
                display: true,
                text: 'Daily Attendance Summary'
            }
        }
    };

    return (
        <div className="dashboard">
            <div className="row mb-4">
                <div className="col-md-3">
                    <div className="card bg-success text-white">
                        <div className="card-body">
                            <h5>Present</h5>
                            <h2>{summary?.total.present}</h2>
                        </div>
                    </div>
                </div>
                <div className="col-md-3">
                    <div className="card bg-warning text-white">
                        <div className="card-body">
                            <h5>Late</h5>
                            <h2>{summary?.total.late}</h2>
                        </div>
                    </div>
                </div>
                <div className="col-md-3">
                    <div className="card bg-danger text-white">
                        <div className="card-body">
                            <h5>Absent</h5>
                            <h2>{summary?.total.absent}</h2>
                        </div>
                    </div>
                </div>
                <div className="col-md-3">
                    <div className="card bg-info text-white">
                        <div className="card-body">
                            <h5>On Leave</h5>
                            <h2>{summary?.total.leave}</h2>
                        </div>
                    </div>
                </div>
            </div>
            
            <div className="card p-4">
                <Bar options={options} data={chartData} />
            </div>
        </div>
    );
}

export default Dashboard;

5 .NET Core Web API

πŸ”· Model Class

// Models/Attendance.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace AttendanceSystem.Models
{
    public class Attendance
    {
        [Key]
        public int Id { get; set; }
        
        [Required]
        public string UserId { get; set; }
        
        [ForeignKey("UserId")]
        public virtual ApplicationUser User { get; set; }
        
        [Required]
        public DateTime Date { get; set; }
        
        public DateTime CheckIn { get; set; }
        
        public DateTime? CheckOut { get; set; }
        
        [StringLength(20)]
        public string Status { get; set; }
        
        [Column(TypeName = "decimal(5,2)")]
        public decimal WorkingHours { get; set; }
        
        public string CheckInPhoto { get; set; }
        
        public string CheckInLocation { get; set; }
        
        public string CheckInIP { get; set; }
    }
}

🎯 API Controller

// Controllers/AttendanceController.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using AttendanceSystem.Data;
using AttendanceSystem.Models;
using System.Security.Claims;

namespace AttendanceSystem.Controllers
{
    [Authorize]
    [ApiController]
    [Route("api/[controller]")]
    public class AttendanceController : ControllerBase
    {
        private readonly ApplicationDbContext _context;
        private readonly IWebHostEnvironment _env;

        public AttendanceController(
            ApplicationDbContext context,
            IWebHostEnvironment env)
        {
            _context = context;
            _env = env;
        }

        [HttpPost("checkin")]
        public async Task CheckIn(
            [FromForm] CheckInDto model)
        {
            var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
            
            // Check existing
            var existing = await _context.Attendances
                .AnyAsync(a => a.UserId == userId && 
                    a.Date == DateTime.Today);

            if (existing)
            {
                return BadRequest(new { 
                    message = "Already checked in today" 
                });
            }

            // Save photo
            string photoPath = null;
            if (model.Photo != null)
            {
                string uploadsFolder = Path.Combine(
                    _env.WebRootPath, 
                    "uploads", 
                    "attendance"
                );
                
                Directory.CreateDirectory(uploadsFolder);
                
                string fileName = Guid.NewGuid().ToString() 
                    + Path.GetExtension(model.Photo.FileName);
                    
                string filePath = Path.Combine(uploadsFolder, fileName);
                
                using (var stream = new FileStream(filePath, FileMode.Create))
                {
                    await model.Photo.CopyToAsync(stream);
                }
                
                photoPath = Path.Combine("uploads", "attendance", fileName);
            }

            var attendance = new Attendance
            {
                UserId = userId,
                Date = DateTime.Today,
                CheckIn = DateTime.Now,
                Status = DetermineStatus(),
                CheckInPhoto = photoPath,
                CheckInLocation = $"{model.Latitude},{model.Longitude}",
                CheckInIP = HttpContext.Connection.RemoteIpAddress?.ToString()
            };

            _context.Attendances.Add(attendance);
            await _context.SaveChangesAsync();

            return Ok(new
            {
                message = "Check-in successful",
                data = attendance
            });
        }

        private string DetermineStatus()
        {
            var officeStart = new TimeSpan(9, 0, 0);
            var now = DateTime.Now.TimeOfDay;
            
            if (now <= officeStart.Add(TimeSpan.FromMinutes(15)))
                return "present";
            
            return now <= officeStart.Add(TimeSpan.FromMinutes(30)) 
                ? "late" : "absent";
        }
    }
}
Advertisement
[Responsive Leaderboard Ad - 728x90]

6 jQuery + Bootstrap Quick Implementation

πŸ“„ HTML Structure

<!-- attendance.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Attendance System</title>
    
    <!-- Bootstrap 5 -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    
    <!-- Font Awesome -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body>
    <div class="container mt-5">
        <div class="row">
            <div class="col-md-6 mx-auto">
                <div class="card shadow">
                    <div class="card-header bg-primary text-white">
                        <h3 class="mb-0">Employee Check-in</h3>
                    </div>
                    <div class="card-body">
                        <video id="video" class="w-100 mb-3 rounded" autoplay></video>
                        <canvas id="canvas" style="display:none;"></canvas>
                        
                        <button id="captureBtn" class="btn btn-info w-100 mb-3">
                            <i class="fas fa-camera me-2"></i>Capture Photo
                        </button>
                        
                        <button id="locationBtn" class="btn btn-warning w-100 mb-3">
                            <i class="fas fa-map-marker-alt me-2"></i>Get Location
                        </button>
                        
                        <button id="checkinBtn" class="btn btn-success btn-lg w-100" disabled>
                            <i class="fas fa-fingerprint me-2"></i>Check In
                        </button>
                    </div>
                </div>
            </div>
        </div>
    </div>
    
    <!-- Scripts -->
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

πŸ”„ jQuery AJAX

<script>
$(document).ready(function() {
    let photoData = null;
    let locationData = null;
    
    // Initialize camera
    navigator.mediaDevices.getUserMedia({ video: true })
        .then(function(stream) {
            $('#video')[0].srcObject = stream;
        })
        .catch(function(error) {
            alert('Camera access denied');
        });
    
    // Capture photo
    $('#captureBtn').click(function() {
        const video = $('#video')[0];
        const canvas = $('#canvas')[0];
        const context = canvas.getContext('2d');
        
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        context.drawImage(video, 0, 0, canvas.width, canvas.height);
        
        photoData = canvas.toDataURL('image/jpeg');
        $(this).html('<i class="fas fa-check me-2"></i>Photo Captured').removeClass('btn-info').addClass('btn-success');
        
        enableCheckIn();
    });
    
    // Get location
    $('#locationBtn').click(function() {
        if (navigator.geolocation) {
            navigator.geolocation.getCurrentPosition(
                function(position) {
                    locationData = {
                        lat: position.coords.latitude,
                        lng: position.coords.longitude
                    };
                    
                    $(this).html('<i class="fas fa-check me-2"></i>Location Captured')
                        .removeClass('btn-warning')
                        .addClass('btn-success');
                    
                    enableCheckIn();
                }.bind(this),
                function(error) {
                    alert('Please enable location access');
                }
            );
        }
    });
    
    function enableCheckIn() {
        if (photoData && locationData) {
            $('#checkinBtn').prop('disabled', false);
        }
    }
    
    // Check in
    $('#checkinBtn').click(function() {
        const btn = $(this);
        btn.html('<i class="fas fa-spinner fa-spin me-2"></i>Processing...').prop('disabled', true);
        
        // Convert base64 to blob
        const block = photoData.split(';');
        const contentType = block[0].split(':')[1];
        const realData = block[1].split(',')[1];
        const blob = b64toBlob(realData, contentType);
        
        const formData = new FormData();
        formData.append('photo', blob, 'checkin.jpg');
        formData.append('latitude', locationData.lat);
        formData.append('longitude', locationData.lng);
        
        $.ajax({
            url: '/api/attendance/checkin.php',
            type: 'POST',
            data: formData,
            processData: false,
            contentType: false,
            success: function(response) {
                alert('Check-in successful!');
                location.reload();
            },
            error: function(xhr) {
                alert(xhr.responseJSON?.message || 'Check-in failed');
                btn.html('<i class="fas fa-fingerprint me-2"></i>Check In').prop('disabled', false);
            }
        });
    });
    
    function b64toBlob(b64Data, contentType) {
        const sliceSize = 512;
        const byteCharacters = atob(b64Data);
        const byteArrays = [];
        
        for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
            const slice = byteCharacters.slice(offset, offset + sliceSize);
            const byteNumbers = new Array(slice.length);
            
            for (let i = 0; i < slice.length; i++) {
                byteNumbers[i] = slice.charCodeAt(i);
            }
            
            const byteArray = new Uint8Array(byteNumbers);
            byteArrays.push(byteArray);
        }
        
        return new Blob(byteArrays, { type: contentType });
    }
});
</script>

7 SaaS Multi-Tenant Architecture

🏒 Tenants Table

-- Tenants table for SaaS
CREATE TABLE tenants (
    id INT PRIMARY KEY AUTO_INCREMENT,
    company_name VARCHAR(255) NOT NULL,
    subdomain VARCHAR(100) UNIQUE NOT NULL,
    custom_domain VARCHAR(255) UNIQUE,
    email VARCHAR(255) NOT NULL,
    plan ENUM('basic', 'pro', 'enterprise') DEFAULT 'basic',
    status ENUM('active', 'suspended') DEFAULT 'active',
    settings JSON,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Add tenant_id to all tables
ALTER TABLE users ADD COLUMN tenant_id INT NOT NULL;
ALTER TABLE departments ADD COLUMN tenant_id INT NOT NULL;
ALTER TABLE attendance ADD COLUMN tenant_id INT NOT NULL;

-- Subscription plans
CREATE TABLE subscriptions (
    id INT PRIMARY KEY AUTO_INCREMENT,
    tenant_id INT NOT NULL,
    plan_id VARCHAR(50) NOT NULL,
    start_date DATE NOT NULL,
    end_date DATE NOT NULL,
    amount DECIMAL(10,2),
    status ENUM('active', 'expired') DEFAULT 'active',
    FOREIGN KEY (tenant_id) REFERENCES tenants(id)
);

πŸ” Laravel Tenant Middleware

<?php
// app/Http/Middleware/TenantMiddleware.php

namespace App\Http\Middleware;

use Closure;
use App\Models\Tenant;

class TenantMiddleware
{
    public function handle($request, Closure $next)
    {
        $host = $request->getHost();
        $subdomain = explode('.', $host)[0];
        
        // Skip main domain
        if (in_array($subdomain, ['app', 'www', 'demo'])) {
            return $next($request);
        }
        
        // Find tenant
        $tenant = Tenant::where('subdomain', $subdomain)
            ->where('status', 'active')
            ->first();
        
        if (!$tenant) {
            abort(404, 'Tenant not found');
        }
        
        // Check subscription
        if (!$tenant->hasActiveSubscription()) {
            return redirect()->route('subscription.expired');
        }
        
        // Bind tenant to request
        $request->merge(['tenant' => $tenant]);
        app()->instance('current_tenant', $tenant);
        
        return $next($request);
    }
}

πŸ“Š Plan Features Configuration

<?php
// config/plans.php

return [
    'basic' => [
        'name' => 'Basic',
        'price' => 29,
        'features' => [
            'max_employees' => 50,
            'attendance_tracking' => true,
            'leave_management' => true,
            'reports' => 'basic',
            'api_access' => false,
            'support' => 'email'
        ]
    ],
    'pro' => [
        'name' => 'Professional',
        'price' => 79,
        'features' => [
            'max_employees' => 500,
            'attendance_tracking' => true,
            'leave_management' => true,
            'reports' => 'advanced',
            'api_access' => true,
            'biometric_integration' => true,
            'support' => 'priority'
        ]
    ],
    'enterprise' => [
        'name' => 'Enterprise',
        'price' => 199,
        'features' => [
            'max_employees' => 'unlimited',
            'attendance_tracking' => true,
            'leave_management' => true,
            'reports' => 'custom',
            'api_access' => true,
            'biometric_integration' => true,
            'dedicated_support' => true,
            'custom_branding' => true
        ]
    ]
];
Advertisement
[Responsive Large Rectangle Ad - 336x280]

8 Toast Notifications System

πŸ”” Vanilla JavaScript Toast

// toast.js
class Toast {
    constructor() {
        this.container = document.createElement('div');
        this.container.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 9999;
        `;
        document.body.appendChild(this.container);
    }

    show(message, type = 'info', duration = 3000) {
        const toast = document.createElement('div');
        
        const colors = {
            success: '#10b981',
            error: '#ef4444',
            warning: '#f59e0b',
            info: '#3b82f6'
        };
        
        const icons = {
            success: 'βœ“',
            error: 'βœ—',
            warning: '⚠',
            info: 'β„Ή'
        };
        
        toast.style.cssText = `
            background: white;
            border-left: 4px solid ${colors[type]};
            border-radius: 8px;
            padding: 12px 20px;
            margin-bottom: 10px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            display: flex;
            align-items: center;
            gap: 10px;
            min-width: 300px;
            animation: slideIn 0.3s ease;
        `;
        
        toast.innerHTML = `
            <span style="font-weight: bold; color: ${colors[type]}">
                ${icons[type]}
            </span>
            <span style="flex: 1">${message}</span>
            <button onclick="this.parentElement.remove()" 
                    style="background: none; border: none; cursor: pointer; font-size: 18px">
                Γ—
            </button>
        `;
        
        this.container.appendChild(toast);
        
        setTimeout(() => {
            toast.style.animation = 'slideOut 0.3s ease';
            setTimeout(() => toast.remove(), 300);
        }, duration);
    }
}

// Add animations
const style = document.createElement('style');
style.textContent = `
    @keyframes slideIn {
        from { transform: translateX(100%); opacity: 0; }
        to { transform: translateX(0); opacity: 1; }
    }
    @keyframes slideOut {
        from { transform: translateX(0); opacity: 1; }
        to { transform: translateX(100%); opacity: 0; }
    }
`;
document.head.appendChild(style);

const toast = new Toast();

βš›οΈ React Toast Hook

// hooks/useToast.js
import { useState, useCallback } from 'react';

export const useToast = () => {
    const [toasts, setToasts] = useState([]);

    const addToast = useCallback((message, type = 'info', duration = 3000) => {
        const id = Date.now();
        
        setToasts(prev => [...prev, { id, message, type }]);
        
        setTimeout(() => {
            setToasts(prev => prev.filter(t => t.id !== id));
        }, duration);
    }, []);

    const removeToast = useCallback((id) => {
        setToasts(prev => prev.filter(t => t.id !== id));
    }, []);

    return { toasts, addToast, removeToast };
};

// components/ToastContainer.jsx
import React from 'react';
import { useToast } from '../hooks/useToast';

const ToastContainer = () => {
    const { toasts, removeToast } = useToast();

    const getStyles = (type) => {
        const colors = {
            success: '#10b981',
            error: '#ef4444',
            warning: '#f59e0b',
            info: '#3b82f6'
        };
        
        return {
            borderLeft: `4px solid ${colors[type]}`,
            color: colors[type]
        };
    };

    return (
        <div style={{
            position: 'fixed',
            top: 20,
            right: 20,
            zIndex: 9999
        }}>
            {toasts.map(toast => (
                <div
                    key={toast.id}
                    style={{
                        ...getStyles(toast.type),
                        background: 'white',
                        borderRadius: 8,
                        padding: '12px 20px',
                        marginBottom: 10,
                        boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
                        display: 'flex',
                        alignItems: 'center',
                        gap: 10,
                        minWidth: 300,
                        animation: 'slideIn 0.3s ease'
                    }}
                >
                    <span style={{ fontWeight: 'bold' }}>
                        {toast.type === 'success' && 'βœ“'}
                        {toast.type === 'error' && 'βœ—'}
                        {toast.type === 'warning' && '⚠'}
                        {toast.type === 'info' && 'β„Ή'}
                    </span>
                    <span style={{ flex: 1 }}>{toast.message}</span>
                    <button
                        onClick={() => removeToast(toast.id)}
                        style={{
                            background: 'none',
                            border: 'none',
                            cursor: 'pointer',
                            fontSize: 18
                        }}
                    >
                        Γ—
                    </button>
                </div>
            ))}
        </div>
    );
};

export default ToastContainer;

Live Demo:

Click buttons to see how toasts would appear in your app

Advertisement
[Responsive Leaderboard Ad - 728x90]

πŸ“Œ Quick Reference: Tech Stack Comparison

🐘

PHP Laravel

Best for rapid dev

βš›οΈ

React

Real-time UI

πŸ”·

.NET Core

Enterprise ready

πŸ”„

jQuery

Quick prototypes

Frequently Asked Questions

Which tech stack should I choose?

For startups: Laravel + jQuery. For real-time apps: React + Node.js. For enterprise: .NET Core + React. Choose based on your team's expertise.

How to handle biometric integration?

Use device SDKs (ZKTeco, Suprema) or implement mobile app with fingerprint/face recognition. Most devices provide REST APIs.

How to implement geo-fencing?

Store office coordinates, get user location via browser/mobile GPS, calculate distance using Haversine formula, validate within allowed radius (e.g., 100m).

How to handle leave management?

Define leave types, set quotas per employee, implement approval workflow (employee β†’ manager β†’ HR), auto-calculate balances, integrate with attendance.

How to generate payroll reports?

Calculate: Basic Salary + Allowances - Deductions = Net Pay. Track present days, late days, overtime hours, leave without pay, and apply company policies.

How to make it SaaS ready?

Implement multi-tenancy with tenant_id in all tables, subdomain routing, plan-based feature restrictions, subscription management, and isolated data per client.

Share this guide with your developer friends: