feat: add in-place editing functionality for classes and instructors with dedicated API routes.
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
'use client';
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import styles from './branches.module.css';
|
||||
|
||||
export function BranchList({ branches }: { branches: any[] }) {
|
||||
const router = useRouter();
|
||||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
const [editForm, setEditForm] = useState<any>(null);
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
if (!confirm('Silmek istediğinize emin misiniz?')) return;
|
||||
@@ -11,17 +14,92 @@ export function BranchList({ branches }: { branches: any[] }) {
|
||||
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 (
|
||||
<div className={styles.list}>
|
||||
{branches.map(b => (
|
||||
<div key={b.id} className={styles.card}>
|
||||
{editingId === b.id ? (
|
||||
<form onSubmit={handleUpdate} style={{ width: '100%', display: 'flex', gap: '1rem', flexWrap: 'wrap', alignItems: 'flex-end' }}>
|
||||
<div className={styles.inputGroup}>
|
||||
<label>Şube Adı</label>
|
||||
<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>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>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
'use client';
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
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 [editingId, setEditingId] = useState<string | null>(null);
|
||||
const [editForm, setEditForm] = useState<any>(null);
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
if (!confirm('Silmek istediğinize emin misiniz?')) return;
|
||||
@@ -11,17 +22,102 @@ export function ClassList({ classes }: { classes: any[] }) {
|
||||
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 (
|
||||
<div className={styles.list}>
|
||||
{classes.map(c => (
|
||||
<div key={c.id} className={styles.card}>
|
||||
{editingId === c.id ? (
|
||||
<form onSubmit={handleUpdate} style={{ width: '100%', display: 'flex', gap: '1rem', flexWrap: 'wrap', alignItems: 'flex-end' }}>
|
||||
<div className={styles.inputGroup}>
|
||||
<label>Sınıf Adı</label>
|
||||
<input
|
||||
value={editForm.name}
|
||||
onChange={e => setEditForm({ ...editForm, name: e.target.value })}
|
||||
className={styles.input}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.inputGroup}>
|
||||
<label>Açı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>
|
||||
);
|
||||
|
||||
@@ -4,9 +4,11 @@ import { ClassList } from './ClassList';
|
||||
import styles from '../branches/branches.module.css';
|
||||
|
||||
export default async function Page() {
|
||||
const classes = await prisma.danceClass.findMany({ include: { branch: true, instructor: true }, orderBy: { createdAt: 'desc' } });
|
||||
const branches = await prisma.branch.findMany();
|
||||
const instructors = await prisma.instructor.findMany();
|
||||
const [classes, branches, instructors] = await Promise.all([
|
||||
prisma.danceClass.findMany({ include: { branch: true, instructor: true }, orderBy: { createdAt: 'desc' } }),
|
||||
prisma.branch.findMany({ select: { id: true, name: true } }),
|
||||
prisma.instructor.findMany({ select: { id: true, name: true } })
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
@@ -14,7 +16,7 @@ export default async function Page() {
|
||||
<h1>Sınıflar</h1>
|
||||
</div>
|
||||
<AddClassForm branches={branches} instructors={instructors} />
|
||||
<ClassList classes={classes} />
|
||||
<ClassList classes={classes} branches={branches} instructors={instructors} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
'use client';
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import styles from '../branches/branches.module.css';
|
||||
|
||||
export function InstructorList({ instructors }: { instructors: any[] }) {
|
||||
const router = useRouter();
|
||||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
const [editForm, setEditForm] = useState<any>(null);
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
if (!confirm('Silmek istediğinize emin misiniz?')) return;
|
||||
@@ -11,16 +14,80 @@ export function InstructorList({ instructors }: { instructors: any[] }) {
|
||||
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 (
|
||||
<div className={styles.list}>
|
||||
{instructors.map(i => (
|
||||
<div key={i.id} className={styles.card}>
|
||||
<div>
|
||||
<h3>{i.name}</h3>
|
||||
<p>{i.bio}</p>
|
||||
{editingId === i.id ? (
|
||||
<form onSubmit={handleUpdate} style={{ width: '100%', display: 'flex', gap: '1rem', flexWrap: 'wrap', alignItems: 'flex-end' }}>
|
||||
<div className={styles.inputGroup}>
|
||||
<label>Hoca Adı</label>
|
||||
<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>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
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(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
|
||||
@@ -1,6 +1,27 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
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(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
|
||||
Reference in New Issue
Block a user