Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions app/Http/Controllers/TeacherController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace App\Http\Controllers;

use App\Models\Team;
use Illuminate\Support\Facades\Auth;
use Inertia\Inertia;
use Inertia\Response as InertiaResponse;

class TeacherController extends Controller
{
public function index(): InertiaResponse
{
$teacherId = Auth::id();

$teams = Team::where('advisor_id', $teacherId)
->with([
'projects:id,team_id,title,proposal_path,poster_path,code_link,created_at,updated_at',
'teammembers.user:id,name,email'
])
->select('id', 'name', 'advisor_id')
->get();

return Inertia::render('Teacher/Index', [
'teams' => $teams,
]);
}
}
8 changes: 4 additions & 4 deletions app/Http/Controllers/WorksController.php
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
<?php
namespace App\Http\Controllers;

use App\Models\Project;
use App\Models\Past_projects;
use Illuminate\Http\Request;
use Inertia\Inertia;

class WorksController extends Controller
{
public function index(Request $request)
{
$query = Project::query();
$query = Past_projects::query();

if ($search = $request->input('search')) {
$query->where('title', 'like', "%{$search}%");
}

$works = $query->orderBy('id', 'desc')->paginate(10);
$pastWorks = $query->orderBy('id', 'desc')->paginate(10);

return Inertia::render('Works/Index', [
'works' => $works,
'pastWorks' => $pastWorks,
'filters' => $request->only('search'),
]);
}
Expand Down
23 changes: 23 additions & 0 deletions app/Models/Past_projects.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Past_projects extends Model
{
use HasFactory;

protected $fillable = [
'team_name',
'members',
'title',
'description',
'poster_path',
'code_link',
'year',
'prize',
'judge_snapshot',
];
}
16 changes: 13 additions & 3 deletions app/Models/Team.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,17 @@

class Team extends Model
{
protected $fillable = [
'id','name','advisor_id','award','created_at','updated_at'
];
protected $fillable = [
'id', 'name', 'advisor_id', 'award', 'created_at', 'updated_at'
];

public function projects()
{
return $this->hasMany(Project::class, 'team_id', 'id');
}

public function teamMembers()
{
return $this->hasMany(Team_members::class, 'team_id', 'id');
}
}
18 changes: 18 additions & 0 deletions app/Models/Team_members.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Team_members extends Model
{

protected $fillable = [
'id', 'team_id', 'user_id', 'created_at', 'updated_at'
];

public function user()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
}
13 changes: 10 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"vite": "^6.2.4"
},
"dependencies": {
"@heroicons/react": "^2.2.0",
"animate.css": "^4.1.1",
"framer-motion": "^12.15.0"
}
Expand Down
63 changes: 63 additions & 0 deletions resources/js/Pages/Teacher/Index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react';
import Header from "@/Components/Header";
import Footer from "@/Components/Footer";
import SidePanel from "@/Components/SidePanel";

export default function Index({ auth, teams = [] }) {
return (
<div className="bg-gray-50 text-gray-800 dark:bg-black dark:text-white min-h-screen relative">
<Header auth={auth} />
<SidePanel auth={auth} />
<main className="max-w-6xl mx-auto px-4 py-8 space-y-12">
<h1 className="text-2xl font-bold mb-8">我的指導隊伍與學生作品</h1>
{teams.length === 0 ? (
<p>目前沒有您的指導隊伍。</p>
) : (
teams.map(team => (
<div
key={team.id}
className="mb-8 p-6 border border-gray-300 rounded-lg bg-white dark:bg-zinc-700"
>
<h2 className="text-lg font-semibold mb-2">隊伍:{team.name}(ID: {team.id})</h2>
<h3 className="font-medium">學生名單:</h3>
<ul className="mb-2 list-disc list-inside">
{team.teammembers && team.teammembers.length > 0 ? (
team.teammembers.map(member => (
<li key={member.id}>
學號:{member.user_id}
{member.user ? `,姓名:${member.user.name}(${member.user.email})` : ',未知學生'}
</li>
))
) : (
<li>尚無學生資料</li>
)}
</ul>
<h3 className="font-medium">作品列表:</h3>
<ul className="list-disc list-inside">
{team.projects && team.projects.length > 0 ? (
team.projects.map(project => (
<li key={project.id}>
{project.title}
{project.proposal_path && (
<> | 企劃書:<a href={project.proposal_path} target="_blank" rel="noopener noreferrer" className="text-blue-600 underline">下載</a></>
)}
{project.poster_path && (
<> | 海報:<a href={project.poster_path} target="_blank" rel="noopener noreferrer" className="text-blue-600 underline">下載</a></>
)}
{project.code_link && (
<> | 程式碼:<a href={project.code_link} target="_blank" rel="noopener noreferrer" className="text-blue-600 underline">檢視</a></>
)}
</li>
))
) : (
<li>尚無作品</li>
)}
</ul>
</div>
))
)}
</main>
<Footer />
</div>
);
}
28 changes: 27 additions & 1 deletion resources/js/Pages/Welcome.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,33 @@ export default function Welcome({ auth, notices }) {
</p>
</Link>
)}

{auth?.user?.role === "teacher" && (
<Link
href="/teacher"
className="bg-indigo-100 p-4 rounded shadow hover:bg-indigo-200 transition-all duration-300 transform hover:animate-wiggle text-center flex flex-col items-center justify-center h-28"
>
<h2 className="font-semibold text-lg">
老師專區
</h2>
<p className="text-sm text-gray-600">
查看指導隊伍作品
</p>
</Link>
)}
{auth?.user?.role === "teacher" && (
<Link
href="/works"
className="bg-indigo-100 p-4 rounded shadow hover:bg-indigo-200 transition-all duration-300 transform hover:animate-wiggle text-center flex flex-col items-center justify-center h-28"
>
<h2 className="font-semibold text-lg">
老師專區
</h2>
<p className="text-sm text-gray-600">
查看歷屆作品
</p>
</Link>
)}

{auth?.user?.role === "admin" && (
<Link href={route("admin.dashboard")}>
<motion.div
Expand Down
25 changes: 10 additions & 15 deletions resources/js/Pages/Works/Index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,26 @@ import Header from "@/Components/Header";
import Footer from "@/Components/Footer";
import SidePanel from "@/Components/SidePanel";

export default function WorksIndex({ works, filters, auth }) {
export default function PastWorksIndex({ pastWorks, filters, auth }) {
const [search, setSearch] = useState(filters.search || '');

const handleSearch = (e) => {
e.preventDefault();
router.get(route('works.index'), { search });
router.get(route('past-works.index'), { search });
};

return (
<div className="bg-gray-50 text-gray-800 dark:bg-black dark:text-white min-h-screen relative">
<Header auth={auth} />
<SidePanel auth={auth} />
<main className="max-w-6xl mx-auto px-4 py-8 space-y-12">
<h1 className="text-2xl font-bold mb-6 text-center">參考過去作品</h1>
<form onSubmit={handleSearch} className="max-w-md mx-auto mb-8 flex space-x-2">
<input
type="text"
value={search}
onChange={e => setSearch(e.target.value)}
placeholder="搜尋標題"
placeholder="搜尋作品標題"
className="flex-1 border rounded px-3 py-2 dark:text-black"
/>
<button
Expand All @@ -38,21 +39,20 @@ export default function WorksIndex({ works, filters, auth }) {
<thead>
<tr className="bg-gray-100 dark:bg-zinc-800">
<th className="px-4 py-3 text-left font-semibold text-gray-700 dark:text-gray-200 w-16 rounded-tl-lg">ID</th>
<th className="px-4 py-3 text-left font-semibold text-gray-700 dark:text-gray-200 w-24">隊伍ID</th>
<th className="px-4 py-3 text-left font-semibold text-gray-700 dark:text-gray-200 w-48">標題</th>
<th className="px-4 py-3 text-center font-semibold text-gray-700 dark:text-gray-200 w-24">企劃書</th>
<th className="px-4 py-3 text-left font-semibold text-gray-700 dark:text-gray-200 w-24">隊伍名稱</th>
<th className="px-4 py-3 text-left font-semibold text-gray-700 dark:text-gray-200 w-48">作品標題</th>
<th className="px-4 py-3 text-center font-semibold text-gray-700 dark:text-gray-200 w-24">海報</th>
<th className="px-4 py-3 text-center font-semibold text-gray-700 dark:text-gray-200 w-32">程式碼連結</th>
<th className="px-4 py-3 text-center font-semibold text-gray-700 dark:text-gray-200 w-40 rounded-tr-lg">建立時間</th>
</tr>
</thead>
<tbody>
{works.data.length === 0 && (
{pastWorks.data.length === 0 && (
<tr>
<td colSpan="7" className="text-center py-6 text-gray-500 dark:text-gray-300">查無資料</td>
<td colSpan="6" className="text-center py-6 text-gray-500 dark:text-gray-300">查無資料</td>
</tr>
)}
{works.data.map((work, idx) => (
{pastWorks.data.map((work, idx) => (
<tr
key={work.id}
className={`
Expand All @@ -61,13 +61,8 @@ export default function WorksIndex({ works, filters, auth }) {
`}
>
<td className="px-4 py-3 border-b border-gray-200 dark:border-zinc-600 text-left">{work.id}</td>
<td className="px-4 py-3 border-b border-gray-200 dark:border-zinc-600 text-left">{work.team_id}</td>
<td className="px-4 py-3 border-b border-gray-200 dark:border-zinc-600 text-left">{work.team_name}</td>
<td className="px-4 py-3 border-b border-gray-200 dark:border-zinc-600 text-left">{work.title}</td>
<td className="px-4 py-3 border-b border-gray-200 dark:border-zinc-600 text-center">
{work.proposal_path && (
<a href={work.proposal_path} target="_blank" rel="noopener noreferrer" className="text-blue-600 underline">下載</a>
)}
</td>
<td className="px-4 py-3 border-b border-gray-200 dark:border-zinc-600 text-center">
{work.poster_path && (
<a href={work.poster_path} target="_blank" rel="noopener noreferrer" className="text-blue-600 underline">下載</a>
Expand Down
14 changes: 6 additions & 8 deletions routes/web.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?php

use App\Http\Controllers\TeacherController;
use App\Http\Controllers\WorksController;
use App\Http\Controllers\JudgeController;
use App\Http\Controllers\ProfileController;
Expand All @@ -9,6 +9,7 @@
use Inertia\Inertia;
use App\Models\Notice;


Route::get('/', function () {
return Inertia::render('Welcome', [
'notices' => \App\Models\Notice::orderBy('created_at', 'desc')->paginate(5),
Expand All @@ -25,21 +26,16 @@
return Inertia::render('Dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');

Route::get('/judges', [JudgeController::class, 'index'])->name('judges');
Route::get('/judges/create', [JudgeController::class, 'create'])->name('judges.create');
Route::post('/scores', [JudgeController::class, 'store'])->name('scores.store');
Route::post('/edit', [JudgeController::class, 'edit'])->name('scores.edit');
Route::post('/judge', [JudgeController::class, 'apiStore']);
// 建議只保留 resource 路由
Route::resource('judges', JudgeController::class);

Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
Route::get('/works', [WorksController::class, 'index'])->name('works.index');
});

Route::get('/works', [WorksController::class, 'index'])->name('works.index');

Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/admin', function () {
return Inertia::render('Admin/Dashboard', [
Expand All @@ -53,6 +49,8 @@
Route::get('/notices/{notice}/edit', [NoticeController::class, 'edit'])->name('notices.edit');
Route::put('/notices/{notice}', [NoticeController::class, 'update'])->name('notices.update');
Route::delete('/notices/{notice}', [NoticeController::class, 'destroy'])->name('notices.destroy');

Route::resource('teacher', TeacherController::class);
});

require __DIR__ . '/auth.php';