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';
|
'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>
|
||||||
|
|||||||
@@ -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>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>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 }> }
|
||||||
|
|||||||
@@ -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 }> }
|
||||||
|
|||||||
Reference in New Issue
Block a user