Commit 21957651 by Shaganaz

Created task list page, implemented email notifications and file uploading options

parent 86331fe6
<?php
namespace App\Http\Controllers;
use App\Mail\TaskAssignedMail;
use App\Models\Task;
use App\Models\Project;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
class TaskController extends Controller
{
public function index()
{
$tasks = Task::with(['project','creator','files'])->get();
$projects = Project::all();
$users = User::all();
return view('admin.tasks', compact('tasks','projects', 'users'));
}
public function store(Request $request)
{
$validated = $request->validate([
'project_id' => 'required|exists:projects,id',
'title' => 'required|string|max:255|unique:tasks,title',
'description' => 'required|string',
'status' => 'required|string|max:255',
'priority' => 'required|string|max:255',
'due_date' => 'required|date|after_or_equal:today',
'created_by' => 'required|exists:users,id',
]);
$task=Task::create($validated);
return redirect()->route('admin.tasks')->with('success', 'Task created successfully.');
}
public function assignUsers(Request $request, Task $task)
{
$validated = $request->validate([
'assigned_users' => 'required|array',
'assigned_users.*' => 'exists:users,id',
]);
$task->assignedUsers()->syncWithPivotValues($validated['assigned_users'], [
'assigned_by' => auth()->id(),
'created_at' => now(),
'updated_at' => now()
]);
foreach ($validated['assigned_users'] as $userId) {
$user = User::find($userId);
Mail::to($user->email)->send(new TaskAssignedMail($task, $user));
}
return redirect()->route('admin.tasks')->with('success', 'Users assigned to task successfully.');
}
public function taskList()
{
$tasks = Task::with(['project', 'creator', 'assignedUsers', 'files.uploader'])->get();
return view('admin.task-list', compact('tasks'));
}
}
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use App\Models\File;
use App\Models\Task;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function dashboard()
{
return view('user.dashboard');
}
public function assignedTasks()
{
$user = User::find(auth()->id());
$tasks = $user->assignedTasks()->with(['project', 'creator'])->get();
return view('user.assigned-task', compact('tasks'));
}
public function uploadFile(Request $request, Task $task)
{
// dd($request->task_file);
$request->validate([
'task_file' => 'required|file|mimes:jpg,jpeg,png,pdf,doc,docx,xlsx,xls,ppt,pptx,txt|max:10240',
]);
$uploadedFile = $request->file('task_file');
$path = $uploadedFile->store('task_uploads', 'public');
File::create([
'task_id' => $task->id,
'file_path' => $path,
'original_name' => $uploadedFile->getClientOriginalName(),
'uploaded_by' => auth()->id(),
]);
return back()->with('success', 'File uploaded successfully.');
}
}
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use App\Models\Task;
use App\Models\User;
class TaskAssignedMail extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*/
public function __construct(public Task $task, public User $user)
{
//
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'You have been assigned to a new task',
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
view: 'emails.task-assigned',
with:[
'task'=>$this->task,
'user'=>$this->user
]
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}
......@@ -8,4 +8,15 @@
class File extends Model
{
use HasFactory;
protected $fillable=['task_id', 'file_path', 'original_name', 'uploaded_by'];
public function task()
{
return $this->belongsTo(Task::class);
}
public function uploader()
{
return $this->belongsTo(User::class, 'uploaded_by');
}
}
......@@ -14,4 +14,8 @@ public function creator()
{
return $this->belongsTo(User::class, 'created_by');
}
public function tasks() {
return $this->hasMany(Task::class);
}
}
......@@ -8,4 +8,22 @@
class Task extends Model
{
use HasFactory;
protected $fillable = ['project_id', 'title', 'description', 'status', 'priority', 'due_date', 'created_by'];
public function project() {
return $this->belongsTo(Project::class);
}
public function creator() {
return $this->belongsTo(User::class, 'created_by');
}
public function assignedUsers(){
return $this->belongsToMany(User::class)->withPivot('assigned_by')->withTimestamps();
}
public function files()
{
return $this->hasMany(File::class);
}
}
......@@ -43,6 +43,14 @@ public function projects()
{
return $this->hasMany(Project::class, 'created_by');
}
public function tasks()
{
return $this->hasMany(Project::class, 'created_by');
}
public function assignedTasks()
{
return $this->belongsToMany(Task::class)->withPivot('assigned_by')->withTimestamps();
}
/**
* The attributes that should be cast.
*
......
.admin-page {
padding: 60px 20px;
max-width: 1200px;
......@@ -9,6 +10,17 @@
margin-top: 20px;
}
.logout-button {
float: right;
background-color: #4f46e5;
color: white;
padding: 12px 24px;
border-radius: 8px;
text-decoration: none;
transition: background-color 0.2s;
font-weight: 500;
}
a.user-button {
display: inline-block;
background-color: #4f46e5;
......@@ -37,4 +49,19 @@ a.project-button {
a.project-button:hover {
background-color: #4338ca;
}
\ No newline at end of file
}
a.task-button {
display: inline-block;
background-color: #4f46e5;
color: white;
padding: 12px 24px;
border-radius: 8px;
text-decoration: none;
transition: background-color 0.2s;
font-weight: 500;
}
a.task-button:hover {
background-color: #4338ca;
}
.tasks-container {
max-width: 1200px;
margin: 40px auto;
padding: 20px;
background: #f9f9f9;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
font-family: Arial, sans-serif;
}
.tasks-container h1 {
font-size: 28px;
margin-bottom: 20px;
color: #333;
text-align: center;
}
.task-table {
width: 100%;
border-collapse: collapse;
}
.task-table th,
.task-table td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
vertical-align: top;
}
.task-table th {
background-color: #004085;
color: #fff;
font-weight: 600;
}
.task-table tr:nth-child(even) {
background-color: #f2f2f2;
}
input[type="file"] {
padding: 6px;
border-radius: 5px;
background-color: #fff;
border: 1px solid #ccc;
margin-right: 10px;
}
button {
background-color: #28a745;
color: #fff;
border: none;
padding: 8px 14px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
}
button:hover {
background-color: #218838;
}
form {
margin-top: 10px;
}
.task-list {
max-width: 900px;
margin: 30px auto;
padding: 20px;
background-color: #f9fafb;
border-radius: 8px;
box-shadow: 0 0 8px rgba(0,0,0,0.05);
font-family: 'Segoe UI', sans-serif;
}
.task-list h1 {
text-align: center;
margin-bottom: 30px;
color: #2c3e50;
}
.task-item {
background-color: #ffffff;
border-left: 5px solid #4f46e5;
padding: 20px;
margin-bottom: 20px;
border-radius: 6px;
box-shadow: 0 0 4px rgba(0,0,0,0.05);
}
.task-item h2 {
margin-top: 0;
font-size: 22px;
color: #333;
}
.task-item p {
margin: 8px 0;
font-size: 15px;
color: #444;
}
.task-item strong {
color: #1f2937;
}
.task-item ul {
padding-left: 18px;
list-style-type: disc;
}
.task-item ul li {
margin: 5px 0;
font-size: 14px;
}
a {
color: #2563eb;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.tasks-container {
max-width: 1000px;
margin: 2rem auto;
padding: 1rem 2rem;
font-family: Arial, sans-serif;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgb(0 0 0 / 0.1);
}
h1 {
font-size: 1.8rem;
margin-bottom: 1rem;
color: #333;
}
.action-bar {
margin-bottom: 1rem;
display: flex;
justify-content: flex-start;
}
.create-button {
background-color: #2563eb; /* Blue */
color: white;
font-weight: 600;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.create-button:hover {
background-color: #1e40af;
}
.form-section {
margin-bottom: 2rem;
border: 1px solid #ddd;
padding: 1rem;
border-radius: 6px;
background-color: #fafafa;
}
.hidden {
display: none;
}
.form-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1rem 2rem;
align-items: center;
}
.form-group {
display: flex;
flex-direction: column;
}
.form-group label {
font-weight: 600;
margin-bottom: 0.4rem;
color: #555;
}
.form-group input,
.form-group textarea,
.form-group select {
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1rem;
transition: border-color 0.2s ease;
}
.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {
border-color: #2563eb;
outline: none;
box-shadow: 0 0 0 2px rgb(37 99 235 / 0.3);
}
.form-group.full-width {
grid-column: 1 / -1;
}
.submit-button {
background-color: #10b981; /* Green */
color: white;
font-weight: 700;
border: none;
padding: 0.6rem 1.5rem;
border-radius: 4px;
cursor: pointer;
margin-top: 1rem;
transition: background-color 0.3s ease;
}
.submit-button:hover {
background-color: #047857;
}
.task-table {
width: 100%;
border-collapse: collapse;
box-shadow: 0 1px 3px rgb(0 0 0 / 0.1);
}
.task-table thead {
background-color: #f3f4f6;
border-bottom: 2px solid #e5e7eb;
}
.task-table th,
.task-table td {
text-align: left;
padding: 0.75rem 1rem;
border-bottom: 1px solid #e5e7eb;
color: #374151;
font-size: 0.95rem;
}
.task-table tbody tr:hover {
background-color: #f9fafb;
cursor: default;
}
.user-dashboard {
max-width: 900px;
margin: 2rem auto;
padding: 1.5rem 2rem;
font-family: Arial, sans-serif;
background-color: #f9f9f9;
border-radius: 8px;
border: 1px solid #ddd;
}
.page-title {
font-size: 24px;
font-weight: bold;
margin-bottom: 1.5rem;
color: #333;
}
.logout-button {
float: right;
background-color: #4f46e5;
color: white;
padding: 12px 24px;
border-radius: 8px;
text-decoration: none;
transition: background-color 0.2s;
font-weight: 500;
}
.dashboard-message {
font-size: 16px;
margin-bottom: 2rem;
color: #444;
}
.dashboard-actions {
margin-top: 1rem;
}
.dashboard-button {
display: inline-block;
padding: 0.6rem 1.2rem;
background-color: #2563eb;
color: white;
text-decoration: none;
font-weight: 600;
border-radius: 5px;
transition: background-color 0.3s ease;
}
.dashboard-button:hover {
background-color: #1e40af;
}
......@@ -2,6 +2,14 @@ import './bootstrap';
import Alpine from 'alpinejs';
import '../css/app.css';
import '../css/admindash.css';
import '../css/listuser.css';
import '../css/projectlist.css';
import '../css/tasklist.css';
import '../css/userdash.css';
import '../css/assigned-task.css';
import '../css/task-list.css';
window.Alpine = Alpine;
Alpine.start();
<x-app-layout>
<x-slot name="header">Welcome Admin</x-slot>
<x-slot name="header">Welcome Admin
<div class="logout-button">
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit">Logout</button>
</div>
</x-slot>
<div class="admin-page">
<div class="admin-actions">
......@@ -9,6 +16,8 @@
<a href="{{ route('admin.projects') }}" class="project-button">
Create New Project
</a>
</div>
<a href="{{route('admin.tasks')}}" class="task-button" >
Create New Task</a>
<a href="{{ route('admin.tasks.list') }}" class="task-button">View Task List</a>
</div>
</x-app-layout>
<x-app-layout>
<x-slot name="header">Task Progress List</x-slot>
<div class="task-list">
<h1>Task List</h1>
@forelse($tasks as $task)
<div class="task-item">
<h2>{{ $task->title }}</h2>
<p><strong>Project:</strong> {{ $task->project->name }}</p>
<p><strong>Status:</strong> {{ $task->status }}</p>
<p><strong>Due Date:</strong> {{ $task->due_date }}</p>
<p><strong>Created By:</strong> {{ $task->creator->name ?? 'N/A' }}</p>
<p><strong>Assigned Users:</strong>
@foreach($task->assignedUsers as $user)
{{ $user->name }}@if (!$loop->last), @endif
@endforeach
</p>
<h4>Uploaded Files</h4>
@if ($task->files->isEmpty())
<p>No files uploaded.</p>
@else
<ul>
@foreach($task->files as $file)
<li>
{{ $file->original_name }} —
<a href="{{ asset('storage/' . $file->file_path) }}" target="_blank">View File</a>
(Uploaded by: {{ $file->uploader->name ?? 'N/A'}})
</li>
@endforeach
</ul>
@endif
<hr>
</div>
@empty
<p>No tasks found.</p>
@endforelse
</div>
</x-app-layout>
<x-app-layout>
<x-slot name="header">Task Management Page</x-slot>
<div class="tasks-container">
<h1>Task List</h1>
<div class="action-bar">
<button onclick="document.getElementById('createForm').classList.toggle('hidden')" class="create-button">
+ Create New Task
</button>
</div>
<div id="createForm" class="form-section hidden">
<form action="{{ route('admin.tasks.store') }}" method="POST">
@csrf
<div class="form-grid">
<div class="form-group">
<label>Project</label>
<select name="project_id" required>
@foreach($projects as $project)
<option value="{{ $project->id }}">{{ $project->name }}</option>
@endforeach
</select>
</div>
<div class="form-group">
<label>Task Title</label>
<input type="text" name="title" required>
</div>
<div class="form-group full-width">
<label>Task Description</label>
<textarea name="description" rows="3" required></textarea>
</div>
<div class="form-group">
<label>Status</label>
<input type="text" name="status" placeholder="e.g.pending, in-progress,completed" required>
</div>
<div class="form-group">
<label>Priority</label>
<input type="text" name="priority" placeholder="e.g. low, medium, high" required>
</div>
<div class="form-group">
<label>Due Date</label>
<input type="datetime-local" name="due_date" required>
</div>
<div class="form-group">
<label>Created By</label>
<select name="created_by" required>
@foreach($users as $user)
<option value="{{ $user->id }}">{{ $user->name }}</option>
@endforeach
</select>
</div>
</div>
<button type="submit" class="submit-button">Create Task</button>
</form>
</div>
<table class="task-table">
<thead>
<tr>
<th>ID</th>
<th>Project</th>
<th>Task Title</th>
<th>Task Description</th>
<th>Status</th>
<th>Priority</th>
<th>Due Date</th>
<th>Created By</th>
<th>Assign Task To The User</th>
</tr>
</thead>
<tbody>
@foreach ($tasks as $task)
<tr>
<td>{{ $task->id }}</td>
<td>{{ $task->project->name }}</td>
<td>{{ $task->title }}</td>
<td>{{ Str::limit($task->description, 50) }}</td>
<td>{{ $task->status }}</td>
<td>{{ $task->priority }}</td>
<td>{{ $task->due_date }}</td>
<td>{{ $task->creator->name }}</td>
<td>
@if ($task->assignedUsers->isNotEmpty())
@foreach ($task->assignedUsers as $user)
<span>{{ $user->name }}</span><br>
@endforeach
@else
<form action="{{ route('admin.tasks.assign', $task->id) }}" method="POST">
@csrf
<select name="assigned_users[]" multiple required>
@foreach($users as $user)
<option value="{{ $user->id }}">{{ $user->name }}</option>
@endforeach
</select>
<button type="submit" class="submit-button">Assign Task</button>
</form>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</x-app-layout>
<h2>Hello {{ $user->name }},</h2>
<p>You’ve been assigned a new task:</p>
<ul>
<li><strong>Title:</strong> {{ $task->title }}</li>
<li><strong>Description:</strong> {{ $task->description }}</li>
<li><strong>Due Date:</strong> {{ $task->due_date }}</li>
<li><strong>Status:</strong> {{ $task->status }}</li>
</ul>
<p>Please log in to your dashboard to view more details.</p>
<p>Thanks,<br>Task Manager</p>
......@@ -12,7 +12,7 @@
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
<!-- Scripts -->
@vite(['resources/css/app.css', 'resources/js/app.js','resources/css/admindash.css','resources/css/listuser.css','resources/css/projectlist.css'])
@vite(['resources/css/app.css', 'resources/js/app.js','resources/css/admindash.css','resources/css/listuser.css','resources/css/projectlist.css','resources/css/tasklist.css','resources/css/userdash.css','resources/css/assigned-task.css','resources/css/task-list.css'])
</head>
<body class="font-sans antialiased">
<div class="min-h-screen bg-gray-100 dark:bg-gray-900">
......
<x-app-layout>
<x-slot name="header">My Assigned Tasks</x-slot>
<div class="tasks-container">
<h1>Tasks Assigned to Me</h1>
<table class="task-table">
<thead>
<tr>
<th>Project</th>
<th>Title</th>
<th>Description</th>
<th>Status</th>
<th>Priority</th>
<th>Due Date</th>
<th>Created By</th>
</tr>
</thead>
<tbody>
@forelse ($tasks as $task)
<tr>
<td>{{ $task->project->name }}</td>
<td>{{ $task->title }}</td>
<td>{{ Str::limit($task->description, 50) }}</td>
<td>{{ $task->status }}</td>
<td>{{ $task->priority }}</td>
<td>{{ $task->due_date }}</td>
<td>{{ $task->creator->name }}</td>
</tr>
<tr>
<td colspan="7">
<form action="{{ route('user.upload', $task->id) }}" method="POST" enctype="multipart/form-data">
@csrf
<input type="file" name="task_file" required>
<button type="submit">Upload File</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="7">No tasks assigned.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</x-app-layout>
<x-app-layout>
<x-slot name="header">Welcome User</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto">You are logged in as <strong>User</strong>.</div>
<x-slot name="header">Welcome, {{Auth::user()->name}}
<div class="logout-button">
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit">Logout</button>
</div>
</x-slot>
<div class="user-dashboard">
<div class="dashboard-message">
You are logged in as <strong>User</strong>.
</div>
<div class="dashboard-actions">
<a href="{{ route('user.tasks') }}" class="dashboard-button">My Assigned Tasks</a>
</div>
</div>
</x-app-layout>
......@@ -4,6 +4,8 @@
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\ProjectController;
use App\Http\Controllers\TaskController;
use App\Http\Controllers\UserController;
/*
|--------------------------------------------------------------------------
| Web Routes
......@@ -31,12 +33,17 @@
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
Route::get('/user/dashboard', function() {
return view('user.dashboard');
})->name('user.dashboard');
Route::middleware('auth')->prefix('user')->group(function () {
Route::get('/dashboard', [UserController::class, 'dashboard'])->name('user.dashboard');
Route::get('/tasks', [UserController::class, 'assignedTasks'])->name('user.tasks');
Route::post('/tasks/{task}/upload', [UserController::class, 'uploadFile'])->name('user.upload');
});
});
Route::middleware('auth')->prefix('admin')->group(function () {
Route::get('/dashboard', function() {
if(Auth::user()->role->name !== 'admin') {
......@@ -49,6 +56,11 @@
Route::post('/users', [AdminUserController::class, 'store'])->name('admin.users.store');
Route::get('projects', [ProjectController::class, 'index'])->name('admin.projects');
Route::post('projects', [ProjectController::class, 'store'])->name('admin.projects.store');
Route::get('tasks', [TaskController::class, 'index'])->name('admin.tasks');
Route::post('tasks', [TaskController::class, 'store'])->name('admin.tasks.store');
Route::post('tasks/{task}/assign', [TaskController::class, 'assignUsers'])->name('admin.tasks.assign');
Route::get('/tasks/list', [TaskController::class, 'taskList'])->name('admin.tasks.list');
});
require __DIR__.'/auth.php';
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment