Skip to content
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
wants to merge 12 commits into
base: develop
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import { useRouter } from 'next/router';
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 router = useRouter();
const { departmentId, divisionId } = router.query;

const [departments, setDepartments] = useState<Department[]>([]);
const [divisions, setDivisions] = useState<Division[]>([]);
const [items, setItems] = useState<Item[]>([]);

const [selectedDepartmentId, setSelectedDepartmentId] = useState(departmentId || '');
const [selectedDivisionId, setSelectedDivisionId] = useState(divisionId || '');

useEffect(() => {
fetchDepartments().then(setDepartments);
}, []);

useEffect(() => {
setSelectedDepartmentId(departmentId || '');
}, [departmentId]);

useEffect(() => {
setSelectedDivisionId(divisionId || '');
}, [divisionId]);

useEffect(() => {
if (selectedDepartmentId) {
fetchDivisions(Number(selectedDepartmentId)).then(setDivisions);
setItems([]);
} else {
setDivisions([]);
setSelectedDivisionId('');
setItems([]);
}

router.push(
{
pathname: router.pathname,
query: {
...router.query,
departmentId: selectedDepartmentId || undefined,
divisionId: undefined,
},
},
undefined,
{ shallow: true },
);
}, [selectedDepartmentId]);

useEffect(() => {
if (selectedDivisionId) {
fetchItems(Number(selectedDivisionId)).then(setItems);
} else {
setItems([]);
}

router.push(
{
pathname: router.pathname,
query: {
...router.query,
departmentId: selectedDepartmentId || undefined,
divisionId: selectedDivisionId || undefined,
},
},
undefined,
{ shallow: true },
);
}, [selectedDivisionId]);

let displayItems: any[] = [];
Copy link
Preview

Copilot AI Dec 2, 2024

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.

Suggested change
let displayItems: any[] = [];
let displayItems: (Department | Division | Item)[] = [];

Copilot is powered by AI, so mistakes are possible. Review output carefully before use.

Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
let title = '購入報告';
const showBudgetColumns = true;

if (selectedDivisionId) {
displayItems = items;
title = '申請物品';
} else if (selectedDepartmentId) {
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;
setSelectedDepartmentId(deptId);
setSelectedDivisionId('');
};

const handleDivisionChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const divId = e.target.value;
setSelectedDivisionId(divId);
};

const handleRowClick = (item: any) => {
if (!selectedDepartmentId) {
setSelectedDepartmentId(item.id);
setSelectedDivisionId('');
} else if (!selectedDivisionId) {
setSelectedDivisionId(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'>
<div className='flex gap-3'>
<span className='text-base font-light'>申請する局</span>
<select
value={selectedDepartmentId}
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 ${selectedDepartmentId ? 'visible' : 'invisible'}`}>
<span className='text-base font-light'>申請する部門</span>
<select
value={selectedDivisionId}
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'>
<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)}
>
<div className='flex justify-center gap-2 py-3'>
Copy link
Preview

Copilot AI Dec 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Div elements are not valid children of tr elements. This should be replaced with a valid table structure.

Suggested change
<div className='flex justify-center gap-2 py-3'>
<td className='flex justify-center gap-2 py-3'>

Copilot is powered by AI, so mistakes are possible. Review output carefully before use.

Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
<td className='text-center text-primary-1 underline'>{item.name}</td>
<EditButton />
</div>

{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 view/next-project/src/components/budget_managements/mockApi.ts
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));
});
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface Props {

function PrimaryButton(props: Props): JSX.Element {
const className =
'px-4 py-2 text-primary-1 font-bold text-md rounded-lg bg-white-0 border border-primary-1 hover:bg-white-100 hover:text-primary-2 hover:border-primary-2' +
'flex justify-center px-4 py-2 text-primary-1 font-bold text-md rounded-lg bg-white-0 border border-primary-1 hover:bg-white-100 hover:text-primary-2 hover:border-primary-2' +
(props.className ? ` ${props.className}` : '');
return (
<button className={clsx(className)} onClick={props.onClick}>
Expand Down
5 changes: 5 additions & 0 deletions view/next-project/src/constants/linkItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ export const FinanceLinkItems: LinkItemProps[] = [
icon: <HiCurrencyDollar className='mx-2 text-xl' />,
href: '/budgets',
},
{
name: '予算管理',
icon: <HiCurrencyDollar className='mx-2 text-xl' />,
href: '/budget_managements',
},
{
name: '学内募金',
icon: <MdOutlineSavings className='mx-2 text-xl' />,
Expand Down
10 changes: 10 additions & 0 deletions view/next-project/src/pages/budget_managements/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import BudgetManagement from '@/components/budget_managements/BudgetManagement';
import MainLayout from '@/components/layout/MainLayout';

export default function Home() {
return (
<MainLayout>
<BudgetManagement />
</MainLayout>
);
}