Browse Source

#302

feature/302
みてるぞ 1 week ago
parent
commit
09763982b5
14 changed files with 231 additions and 45 deletions
  1. +9
    -3
      backend/app/controllers/posts_controller.rb
  2. +23
    -2
      backend/app/controllers/theatre_comments_controller.rb
  3. +17
    -0
      backend/app/controllers/theatre_programmes_controller.rb
  4. +8
    -5
      backend/app/controllers/theatres_controller.rb
  5. +1
    -1
      backend/app/models/tag.rb
  6. +2
    -0
      backend/app/models/theatre.rb
  7. +6
    -0
      backend/app/models/theatre_programme.rb
  8. +2
    -1
      backend/config/routes.rb
  9. +10
    -0
      backend/db/migrate/20260514221900_create_theatre_programmes.rb
  10. +13
    -1
      backend/db/schema.rb
  11. +2
    -2
      frontend/src/App.tsx
  12. +3
    -7
      frontend/src/components/TopNav.tsx
  13. +117
    -18
      frontend/src/pages/theatres/TheatreDetailPage.tsx
  14. +18
    -5
      frontend/src/types.ts

+ 9
- 3
backend/app/controllers/posts_controller.rb View File

@@ -44,7 +44,8 @@ class PostsController < ApplicationController
filtered_posts filtered_posts
.joins("LEFT JOIN (#{ pt_max_sql }) pt_max ON pt_max.post_id = posts.id") .joins("LEFT JOIN (#{ pt_max_sql }) pt_max ON pt_max.post_id = posts.id")
.reselect('posts.*', Arel.sql("#{ updated_at_all_sql } AS updated_at_all")) .reselect('posts.*', Arel.sql("#{ updated_at_all_sql } AS updated_at_all"))
.preload(tags: [:deerjikists, :materials, { tag_name: :wiki_page }])
.preload(:parents, :children,
tags: [:deerjikists, :materials, { tag_name: :wiki_page }])
.with_attached_thumbnail .with_attached_thumbnail


q = q.where('posts.url LIKE ?', "%#{ url }%") if url q = q.where('posts.url LIKE ?', "%#{ url }%") if url
@@ -95,7 +96,8 @@ class PostsController < ApplicationController
end end


def random def random
post = filtered_posts.preload(tags: [:deerjikists, :materials, { tag_name: :wiki_page }])
post = filtered_posts.preload(:parents, :childern,
tags: [:deerjikists, :materials, { tag_name: :wiki_page }])
.order('RAND()') .order('RAND()')
.first .first
return head :not_found unless post return head :not_found unless post
@@ -104,7 +106,11 @@ class PostsController < ApplicationController
end end


def show def show
post = Post.includes(tags: [:deerjikists, :materials, { tag_name: :wiki_page }]).find_by(id: params[:id])
post = Post
.preload(:parents, :children)
.includes(:parents, :children,
tags: [:deerjikists, :materials, { tag_name: :wiki_page }])
.find_by(id: params[:id])
return head :not_found unless post return head :not_found unless post


render json: PostRepr.base(post, current_user) render json: PostRepr.base(post, current_user)


+ 23
- 2
backend/app/controllers/theatre_comments_controller.rb View File

@@ -1,14 +1,21 @@
class TheatreCommentsController < ApplicationController class TheatreCommentsController < ApplicationController
def index def index
limit = params[:limit].to_i
limit = 20 if limit <= 0

no_gt = params[:no_gt].to_i no_gt = params[:no_gt].to_i
no_gt = 0 if no_gt.negative?
no_gt = 0 if no_gt < 0


comments = TheatreComment comments = TheatreComment
.where(theatre_id: params[:theatre_id]) .where(theatre_id: params[:theatre_id])
.where('no > ?', no_gt) .where('no > ?', no_gt)
.order(no: :desc) .order(no: :desc)
.limit(limit)


render json: comments.as_json(include: { user: { only: [:id, :name] } })
render json: comments.map {
_1.as_json(include: { user: { only: [:id, :name] } })
.merge(content: _1.discarded? ? nil : _1.content, deleted: _1.discarded?)
}
end end


def create def create
@@ -29,4 +36,18 @@ class TheatreCommentsController < ApplicationController


render json: comment, status: :created render json: comment, status: :created
end end

def destroy
return head :unauthorized unless current_user

theatre_id = params[:theatre_id].to_i
no = params[:id].to_i

comment = TheatreComment.find_by(theatre_id:, no:)
return head :not_found unless comment

comment.discard!

head :no_content
end
end end

+ 17
- 0
backend/app/controllers/theatre_programmes_controller.rb View File

@@ -0,0 +1,17 @@
class TheatreProgrammesController < ApplicationController
def index
limit = params[:limit].to_i
limit = 100 if limit <= 0

position_gt = params[:position_gt].to_i
position_gt = 0 if position_gt < 0

programmes = TheatreProgramme
.where(theatre_id: params[:theatre_id])
.where('position > ?', position_gt)
.order(position: :desc).limit(100)
.limit(limit)

render json: programmes.as_json(include: { post: PostRepr::BASE })
end
end

+ 8
- 5
backend/app/controllers/theatres_controller.rb View File

@@ -43,11 +43,14 @@ class TheatresController < ApplicationController
return head :not_found unless theatre return head :not_found unless theatre
return head :forbidden if theatre.host_user != current_user return head :forbidden if theatre.host_user != current_user


post = Post.where("url LIKE '%nicovideo.jp%'")
.or(Post.where("url LIKE '%youtube.com%'"))
.order('RAND()')
.first
theatre.update!(current_post: post, current_post_started_at: Time.current)
ApplicationRecord.transaction do
post = Post.where("url LIKE '%nicovideo.jp%'")
.order('RAND()')
.first
theatre.update!(current_post: post, current_post_started_at: Time.current)
position = (theatre.programmes.maximum(:position) || 0) + 1
theatre.programmes.create!(position:, post:)
end


head :no_content head :no_content
end end


+ 1
- 1
backend/app/models/tag.rb View File

@@ -81,7 +81,7 @@ class Tag < ApplicationRecord


def material_id = materials.first&.id def material_id = materials.first&.id


def has_deerjikists = deerjikists.present?
def has_deerjikists = deerjikists.exists?


def self.tagme = find_or_create_by_tag_name!('タグ希望', category: :meta) def self.tagme = find_or_create_by_tag_name!('タグ希望', category: :meta)
def self.bot = find_or_create_by_tag_name!('bot操作', category: :meta) def self.bot = find_or_create_by_tag_name!('bot操作', category: :meta)


+ 2
- 0
backend/app/models/theatre.rb View File

@@ -7,6 +7,8 @@ class Theatre < ApplicationRecord
class_name: 'TheatreWatchingUser', inverse_of: :theatre class_name: 'TheatreWatchingUser', inverse_of: :theatre
has_many :watching_users, through: :active_theatre_watching_users, source: :user has_many :watching_users, through: :active_theatre_watching_users, source: :user


has_many :programmes, class_name: 'TheatreProgramme'

belongs_to :host_user, class_name: 'User', optional: true belongs_to :host_user, class_name: 'User', optional: true
belongs_to :current_post, class_name: 'Post', optional: true belongs_to :current_post, class_name: 'Post', optional: true
belongs_to :created_by_user, class_name: 'User' belongs_to :created_by_user, class_name: 'User'


+ 6
- 0
backend/app/models/theatre_programme.rb View File

@@ -0,0 +1,6 @@
class TheatreProgramme < ApplicationRecord
self.primary_key = :theatre_id, :position

belongs_to :theatre
belongs_to :post
end

+ 2
- 1
backend/config/routes.rb View File

@@ -87,7 +87,8 @@ Rails.application.routes.draw do
patch :next_post patch :next_post
end end


resources :comments, controller: :theatre_comments, only: [:index, :create]
resources :comments, controller: :theatre_comments, only: [:index, :create, :destroy]
resources :programmes, controller: :theatre_programmes, only: [:index]
end end


resources :materials, only: [:index, :show, :create, :update, :destroy] resources :materials, only: [:index, :show, :create, :update, :destroy]


+ 10
- 0
backend/db/migrate/20260514221900_create_theatre_programmes.rb View File

@@ -0,0 +1,10 @@
class CreateTheatreProgrammes < ActiveRecord::Migration[8.0]
def change
create_table :theatre_programmes, primary_key: [:theatre_id, :position] do |t|
t.references :theatre, null: false, foreign_key: true
t.integer :position, null: false
t.references :post, null: false, foreign_key: true
t.datetime :created_at, null: false
end
end
end

+ 13
- 1
backend/db/schema.rb View File

@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.


ActiveRecord::Schema[8.0].define(version: 2026_05_07_213300) do
ActiveRecord::Schema[8.0].define(version: 2026_05_14_221900) do
create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.string "record_type", null: false t.string "record_type", null: false
@@ -283,6 +283,15 @@ ActiveRecord::Schema[8.0].define(version: 2026_05_07_213300) do
t.index ["user_id"], name: "index_theatre_comments_on_user_id" t.index ["user_id"], name: "index_theatre_comments_on_user_id"
end end


create_table "theatre_programmes", primary_key: ["theatre_id", "position"], charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "theatre_id", null: false
t.integer "position", null: false
t.bigint "post_id", null: false
t.datetime "created_at", null: false
t.index ["post_id"], name: "index_theatre_programmes_on_post_id"
t.index ["theatre_id"], name: "index_theatre_programmes_on_theatre_id"
end

create_table "theatre_watching_users", primary_key: ["theatre_id", "user_id"], charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| create_table "theatre_watching_users", primary_key: ["theatre_id", "user_id"], charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "theatre_id", null: false t.bigint "theatre_id", null: false
t.bigint "user_id", null: false t.bigint "user_id", null: false
@@ -290,6 +299,7 @@ ActiveRecord::Schema[8.0].define(version: 2026_05_07_213300) do
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.index ["expires_at"], name: "index_theatre_watching_users_on_expires_at" t.index ["expires_at"], name: "index_theatre_watching_users_on_expires_at"
t.index ["theatre_id", "expires_at"], name: "idx_on_theatre_id_skip_expires_at_4c8de1dd42"
t.index ["theatre_id", "expires_at"], name: "index_theatre_watching_users_on_theatre_id_and_expires_at" t.index ["theatre_id", "expires_at"], name: "index_theatre_watching_users_on_theatre_id_and_expires_at"
t.index ["theatre_id"], name: "index_theatre_watching_users_on_theatre_id" t.index ["theatre_id"], name: "index_theatre_watching_users_on_theatre_id"
t.index ["user_id"], name: "index_theatre_watching_users_on_user_id" t.index ["user_id"], name: "index_theatre_watching_users_on_user_id"
@@ -464,6 +474,8 @@ ActiveRecord::Schema[8.0].define(version: 2026_05_07_213300) do
add_foreign_key "tags", "tag_names" add_foreign_key "tags", "tag_names"
add_foreign_key "theatre_comments", "theatres" add_foreign_key "theatre_comments", "theatres"
add_foreign_key "theatre_comments", "users" add_foreign_key "theatre_comments", "users"
add_foreign_key "theatre_programmes", "posts"
add_foreign_key "theatre_programmes", "theatres"
add_foreign_key "theatre_watching_users", "theatres" add_foreign_key "theatre_watching_users", "theatres"
add_foreign_key "theatre_watching_users", "users" add_foreign_key "theatre_watching_users", "users"
add_foreign_key "theatres", "posts", column: "current_post_id" add_foreign_key "theatres", "posts", column: "current_post_id"


+ 2
- 2
frontend/src/App.tsx View File

@@ -63,7 +63,7 @@ const RouteTransitionWrapper = ({ user, setUser }: {
<Route path="/tags/:id/deerjikists" element={<DeerjikistDetailPage/>}/> <Route path="/tags/:id/deerjikists" element={<DeerjikistDetailPage/>}/>
<Route path="/tags/nico" element={<NicoTagListPage user={user}/>}/> <Route path="/tags/nico" element={<NicoTagListPage user={user}/>}/>
<Route path="/tags/changes" element={<TagHistoryPage/>}/> <Route path="/tags/changes" element={<TagHistoryPage/>}/>
<Route path="/theatres/:id" element={<TheatreDetailPage/>}/>
<Route path="/theatres/:id" element={<TheatreDetailPage user={user}/>}/>
<Route path="/materials" element={<MaterialBasePage/>}> <Route path="/materials" element={<MaterialBasePage/>}>
<Route index element={<MaterialListPage/>}/> <Route index element={<MaterialListPage/>}/>
<Route path="new" element={<MaterialNewPage/>}/> <Route path="new" element={<MaterialNewPage/>}/>
@@ -158,4 +158,4 @@ const App: FC = () => {
</>) </>)
} }


export default App
export default App

+ 3
- 7
frontend/src/components/TopNav.tsx View File

@@ -55,12 +55,6 @@ export const menuOutline = ({ tag, wikiId, user, pathName }: {
{ name: '追加', to: '/materials/new' }, { name: '追加', to: '/materials/new' },
{ name: '全体履歴', to: '/materials/changes', visible: false }, { name: '全体履歴', to: '/materials/changes', visible: false },
{ name: 'ヘルプ', to: '/wiki/ヘルプ:素材集' }] }, { name: 'ヘルプ', to: '/wiki/ヘルプ:素材集' }] },
{ name: '上映会', to: '/theatres/1', base: '/theatres', subMenu: [
{ name: <>第&thinsp;1&thinsp;会場</>, to: '/theatres/1' },
{ name: 'CyTube', to: '//cytube.mm428.net/r/deernijika' },
{ name: <>ニジカ放送局第&thinsp;1&thinsp;チャンネル</>,
to: '//www.youtube.com/watch?v=DCU3hL4Uu6A' },
{ name: 'ヘルプ', to: '/wiki/ヘルプ:上映会' }] },
{ name: 'Wiki', to: '/wiki/ヘルプ:ホーム', base: '/wiki', subMenu: [ { name: 'Wiki', to: '/wiki/ヘルプ:ホーム', base: '/wiki', subMenu: [
{ name: '検索', to: '/wiki' }, { name: '検索', to: '/wiki' },
{ name: '新規', to: '/wiki/new' }, { name: '新規', to: '/wiki/new' },
@@ -71,6 +65,8 @@ export const menuOutline = ({ tag, wikiId, user, pathName }: {
visible: wikiPageFlg }, visible: wikiPageFlg },
{ name: '履歴', to: `/wiki/changes?id=${ wikiId }`, visible: wikiPageFlg }, { name: '履歴', to: `/wiki/changes?id=${ wikiId }`, visible: wikiPageFlg },
{ name: '編輯', to: `/wiki/${ wikiId || wikiTitle }/edit`, visible: wikiPageFlg }] }, { name: '編輯', to: `/wiki/${ wikiId || wikiTitle }/edit`, visible: wikiPageFlg }] },
{ name: 'おたのしみ', visible: false, subMenu: [
{ name: '上映会 (β)', to: '/theatres/1' }] },
{ name: 'ユーザ', to: '/users/settings', visible: false, subMenu: [ { name: 'ユーザ', to: '/users/settings', visible: false, subMenu: [
{ name: '一覧', to: '/users', visible: false }, { name: '一覧', to: '/users', visible: false },
{ name: 'お前', to: `/users/${ user?.id }`, visible: false }, { name: 'お前', to: `/users/${ user?.id }`, visible: false },
@@ -267,7 +263,7 @@ const TopNav: FC<Props> = ({ user }) => {
: { initial: { x: 40, y: -40, opacity: 0 }, : { initial: { x: 40, y: -40, opacity: 0 },
animate: { x: 0, y: 0, opacity: 1 }, animate: { x: 0, y: 0, opacity: 1 },
exit: { x: 40, y: -40, opacity: 0 } })} exit: { x: 40, y: -40, opacity: 0 } })}
className="z-10 h-full flex items-center px-3 font-bold w-24">
className="z-10 h-full flex items-center px-3 font-bold w-28">
<h2>{item.name}</h2> <h2>{item.name}</h2>
</motion.div> </motion.div>
{item.subMenu {item.subMenu


+ 117
- 18
frontend/src/pages/theatres/TheatreDetailPage.tsx View File

@@ -6,20 +6,24 @@ import ErrorScreen from '@/components/ErrorScreen'
import PostEmbed from '@/components/PostEmbed' import PostEmbed from '@/components/PostEmbed'
import PrefetchLink from '@/components/PrefetchLink' import PrefetchLink from '@/components/PrefetchLink'
import TagDetailSidebar from '@/components/TagDetailSidebar' import TagDetailSidebar from '@/components/TagDetailSidebar'
import SectionTitle from '@/components/common/SectionTitle'
import { useDialogue } from '@/components/dialogues/DialogueProvider'
import { Button } from '@/components/ui/button'
import MainArea from '@/components/layout/MainArea' import MainArea from '@/components/layout/MainArea'
import SidebarComponent from '@/components/layout/SidebarComponent' import SidebarComponent from '@/components/layout/SidebarComponent'
import { SITE_TITLE } from '@/config' import { SITE_TITLE } from '@/config'
import { apiGet, apiPatch, apiPost, apiPut, isApiError } from '@/lib/api'
import { apiGet, apiDelete, apiPatch, apiPost, apiPut, isApiError } from '@/lib/api'
import { fetchPost } from '@/lib/posts' import { fetchPost } from '@/lib/posts'
import { dateString } from '@/lib/utils' import { dateString } from '@/lib/utils'


import type { FC } from 'react'
import type { FC, User } from 'react'


import type { NiconicoMetadata, import type { NiconicoMetadata,
NiconicoViewerHandle, NiconicoViewerHandle,
Post, Post,
Theatre, Theatre,
TheatreComment } from '@/types'
TheatreComment,
TheatreProgramme } from '@/types'


type TheatreInfo = { type TheatreInfo = {
hostFlg: boolean hostFlg: boolean
@@ -34,9 +38,36 @@ const INITIAL_THEATRE_INFO =
watchingUsers: [] as { id: number; name: string }[] } as const watchingUsers: [] as { id: number; name: string }[] } as const




const TheatreDetailPage: FC = () => {
const commentBox: ReactNode[] = (comment: TheatreComment) =>
[(
<div key={`${ comment.no }-content`} className="w-full">
{comment.deleted
? (
<span className="text-sm font-bold">
削除されました.
</span>)
: comment.content}
</div>),
(
<div key={`${ comment.no }-user`} className="w-full text-sm text-right">
by {comment.user
? (comment.user.name || `名もなきニジラー(#${ comment.user.id })`)
: '運営'}
</div>),
(
<div key={`${ comment.no }-createdAt`} className="w-full text-sm text-right">
{dateString (comment.createdAt)}
</div>)]


type Props = { user: User }


const TheatreDetailPage: FC<Props> = ({ user }: Props) => {
const { id } = useParams () const { id } = useParams ()


const dialogue = useDialogue ()

const commentsRef = useRef<HTMLDivElement> (null) const commentsRef = useRef<HTMLDivElement> (null)
const embedRef = useRef<NiconicoViewerHandle> (null) const embedRef = useRef<NiconicoViewerHandle> (null)
const loadingRef = useRef (false) const loadingRef = useRef (false)
@@ -52,6 +83,7 @@ const TheatreDetailPage: FC = () => {
const [theatre, setTheatre] = useState<Theatre | null> (null) const [theatre, setTheatre] = useState<Theatre | null> (null)
const [theatreInfo, setTheatreInfo] = useState<TheatreInfo> (INITIAL_THEATRE_INFO) const [theatreInfo, setTheatreInfo] = useState<TheatreInfo> (INITIAL_THEATRE_INFO)
const [post, setPost] = useState<Post | null> (null) const [post, setPost] = useState<Post | null> (null)
const [programmes, setProgrammes] = useState<TheatreProgramme[]> ([])
const [videoLength, setVideoLength] = useState (0) const [videoLength, setVideoLength] = useState (0)


useEffect (() => { useEffect (() => {
@@ -118,7 +150,7 @@ const TheatreDetailPage: FC = () => {
{ {
const newComments = await apiGet<TheatreComment[]> ( const newComments = await apiGet<TheatreComment[]> (
`/theatres/${ id }/comments`, `/theatres/${ id }/comments`,
{ params: { no_gt: lastCommentNoRef.current } })
{ params: { no_gt: lastCommentNoRef.current, limit: '20' } })


if (!(cancelled) && newComments.length > 0) if (!(cancelled) && newComments.length > 0)
{ {
@@ -214,10 +246,24 @@ const TheatreDetailPage: FC = () => {
} }
}) () }) ()


void (async () => {
try
{
const data = await apiGet<TheatreProgramme[]> (
`/theatres/${ id }/programmes`, { params: { limit: '100' } })
if (!(cancelled))
setProgrammes (data)
}
catch (e)
{
console.error (e)
}
}) ()

return () => { return () => {
cancelled = true cancelled = true
} }
}, [theatreInfo.postId])
}, [id, theatreInfo.postId])


const syncPlayback = (meta: NiconicoMetadata) => { const syncPlayback = (meta: NiconicoMetadata) => {
if (!(theatreInfo.postStartedAt)) if (!(theatreInfo.postStartedAt))
@@ -233,12 +279,30 @@ const TheatreDetailPage: FC = () => {
embedRef.current?.seek (targetTime) embedRef.current?.seek (targetTime)
} }


const handleDelete = async (commentNo: number) => {
try
{
await apiDelete (`/theatres/${ id }/comments/${ commentNo }`)
setComments (prev => {
const rtn = [...prev]
const idx = rtn.findIndex (x => x.no === commentNo)
rtn[idx] = { ...rtn[idx], deleted: true }
return rtn
})
}
catch
{
;
}
}

if (status >= 400) if (status >= 400)
return <ErrorScreen status={status}/> return <ErrorScreen status={status}/>


return ( return (
<div className="md:flex md:flex-1 overflow-y-auto md:overflow-y-hidden"> <div className="md:flex md:flex-1 overflow-y-auto md:overflow-y-hidden">
<Helmet> <Helmet>
<meta name="robots" content="noindex"/>
{theatre && ( {theatre && (
<title> <title>
{'上映会場' {'上映会場'
@@ -270,6 +334,22 @@ const TheatreDetailPage: FC = () => {
</PrefetchLink> </PrefetchLink>
</div> </div>
</>) : 'Loading...'} </>) : 'Loading...'}

<div>
<SectionTitle>
上映履歴
</SectionTitle>

<div>
{programmes.map ((programme, i) => (
<div key={i}>
<PrefetchLink to={`/posts/${ programme.post.id }`}>
{programme.post.title}
</PrefetchLink>
({dateString (programme.createdAt)})
</div>))}
</div>
</div>
</MainArea> </MainArea>


<SidebarComponent> <SidebarComponent>
@@ -306,18 +386,36 @@ const TheatreDetailPage: FC = () => {
className="overflow-x-hidden overflow-y-scroll text-wrap w-full className="overflow-x-hidden overflow-y-scroll text-wrap w-full
h-[32vh] md:h-[64vh] border rounded"> h-[32vh] md:h-[64vh] border rounded">
{comments.map (comment => ( {comments.map (comment => (
<div key={comment.no} className="p-2">
<div className="w-full">
{comment.content}
</div>
<div className="w-full text-sm text-right">
by {comment.user
? (comment.user.name || `名もなきニジラー(#${ comment.user.id })`)
: '運営'}
</div>
<div className="w-full text-sm text-right">
{dateString (comment.createdAt)}
</div>
<div
key={comment.no}
className="p-2 group relative rounded py-1 hover:bg-gray-100
dark:hover:bg-gray-800">
{(user && comment.user?.id === user.id && !(comment.deleted)) && (
<button
type="button"
className="absolute left-1 top-1 hidden rounded text-md text-red-600
hover:bg-red-100 group-hover:inline-block
dark:text-red-300 dark:hover:bg-red-950"
aria-label="コメントを削除"
onClick={async e => {
e.stopPropagation ()

if (!(await dialogue.confirm ({
title: 'このコメントを削除しますか?',
description: (
<div className="border border-black dark:border-white rounded
my-3 p-2 w-64">
{commentBox (comment)}
</div>),
confirmText: '削除',
variant: 'danger' })))
return

await handleDelete (comment.no)
}}>
&times;
</button>)}
{commentBox (comment)}
</div>))} </div>))}
</div> </div>
</form> </form>
@@ -345,4 +443,5 @@ const TheatreDetailPage: FC = () => {
</div>) </div>)
} }



export default TheatreDetailPage export default TheatreDetailPage

+ 18
- 5
frontend/src/types.ts View File

@@ -210,11 +210,24 @@ export type Theatre = {
createdAt: string createdAt: string
updatedAt: string } updatedAt: string }


export type TheatreComment = {
theatreId: number,
no: number,
user: { id: number, name: string } | null
content: string
export type TheatreComment =
| { theatreId: number
no: number
deteled: false
user: { id: number, name: string } | null
content: string
createdAt: string }
| { theatreId: number
no: number
deleted: true
user: { id: number, name: string } | null
content null,
createdAt: string }

export type TheatreProgramme = {
theatreId: number
position: number
post: Post
createdAt: string } createdAt: string }


export type User = { export type User = {


Loading…
Cancel
Save