Procházet zdrojové kódy

fix: скролл списков и кнопка удаления в Мои места

neyrogovnarik před 1 měsícem
rodič
revize
2947d14734

+ 1 - 1
frontend/src/app/admin/layout.tsx

@@ -110,7 +110,7 @@ export default function AdminLayout({ children }: { children: React.ReactNode })
   ]
 
   return (
-    <div className="flex min-h-screen bg-[#0f172a] pt-16">
+    <div className="flex h-screen bg-[#0f172a] pt-16">
       <aside className="w-56 border-r border-white/10 p-4">
         <nav className="space-y-1">
           {navItems.map((item) => (

+ 77 - 23
frontend/src/app/places/my/page.tsx

@@ -257,6 +257,69 @@ const FILTERS: { key: string; label: string }[] = [
   { key: 'rejected', label: 'Отклонено' },
 ]
 
+function PlaceCard({ place, onEdit, onDeleted }: { place: Place; onEdit: () => void; onDeleted: () => void }) {
+  const [showConfirm, setShowConfirm] = useState(false)
+  const [deleting, setDeleting] = useState(false)
+  const st = STATUS_LABELS[place.status] || { label: place.status, color: 'text-white/40' }
+
+  return (
+    <div className="rounded-xl bg-white/5 p-4 text-white">
+      <div className="flex items-start justify-between gap-3">
+        <div className="flex-1 min-w-0">
+          <h2 className="text-lg font-semibold truncate">{place.title}</h2>
+          {place.address && <p className="mt-1 text-sm text-white/60 truncate">{place.address}</p>}
+          <div className="mt-2 flex items-center gap-3 text-sm">
+            <span className={st.color}>{st.label}</span>
+            {place.type === 'studio' && <span className="text-white/40">Студия</span>}
+          </div>
+          {place.moderation_comment && (
+            <p className="mt-2 rounded bg-yellow-500/10 px-3 py-2 text-sm text-yellow-400">
+              Комментарий модератора: {place.moderation_comment}
+            </p>
+          )}
+        </div>
+        <div className="flex shrink-0 gap-2">
+          <button onClick={onEdit}
+            className="rounded-lg bg-white/10 px-4 py-1.5 text-sm text-white hover:bg-white/20 transition"
+          >Редактировать</button>
+          <button onClick={() => setShowConfirm(true)}
+            className="rounded-lg bg-red-600/80 px-4 py-1.5 text-sm text-white hover:bg-red-500 transition"
+          >Удалить</button>
+        </div>
+      </div>
+
+      {showConfirm && (
+        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60" onClick={() => setShowConfirm(false)}>
+          <div className="w-full max-w-sm rounded-xl bg-[#1e293b] p-6 shadow-xl" onClick={(e) => e.stopPropagation()}>
+            <h3 className="mb-2 text-lg font-bold text-white">Удалить место?</h3>
+            <p className="mb-4 text-sm text-white/60">{place.title}</p>
+            <p className="mb-4 text-sm text-red-400">Это действие нельзя отменить.</p>
+            <div className="flex justify-end gap-3">
+              <button onClick={() => setShowConfirm(false)} disabled={deleting}
+                className="rounded-lg bg-white/10 px-4 py-2 text-sm text-white hover:bg-white/20 transition disabled:opacity-50"
+              >Отмена</button>
+              <button onClick={async () => {
+                setDeleting(true)
+                try {
+                  await api.delete(`/places/${place.id}`)
+                  setShowConfirm(false)
+                  onDeleted()
+                } catch {
+                  setShowConfirm(false)
+                } finally {
+                  setDeleting(false)
+                }
+              }} disabled={deleting}
+                className="rounded-lg bg-red-600 px-4 py-2 text-sm text-white hover:bg-red-500 transition disabled:opacity-50"
+              >{deleting ? 'Удаление...' : 'Удалить'}</button>
+            </div>
+          </div>
+        </div>
+      )}
+    </div>
+  )
+}
+
 export default function MyPlacesPage() {
   const [places, setPlaces] = useState<Place[]>([])
   const [loading, setLoading] = useState(true)
@@ -313,29 +376,20 @@ export default function MyPlacesPage() {
           }
 
           return (
-            <div className="space-y-4">
-              {filtered.map((place) => {
-              const st = STATUS_LABELS[place.status] || { label: place.status, color: 'text-white/40' }
-              return (
-                <div key={place.id} className="rounded-xl bg-white/5 p-4 text-white">
-                  <h2 className="text-lg font-semibold">{place.title}</h2>
-                  {place.address && <p className="mt-1 text-sm text-white/60">{place.address}</p>}
-                  <div className="mt-2 flex items-center gap-3 text-sm">
-                    <span className={st.color}>{st.label}</span>
-                    {place.type === 'studio' && <span className="text-white/40">Студия</span>}
-                  </div>
-                  {place.moderation_comment && (
-                    <p className="mt-2 rounded bg-yellow-500/10 px-3 py-2 text-sm text-yellow-400">
-                      Комментарий модератора: {place.moderation_comment}
-                    </p>
-                  )}
-                  <button onClick={() => setEditId(place.id)}
-                    className="mt-3 rounded-lg bg-white/10 px-4 py-1.5 text-sm text-white hover:bg-white/20 transition"
-                  >Редактировать</button>
-                </div>
-              )
-            })}
-          </div>
+            <div className="max-h-[calc(100vh-14rem)] space-y-4 overflow-y-auto pr-1">
+              {filtered.map((place) => (
+                <PlaceCard
+                  key={place.id}
+                  place={place}
+                  onEdit={() => setEditId(place.id)}
+                  onDeleted={() => {
+                    setPlaces((prev) => prev.filter((p) => p.id !== place.id))
+                    setToast('Место удалено')
+                    setTimeout(() => setToast(''), 3000)
+                  }}
+                />
+              ))}
+            </div>
           )
         })()}
       </div>