-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
予算管理ページの作成(フロント) #894
Open
TkymHrt
wants to merge
11
commits into
develop
Choose a base branch
from
feat/yama/893-budget-management-page
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
予算管理ページの作成(フロント) #894
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
d2b046b
[fix] OutlinePrimaryButtonの微調整
TkymHrt 24af0b9
[feat] navバーに項目を追加
TkymHrt 3f8ca46
[feat] BudgetManagementコンポーネントの作成
TkymHrt 5324f71
[feat] 予算ページの作成
TkymHrt 5b8465b
[fix] cssのモバイル向けに調整
TkymHrt 64c50f4
formatted by workflow
TkymHrt 76edb82
[fix] コンポーネントの調整
TkymHrt e94e015
[fix] モックの作成とコンポーネントへのリファクタリング
TkymHrt 89c0b13
[fix] テーブル構造の修正
TkymHrt 084eae8
[feat] nuqsの導入
TkymHrt e87c8b4
[feat] nuqsを使用する実装に修正
TkymHrt File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
192 changes: 192 additions & 0 deletions
192
view/next-project/src/components/budget_managements/BudgetManagement.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
import { useQueryStates, parseAsInteger } from 'nuqs'; | ||
import { useState, useEffect } from 'react'; | ||
import { | ||
Department, | ||
Division, | ||
Item, | ||
fetchDepartments, | ||
fetchDivisions, | ||
fetchItems, | ||
} from './mockApi'; | ||
import { Card, EditButton, AddButton, Title } from '@/components/common'; | ||
import PrimaryButton from '@/components/common/OutlinePrimaryButton/OutlinePrimaryButton'; | ||
|
||
export default function BudgetManagement() { | ||
const [departments, setDepartments] = useState<Department[]>([]); | ||
const [divisions, setDivisions] = useState<Division[]>([]); | ||
const [items, setItems] = useState<Item[]>([]); | ||
|
||
const [{ departmentId, divisionId }, setQueryState] = useQueryStates({ | ||
departmentId: parseAsInteger.withOptions({ history: 'push', shallow: true }), | ||
divisionId: parseAsInteger.withOptions({ history: 'push', shallow: true }), | ||
}); | ||
|
||
useEffect(() => { | ||
fetchDepartments().then(setDepartments); | ||
}, []); | ||
|
||
// FIXME: APIが実装されたら、修正する。 | ||
useEffect(() => { | ||
if (departmentId !== null) { | ||
fetchDivisions(departmentId).then(setDivisions); | ||
setItems([]); | ||
} else { | ||
setDivisions([]); | ||
setQueryState({ divisionId: null }); | ||
setItems([]); | ||
} | ||
}, [departmentId]); | ||
|
||
useEffect(() => { | ||
if (divisionId !== null) { | ||
fetchItems(divisionId).then(setItems); | ||
} else { | ||
setItems([]); | ||
} | ||
}, [divisionId]); | ||
|
||
// FIXME: any型はAPIのレスポンスに合わせて変更する。 | ||
let displayItems: any[] = []; | ||
let title = '購入報告'; | ||
const showBudgetColumns = true; | ||
|
||
if (divisionId !== null) { | ||
displayItems = items; | ||
title = '申請物品'; | ||
} else if (departmentId !== null) { | ||
displayItems = divisions; | ||
title = '申請部門'; | ||
} else { | ||
displayItems = departments; | ||
title = '申請局'; | ||
} | ||
|
||
const totalBudget = displayItems.reduce((sum, item) => sum + (item.budget || 0), 0); | ||
const totalUsed = displayItems.reduce((sum, item) => sum + (item.used || 0), 0); | ||
const totalRemaining = displayItems.reduce((sum, item) => sum + (item.remaining || 0), 0); | ||
|
||
const handleDepartmentChange = (e: React.ChangeEvent<HTMLSelectElement>) => { | ||
const deptId = e.target.value ? parseInt(e.target.value, 10) : null; | ||
setQueryState({ departmentId: deptId, divisionId: null }); | ||
}; | ||
|
||
const handleDivisionChange = (e: React.ChangeEvent<HTMLSelectElement>) => { | ||
const divId = e.target.value ? parseInt(e.target.value, 10) : null; | ||
setQueryState({ divisionId: divId }); | ||
}; | ||
|
||
// FIXME: any型はAPIのレスポンスに合わせて変更する。 | ||
const handleRowClick = (item: any) => { | ||
if (departmentId === null) { | ||
setQueryState({ departmentId: item.id, divisionId: null }); | ||
} else if (divisionId === null) { | ||
setQueryState({ divisionId: item.id }); | ||
} | ||
}; | ||
|
||
return ( | ||
<Card> | ||
<div className='px-4 py-10'> | ||
<div className='flex-start mb-4 flex'> | ||
<Title>予算管理ページ</Title> | ||
</div> | ||
<div className='mb-4 flex flex-col items-center md:flex-row md:justify-between'> | ||
<div className='flex flex-col gap-4 text-nowrap py-2'> | ||
<div className='flex gap-3'> | ||
<span className='text-base font-light'>申請する局</span> | ||
<select | ||
value={departmentId ?? ''} | ||
onChange={handleDepartmentChange} | ||
className='border-b border-black-300 focus:outline-none' | ||
> | ||
<option value=''>ALL</option> | ||
{departments.map((dept) => ( | ||
<option key={dept.id} value={dept.id}> | ||
{dept.name} | ||
</option> | ||
))} | ||
</select> | ||
</div> | ||
<div className={`flex gap-3 ${departmentId !== null ? 'visible' : 'invisible'}`}> | ||
<span className='text-base font-light'>申請する部門</span> | ||
<select | ||
value={divisionId ?? ''} | ||
onChange={handleDivisionChange} | ||
className='border-b border-black-300 focus:outline-none' | ||
> | ||
<option value=''>ALL</option> | ||
{divisions.map((div) => ( | ||
<option key={div.id} value={div.id}> | ||
{div.name} | ||
</option> | ||
))} | ||
</select> | ||
</div> | ||
</div> | ||
<div className='mt-2 flex w-full flex-col gap-1 md:w-fit md:flex-row md:gap-3'> | ||
<PrimaryButton className='w-full md:w-fit'>CSVダウンロード</PrimaryButton> | ||
<AddButton className='w-full md:w-fit'>{title}登録</AddButton> | ||
</div> | ||
</div> | ||
<div className='mt-5 overflow-x-auto'> | ||
<table className='w-full table-auto border-collapse text-nowrap'> | ||
<thead> | ||
<tr className='border border-x-white-0 border-b-primary-1 border-t-white-0 py-3'> | ||
<th className='w-1/4 pb-2 text-center font-medium text-black-600'>{title}</th> | ||
{showBudgetColumns && ( | ||
<> | ||
<th className='w-1/4 pb-2 text-center font-medium text-black-600'>予算</th> | ||
<th className='w-1/4 pb-2 text-center font-medium text-black-600'>使用額</th> | ||
<th className='w-1/4 pb-2 text-center font-medium text-black-600'>残高</th> | ||
</> | ||
)} | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{displayItems.map((item, index) => ( | ||
<tr | ||
key={item.id} | ||
className={`cursor-pointer ${ | ||
index !== displayItems.length - 1 ? 'border-b' : '' | ||
}`} | ||
onClick={() => handleRowClick(item)} | ||
> | ||
<td className='flex justify-center gap-2 py-3'> | ||
<div className='text-center text-primary-1 underline'>{item.name}</div> | ||
<EditButton /> | ||
</td> | ||
|
||
{showBudgetColumns && ( | ||
<> | ||
<td className='py-3 text-center'>{item.budget}</td> | ||
<td className='py-3 text-center'>{item.used}</td> | ||
<td className='py-3 text-center'>{item.remaining}</td> | ||
</> | ||
)} | ||
</tr> | ||
))} | ||
{showBudgetColumns && displayItems.length > 0 && ( | ||
<tr className='border border-x-white-0 border-b-white-0 border-t-primary-1'> | ||
<td className='py-3 text-center font-bold'>合計</td> | ||
<td className='py-3 text-center font-bold'>{totalBudget}</td> | ||
<td className='py-3 text-center font-bold'>{totalUsed}</td> | ||
<td className='py-3 text-center font-bold'>{totalRemaining}</td> | ||
</tr> | ||
)} | ||
{displayItems.length === 0 && ( | ||
<tr> | ||
<td | ||
colSpan={showBudgetColumns ? 4 : 1} | ||
className='text-gray-500 px-4 py-6 text-center text-sm' | ||
> | ||
データがありません | ||
</td> | ||
</tr> | ||
)} | ||
</tbody> | ||
</table> | ||
</div> | ||
</div> | ||
</Card> | ||
); | ||
} |
100 changes: 100 additions & 0 deletions
100
view/next-project/src/components/budget_managements/mockApi.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
export interface Department { | ||
id: number; | ||
name: string; | ||
budget: number; | ||
used: number; | ||
remaining: number; | ||
} | ||
|
||
export interface Division { | ||
id: number; | ||
name: string; | ||
departmentId: number; | ||
budget: number; | ||
used: number; | ||
remaining: number; | ||
} | ||
|
||
export interface Item { | ||
id: number; | ||
name: string; | ||
divisionId: number; | ||
budget: number; | ||
used: number; | ||
remaining: number; | ||
} | ||
|
||
const departments: Department[] = [ | ||
{ id: 1, name: '制作局', budget: 20000, used: 5000, remaining: 15000 }, | ||
{ id: 2, name: '渉外局', budget: 18000, used: 4000, remaining: 14000 }, | ||
{ id: 3, name: '企画局', budget: 22000, used: 6000, remaining: 16000 }, | ||
{ id: 4, name: '財務局', budget: 25000, used: 5500, remaining: 19500 }, | ||
{ id: 5, name: '情報局', budget: 21000, used: 7000, remaining: 14000 }, | ||
{ id: 6, name: '総務局', budget: 23000, used: 4500, remaining: 18500 }, | ||
]; | ||
|
||
const divisions: Division[] = [ | ||
{ id: 1, name: '制作部門A', departmentId: 1, budget: 10000, used: 3000, remaining: 7000 }, | ||
{ id: 2, name: '制作部門B', departmentId: 1, budget: 10000, used: 2000, remaining: 8000 }, | ||
{ id: 3, name: '渉外部門A', departmentId: 2, budget: 9000, used: 4000, remaining: 5000 }, | ||
{ id: 4, name: '渉外部門B', departmentId: 2, budget: 9000, used: 0, remaining: 9000 }, | ||
{ id: 5, name: '企画部門A', departmentId: 3, budget: 11000, used: 5000, remaining: 6000 }, | ||
{ id: 6, name: '企画部門B', departmentId: 3, budget: 11000, used: 1000, remaining: 10000 }, | ||
{ id: 7, name: '財務部門A', departmentId: 4, budget: 12500, used: 3000, remaining: 9500 }, | ||
{ id: 8, name: '財務部門B', departmentId: 4, budget: 12500, used: 2500, remaining: 10000 }, | ||
{ id: 9, name: '情報部門A', departmentId: 5, budget: 10500, used: 4000, remaining: 6500 }, | ||
{ id: 10, name: '情報部門B', departmentId: 5, budget: 10500, used: 3000, remaining: 7500 }, | ||
{ id: 11, name: '総務部門A', departmentId: 6, budget: 11500, used: 2000, remaining: 9500 }, | ||
{ id: 12, name: '総務部門B', departmentId: 6, budget: 11500, used: 2500, remaining: 9000 }, | ||
]; | ||
|
||
const items: Item[] = [ | ||
{ id: 1, name: '物品A', divisionId: 1, budget: 5000, used: 1000, remaining: 4000 }, | ||
{ id: 2, name: '物品B', divisionId: 1, budget: 5000, used: 500, remaining: 4500 }, | ||
{ id: 3, name: '物品C', divisionId: 2, budget: 5000, used: 2000, remaining: 3000 }, | ||
{ id: 4, name: '物品D', divisionId: 2, budget: 5000, used: 0, remaining: 5000 }, | ||
{ id: 5, name: '物品E', divisionId: 3, budget: 5000, used: 3000, remaining: 2000 }, | ||
{ id: 6, name: '物品F', divisionId: 3, budget: 5000, used: 500, remaining: 4500 }, | ||
{ id: 7, name: '物品G', divisionId: 4, budget: 5000, used: 2000, remaining: 3000 }, | ||
{ id: 8, name: '物品H', divisionId: 4, budget: 5000, used: 1500, remaining: 3500 }, | ||
{ id: 9, name: '物品I', divisionId: 5, budget: 5000, used: 4000, remaining: 1000 }, | ||
{ id: 10, name: '物品J', divisionId: 5, budget: 5000, used: 3000, remaining: 2000 }, | ||
{ id: 11, name: '物品K', divisionId: 6, budget: 5000, used: 1000, remaining: 4000 }, | ||
{ id: 12, name: '物品L', divisionId: 6, budget: 5000, used: 1500, remaining: 3500 }, | ||
{ id: 13, name: '物品M', divisionId: 7, budget: 5000, used: 3000, remaining: 2000 }, | ||
{ id: 14, name: '物品N', divisionId: 7, budget: 5000, used: 2000, remaining: 3000 }, | ||
{ id: 15, name: '物品O', divisionId: 8, budget: 5000, used: 1000, remaining: 4000 }, | ||
{ id: 16, name: '物品P', divisionId: 8, budget: 5000, used: 2500, remaining: 2500 }, | ||
{ id: 17, name: '物品Q', divisionId: 9, budget: 5000, used: 4000, remaining: 1000 }, | ||
{ id: 18, name: '物品R', divisionId: 9, budget: 5000, used: 3000, remaining: 2000 }, | ||
{ id: 19, name: '物品S', divisionId: 10, budget: 5000, used: 1000, remaining: 4000 }, | ||
{ id: 20, name: '物品T', divisionId: 10, budget: 5000, used: 2500, remaining: 2500 }, | ||
{ id: 21, name: '物品U', divisionId: 11, budget: 5000, used: 4000, remaining: 1000 }, | ||
{ id: 22, name: '物品V', divisionId: 11, budget: 5000, used: 3000, remaining: 2000 }, | ||
{ id: 23, name: '物品W', divisionId: 12, budget: 5000, used: 1000, remaining: 4000 }, | ||
{ id: 24, name: '物品X', divisionId: 12, budget: 5000, used: 2500, remaining: 2500 }, | ||
]; | ||
|
||
export const fetchDepartments = async (): Promise<Department[]> => { | ||
return new Promise((resolve) => { | ||
setTimeout(() => { | ||
resolve(departments); | ||
}); | ||
}); | ||
}; | ||
|
||
export const fetchDivisions = async (departmentId: number): Promise<Division[]> => { | ||
return new Promise((resolve) => { | ||
setTimeout(() => { | ||
resolve(divisions.filter((division) => division.departmentId === departmentId)); | ||
}); | ||
}); | ||
}; | ||
|
||
export const fetchItems = async (divisionId: number): Promise<Item[]> => { | ||
return new Promise((resolve) => { | ||
setTimeout(() => { | ||
resolve(items.filter((item) => item.divisionId === divisionId)); | ||
}); | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of 'any' type for 'displayItems' can lead to potential type safety issues. Consider using a more specific type.
Copilot is powered by AI, so mistakes are possible. Review output carefully before use.