Skip to content

Commit

Permalink
Merge pull request #63 from Sports-day/feature/information
Browse files Browse the repository at this point in the history
お知らせ機能
  • Loading branch information
testusuke authored Oct 8, 2024
2 parents ee43a4b + f489777 commit eb9b2b6
Show file tree
Hide file tree
Showing 7 changed files with 378 additions and 2 deletions.
56 changes: 56 additions & 0 deletions app/(authenticated)/information/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {Alert, Breadcrumbs, Button, Link, Stack, Typography} from "@mui/material";
import CardBackground from "@/components/layout/cardBackground";
import NextLink from "next/link";
import {informationFactory} from "@/src/models/InformationModel";
import InformationEditor from "@/components/information/informationEditor";

export default async function InformationDetailPage({params}: { params: { id: string } }) {
const informationId = parseInt(params.id, 10)
const information = await informationFactory().show(informationId)

if (isNaN(informationId) || !information) {
return (
<Stack spacing={1} mx={2} my={3}>
<Alert severity="error">
<Typography>お知らせが存在しません。</Typography>
</Alert>

<Button
variant="contained"
href="/information/"
component={NextLink}
>
お知らせ管理に戻る
</Button>
</Stack>
)
}

return (
<Stack spacing={1} mx={2} my={3}>
<Breadcrumbs aria-label="breadcrumb" sx={{pl: 2}}>
<Link
underline="hover"
color="inherit"
href="/"
component={NextLink}
>
管理者のダッシュボード
</Link>
<Link
underline="hover"
color="inherit"
href={"/information/"}
component={NextLink}
>
お知らせ管理
</Link>
<Typography color="text.primary">{information.name}</Typography>
</Breadcrumbs>

<CardBackground title={`お知らせ`}>
<InformationEditor information={information} />
</CardBackground>
</Stack>
)
}
36 changes: 36 additions & 0 deletions app/(authenticated)/information/create/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {Stack, Breadcrumbs, Link, Typography} from "@mui/material";
import CardBackground from "@/components/layout/cardBackground";
import NextLink from "next/link";
import InformationCreator from "@/components/information/informationCreator";

export default function InformationCreatePage() {

return (
<Stack spacing={1} mx={2} my={3}>
<Breadcrumbs aria-label="breadcrumb" sx={{pl: 2}}>
<Link
underline="hover"
color="inherit"
href="/"
component={NextLink}
>
管理者のダッシュボード
</Link>
<Link
underline="hover"
color="inherit"
href={"/tags/"}
component={NextLink}
>
お知らせ管理
</Link>
<Typography color="text.primary">お知らせ作成</Typography>
</Breadcrumbs>
<CardBackground
title={"お知らせ作成"}
>
<InformationCreator />
</CardBackground>
</Stack>
);
}
32 changes: 32 additions & 0 deletions app/(authenticated)/information/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {Stack, Breadcrumbs, Link, Typography} from "@mui/material";
import CardBackground from "@/components/layout/cardBackground";
import NextLink from "next/link";
import {informationFactory} from "@/src/models/InformationModel";
import InformationAgGrid from "@/components/information/informationAgGrid";

export default async function TagsPage() {
const informationList = await informationFactory().index()

return (
<Stack spacing={1} mx={2} my={3}>
<Breadcrumbs aria-label="breadcrumb" sx={{pl: 2}}>
<Link
underline="hover"
color="inherit"
href="/"
component={NextLink}
>
管理者のダッシュボード
</Link>
<Typography color="text.primary">お知らせ管理</Typography>
</Breadcrumbs>
<CardBackground
title={"お知らせ一覧"}
button={"作成"}
link={"/information/create"}
>
<InformationAgGrid informationList={informationList}/>
</CardBackground>
</Stack>
);
}
78 changes: 78 additions & 0 deletions components/information/informationAgGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
'use client'
import {useEffect, useState} from 'react';
import {AgGridReact} from 'ag-grid-react';
import "ag-grid-community/styles/ag-grid.css"; // Mandatory CSS required by the grid
import "ag-grid-community/styles/ag-theme-quartz.css"; // Optional Theme applied to the grid

import {ColDef, ModuleRegistry, RowClickedEvent} from 'ag-grid-community';
import {ClientSideRowModelModule} from 'ag-grid-community';
import {useRouter} from "next/navigation";
import {Information} from "@/src/models/InformationModel";

ModuleRegistry.registerModules([ClientSideRowModelModule]);

export type InformationAgGridProps = {
informationList: Information[]
}

// Row Data Interface
type IRow = {
id: number,
name: string,
content: string,
}

// Create new GridExample component
export default function InformationAgGrid(props: InformationAgGridProps) {
const height = 'calc(100vh - 230px)';
const router = useRouter()
// Row Data: The data to be displayed.
const [rowData, setRowData] = useState<IRow[]>([])
// Column Definitions: Defines & controls grid columns.
const [colDefs] = useState<ColDef<IRow>[]>([
{field: "id", headerName: "ID"},
{field: "name", headerName: "名前"},
{field: "content", headerName: "内容"},
]);

useEffect(() => {
const informationList = props.informationList.map((information): IRow => {
return {
id: information.id,
name: information.name,
content: information.content
}
})

// set row data
setRowData(informationList)
}, [props.informationList])

const handleRowClick = (e: RowClickedEvent<IRow>) => {
const data = e.data

// ignore undefined
if (!data) return

// redirect
router.push(`/information/${data.id}`)
}

// Container: Defines the grid's theme & dimensions.
return (
<div
className={"ag-theme-quartz"}
style={{
width: '100%',
height: height,
borderRadius: "10px"
}}
>
<AgGridReact
rowData={rowData}
columnDefs={colDefs}
onRowClicked={handleRowClick}
/>
</div>
);
}
85 changes: 85 additions & 0 deletions components/information/informationCreator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
'use client'
import {Button, Stack, TextField, TextFieldProps} from "@mui/material";
import {useRouter} from "next/navigation";
import {useRef} from "react";
import NextLink from "next/link";
import {informationFactory} from "@/src/models/InformationModel";

export default function InformationCreator() {
const router = useRouter()
const nameRef = useRef<TextFieldProps>(null)
const contentRef = useRef<TextFieldProps>(null)

const handleCreate = async () => {
// create role
const name = nameRef.current?.value as string
const content = contentRef.current?.value as string


if (!name) {
alert("名前を入力してください")
return
}

if (!content) {
alert("内容を入力してください")
return
}

await informationFactory().create({
name: name,
content: content
})

// redirect to role page
router.push('/information')
}

return (
<Stack
spacing={1}
>
<TextField
fullWidth
inputRef={nameRef}
label="名前"
sx={{
width: "50%"
}}
required
/>

<TextField
fullWidth
inputRef={contentRef}
label="内容"
sx={{
width: "50%"
}}
multiline
required
/>


<Stack
direction="row"
spacing={1}
>
<Button
onClick={handleCreate}
variant="contained"
>
作成
</Button>

<Button
href="/tags"
component={NextLink}
variant="contained"
>
キャンセル
</Button>
</Stack>
</Stack>
)
}
90 changes: 90 additions & 0 deletions components/information/informationEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
'use client'
import React, {useRef} from "react";
import {
Button,
Stack,
TextField,
TextFieldProps
} from "@mui/material";
import {useRouter} from "next/navigation";
import {Information, informationFactory} from "@/src/models/InformationModel";
import {HiTrash} from "react-icons/hi2";

export type InformationEditorProps = {
information: Information
}

export default function InformationEditor(props: InformationEditorProps) {
const router = useRouter()
const nameRef = useRef<TextFieldProps>(null)
const contentRef = useRef<TextFieldProps>(null)

const handleSubmit = async () => {
const name = nameRef.current?.value as string
const content = contentRef.current?.value as string

await informationFactory().update(props.information.id, {
name: name,
content: content,
})

// reload
router.push("/information")
}

const handleDelete = async () => {
await informationFactory().delete(props.information.id)

// reload
router.push("/information")
}

return (
<Stack spacing={1}>
<TextField
label={"名前"}
name={"name"}
defaultValue={props.information.name}
inputRef={nameRef}
sx={{
width: "50%"
}}
/>

<TextField
label={"内容"}
name={"content"}
defaultValue={props.information.content}
inputRef={contentRef}
sx={{
width: "50%"
}}
multiline
/>

<Stack
direction={"row"}
my={0.5}
spacing={1}
width={"100%"}
justifyContent={"space-between"}
alignItems="center"
>
<Button
variant="outlined"
color={"error"}
startIcon={<HiTrash/>}
onClick={handleDelete}
>
削除
</Button>
<Button
variant={"contained"}
onClick={handleSubmit}
>
保存
</Button>
</Stack>
</Stack>
)
}
3 changes: 1 addition & 2 deletions components/layout/navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,10 @@ export const Navigation = () => {
</Button>
<Button
color={"secondary"}
disabled={true}
variant={"contained"}
sx={{py: buttonPadding, width: "100%", fontWeight: "600"}}
component={NextLink}
href={"/"}
href={"/information"}
onClick={handleDrawerClose}
>
<Stack spacing={1} mx={1} width={"100%"} direction={"row"} justifyContent={"flex-start"}
Expand Down

0 comments on commit eb9b2b6

Please sign in to comment.