feat: add in-place editing functionality for classes and instructors with dedicated API routes.

This commit is contained in:
kertenkerem
2026-01-08 02:03:26 +03:00
parent 091435c8b4
commit dbca26f3ad
6 changed files with 308 additions and 22 deletions

View File

@@ -1,9 +1,12 @@
'use client'; 'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import styles from './branches.module.css'; import styles from './branches.module.css';
export function BranchList({ branches }: { branches: any[] }) { export function BranchList({ branches }: { branches: any[] }) {
const router = useRouter(); const router = useRouter();
const [editingId, setEditingId] = useState<string | null>(null);
const [editForm, setEditForm] = useState<any>(null);
const handleDelete = async (id: string) => { const handleDelete = async (id: string) => {
if (!confirm('Silmek istediğinize emin misiniz?')) return; if (!confirm('Silmek istediğinize emin misiniz?')) return;
@@ -11,16 +14,91 @@ export function BranchList({ branches }: { branches: any[] }) {
router.refresh(); router.refresh();
}; };
const startEdit = (branch: any) => {
setEditingId(branch.id);
setEditForm({
name: branch.name,
phone: branch.phone || '',
address: branch.address || '',
hallCount: branch.hallCount?.toString() || '1'
});
};
const handleUpdate = async (e: React.FormEvent) => {
e.preventDefault();
const res = await fetch(`/api/branches/${editingId}`, {
method: 'PUT',
body: JSON.stringify(editForm),
headers: { 'Content-Type': 'application/json' }
});
if (res.ok) {
setEditingId(null);
router.refresh();
} else {
alert('Güncelleme başarısız oldu.');
}
};
return ( return (
<div className={styles.list}> <div className={styles.list}>
{branches.map(b => ( {branches.map(b => (
<div key={b.id} className={styles.card}> <div key={b.id} className={styles.card}>
<div> {editingId === b.id ? (
<h3>{b.name}</h3> <form onSubmit={handleUpdate} style={{ width: '100%', display: 'flex', gap: '1rem', flexWrap: 'wrap', alignItems: 'flex-end' }}>
<p>{b.phone ? `Tel: ${b.phone}` : 'Telefon yok'} | {b.address || 'Adres yok'}</p> <div className={styles.inputGroup}>
<small>Salon Sayısı: {b.hallCount || 1}</small> <label>Şube Adı</label>
</div> <input
<button onClick={() => handleDelete(b.id)} className={`${styles.btn} ${styles.btnDelete}`}>Sil</button> value={editForm.name}
onChange={e => setEditForm({ ...editForm, name: e.target.value })}
className={styles.input}
required
/>
</div>
<div className={styles.inputGroup}>
<label>Telefon</label>
<input
value={editForm.phone}
onChange={e => setEditForm({ ...editForm, phone: e.target.value })}
className={styles.input}
/>
</div>
<div className={styles.inputGroup}>
<label>Adres</label>
<input
value={editForm.address}
onChange={e => setEditForm({ ...editForm, address: e.target.value })}
className={styles.input}
/>
</div>
<div className={styles.inputGroup}>
<label>Salon Sayısı</label>
<input
type="number"
min="1"
value={editForm.hallCount}
onChange={e => setEditForm({ ...editForm, hallCount: e.target.value })}
className={styles.input}
/>
</div>
<div style={{ display: 'flex', gap: '0.5rem' }}>
<button type="submit" className={styles.btn} style={{ background: '#2e7d32' }}>Kaydet</button>
<button type="button" onClick={() => setEditingId(null)} className={styles.btn}>İptal</button>
</div>
</form>
) : (
<>
<div>
<h3>{b.name}</h3>
<p>{b.phone ? `Tel: ${b.phone}` : 'Telefon yok'} | {b.address || 'Adres yok'}</p>
<small>Salon Sayısı: {b.hallCount || 1}</small>
</div>
<div style={{ display: 'flex', gap: '0.5rem' }}>
<button onClick={() => startEdit(b)} className={styles.btn} style={{ background: '#ffa000' }}>Düzenle</button>
<button onClick={() => handleDelete(b.id)} className={`${styles.btn} ${styles.btnDelete}`}>Sil</button>
</div>
</>
)}
</div> </div>
))} ))}
</div> </div>

View File

@@ -1,9 +1,20 @@
'use client'; 'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import styles from '../branches/branches.module.css'; import styles from '../branches/branches.module.css';
export function ClassList({ classes }: { classes: any[] }) { export function ClassList({
classes,
branches,
instructors
}: {
classes: any[],
branches: any[],
instructors: any[]
}) {
const router = useRouter(); const router = useRouter();
const [editingId, setEditingId] = useState<string | null>(null);
const [editForm, setEditForm] = useState<any>(null);
const handleDelete = async (id: string) => { const handleDelete = async (id: string) => {
if (!confirm('Silmek istediğinize emin misiniz?')) return; if (!confirm('Silmek istediğinize emin misiniz?')) return;
@@ -11,16 +22,101 @@ export function ClassList({ classes }: { classes: any[] }) {
router.refresh(); router.refresh();
}; };
const startEdit = (c: any) => {
setEditingId(c.id);
setEditForm({
name: c.name,
description: c.description || '',
branchId: c.branchId,
instructorId: c.instructorId || ''
});
};
const handleUpdate = async (e: React.FormEvent) => {
e.preventDefault();
const res = await fetch(`/api/classes/${editingId}`, {
method: 'PATCH',
body: JSON.stringify(editForm),
headers: { 'Content-Type': 'application/json' }
});
if (res.ok) {
setEditingId(null);
router.refresh();
} else {
alert('Güncelleme başarısız oldu.');
}
};
return ( return (
<div className={styles.list}> <div className={styles.list}>
{classes.map(c => ( {classes.map(c => (
<div key={c.id} className={styles.card}> <div key={c.id} className={styles.card}>
<div> {editingId === c.id ? (
<h3>{c.name}</h3> <form onSubmit={handleUpdate} style={{ width: '100%', display: 'flex', gap: '1rem', flexWrap: 'wrap', alignItems: 'flex-end' }}>
<p>Şube: {c.branch?.name}</p> <div className={styles.inputGroup}>
{c.instructor && <p>Hoca: {c.instructor.name}</p>} <label>Sınıf Adı</label>
</div> <input
<button onClick={() => handleDelete(c.id)} className={`${styles.btn} ${styles.btnDelete}`}>Sil</button> value={editForm.name}
onChange={e => setEditForm({ ...editForm, name: e.target.value })}
className={styles.input}
required
/>
</div>
<div className={styles.inputGroup}>
<label>ıklama</label>
<input
value={editForm.description}
onChange={e => setEditForm({ ...editForm, description: e.target.value })}
className={styles.input}
/>
</div>
<div className={styles.inputGroup}>
<label>Şube</label>
<select
value={editForm.branchId}
onChange={e => setEditForm({ ...editForm, branchId: e.target.value })}
className={styles.input}
required
>
<option value="">Şube Seçiniz</option>
{branches.map(b => (
<option key={b.id} value={b.id}>{b.name}</option>
))}
</select>
</div>
<div className={styles.inputGroup}>
<label>Eğitmen</label>
<select
value={editForm.instructorId}
onChange={e => setEditForm({ ...editForm, instructorId: e.target.value })}
className={styles.input}
>
<option value="">Eğitmen Seçiniz</option>
{instructors.map(i => (
<option key={i.id} value={i.id}>{i.name}</option>
))}
</select>
</div>
<div style={{ display: 'flex', gap: '0.5rem' }}>
<button type="submit" className={styles.btn} style={{ background: '#2e7d32' }}>Kaydet</button>
<button type="button" onClick={() => setEditingId(null)} className={styles.btn}>İptal</button>
</div>
</form>
) : (
<>
<div>
<h3>{c.name}</h3>
<p>Şube: {c.branch?.name}</p>
{c.instructor && <p>Hoca: {c.instructor.name}</p>}
<small>{c.description}</small>
</div>
<div style={{ display: 'flex', gap: '0.5rem' }}>
<button onClick={() => startEdit(c)} className={styles.btn} style={{ background: '#ffa000' }}>Düzenle</button>
<button onClick={() => handleDelete(c.id)} className={`${styles.btn} ${styles.btnDelete}`}>Sil</button>
</div>
</>
)}
</div> </div>
))} ))}
</div> </div>

View File

@@ -4,9 +4,11 @@ import { ClassList } from './ClassList';
import styles from '../branches/branches.module.css'; import styles from '../branches/branches.module.css';
export default async function Page() { export default async function Page() {
const classes = await prisma.danceClass.findMany({ include: { branch: true, instructor: true }, orderBy: { createdAt: 'desc' } }); const [classes, branches, instructors] = await Promise.all([
const branches = await prisma.branch.findMany(); prisma.danceClass.findMany({ include: { branch: true, instructor: true }, orderBy: { createdAt: 'desc' } }),
const instructors = await prisma.instructor.findMany(); prisma.branch.findMany({ select: { id: true, name: true } }),
prisma.instructor.findMany({ select: { id: true, name: true } })
]);
return ( return (
<div className={styles.container}> <div className={styles.container}>
@@ -14,7 +16,7 @@ export default async function Page() {
<h1>Sınıflar</h1> <h1>Sınıflar</h1>
</div> </div>
<AddClassForm branches={branches} instructors={instructors} /> <AddClassForm branches={branches} instructors={instructors} />
<ClassList classes={classes} /> <ClassList classes={classes} branches={branches} instructors={instructors} />
</div> </div>
); );
} }

View File

@@ -1,9 +1,12 @@
'use client'; 'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import styles from '../branches/branches.module.css'; import styles from '../branches/branches.module.css';
export function InstructorList({ instructors }: { instructors: any[] }) { export function InstructorList({ instructors }: { instructors: any[] }) {
const router = useRouter(); const router = useRouter();
const [editingId, setEditingId] = useState<string | null>(null);
const [editForm, setEditForm] = useState<any>(null);
const handleDelete = async (id: string) => { const handleDelete = async (id: string) => {
if (!confirm('Silmek istediğinize emin misiniz?')) return; if (!confirm('Silmek istediğinize emin misiniz?')) return;
@@ -11,15 +14,79 @@ export function InstructorList({ instructors }: { instructors: any[] }) {
router.refresh(); router.refresh();
}; };
const startEdit = (instructor: any) => {
setEditingId(instructor.id);
setEditForm({
name: instructor.name,
phone: instructor.phone || '',
bio: instructor.bio || ''
});
};
const handleUpdate = async (e: React.FormEvent) => {
e.preventDefault();
const res = await fetch(`/api/instructors/${editingId}`, {
method: 'PATCH',
body: JSON.stringify(editForm),
headers: { 'Content-Type': 'application/json' }
});
if (res.ok) {
setEditingId(null);
router.refresh();
} else {
alert('Güncelleme başarısız oldu.');
}
};
return ( return (
<div className={styles.list}> <div className={styles.list}>
{instructors.map(i => ( {instructors.map(i => (
<div key={i.id} className={styles.card}> <div key={i.id} className={styles.card}>
<div> {editingId === i.id ? (
<h3>{i.name}</h3> <form onSubmit={handleUpdate} style={{ width: '100%', display: 'flex', gap: '1rem', flexWrap: 'wrap', alignItems: 'flex-end' }}>
<p>{i.bio}</p> <div className={styles.inputGroup}>
</div> <label>Hoca Adı</label>
<button onClick={() => handleDelete(i.id)} className={`${styles.btn} ${styles.btnDelete}`}>Sil</button> <input
value={editForm.name}
onChange={e => setEditForm({ ...editForm, name: e.target.value })}
className={styles.input}
required
/>
</div>
<div className={styles.inputGroup}>
<label>Telefon</label>
<input
value={editForm.phone}
onChange={e => setEditForm({ ...editForm, phone: e.target.value })}
className={styles.input}
/>
</div>
<div className={styles.inputGroup}>
<label>Biyo</label>
<input
value={editForm.bio}
onChange={e => setEditForm({ ...editForm, bio: e.target.value })}
className={styles.input}
/>
</div>
<div style={{ display: 'flex', gap: '0.5rem' }}>
<button type="submit" className={styles.btn} style={{ background: '#2e7d32' }}>Kaydet</button>
<button type="button" onClick={() => setEditingId(null)} className={styles.btn}>İptal</button>
</div>
</form>
) : (
<>
<div>
<h3>{i.name} {i.phone && <small>({i.phone})</small>}</h3>
<p>{i.bio || 'Biyografi bulunmuyor'}</p>
</div>
<div style={{ display: 'flex', gap: '0.5rem' }}>
<button onClick={() => startEdit(i)} className={styles.btn} style={{ background: '#ffa000' }}>Düzenle</button>
<button onClick={() => handleDelete(i.id)} className={`${styles.btn} ${styles.btnDelete}`}>Sil</button>
</div>
</>
)}
</div> </div>
))} ))}
</div> </div>

View File

@@ -1,6 +1,28 @@
import { NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { prisma } from '@/infrastructure/db/prisma'; import { prisma } from '@/infrastructure/db/prisma';
export async function PATCH(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params;
const json = await request.json();
const danceClass = await prisma.danceClass.update({
where: { id },
data: {
name: json.name,
description: json.description,
branchId: json.branchId,
instructorId: json.instructorId
},
});
return NextResponse.json({ success: true, danceClass });
} catch (error) {
return NextResponse.json({ error: 'Failed to update class' }, { status: 500 });
}
}
export async function DELETE( export async function DELETE(
request: Request, request: Request,
{ params }: { params: Promise<{ id: string }> } { params }: { params: Promise<{ id: string }> }

View File

@@ -1,6 +1,27 @@
import { NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { prisma } from '@/infrastructure/db/prisma'; import { prisma } from '@/infrastructure/db/prisma';
export async function PATCH(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params;
const json = await request.json();
const instructor = await prisma.instructor.update({
where: { id },
data: {
name: json.name,
bio: json.bio,
phone: json.phone
},
});
return NextResponse.json({ success: true, instructor });
} catch (error) {
return NextResponse.json({ error: 'Failed to update instructor' }, { status: 500 });
}
}
export async function DELETE( export async function DELETE(
request: Request, request: Request,
{ params }: { params: Promise<{ id: string }> } { params }: { params: Promise<{ id: string }> }