mirror of
https://github.com/pikami/scrummie-poker.git
synced 2026-04-17 12:00:07 +01:00
Allow user to import tickets from JIRA via CSV upload
This commit is contained in:
@@ -47,7 +47,7 @@ const Estimation: React.FC = () => {
|
||||
return (
|
||||
<div className="flex h-full">
|
||||
<TaskSidebar
|
||||
className="w-64 overflow-y-scroll bg-gray-50 p-4 dark:bg-nero-800"
|
||||
className="w-96 overflow-y-scroll bg-gray-50 p-4 dark:bg-nero-800"
|
||||
tickets={tickets}
|
||||
onSelectTicket={(ticket) => setActiveTicket(ticket.id)}
|
||||
onAddTicket={() => setIsDrawerOpen(true)}
|
||||
|
||||
@@ -14,12 +14,12 @@ const PlayerList: React.FC<PlayerListProps> = ({
|
||||
title = 'Players',
|
||||
}) => {
|
||||
return (
|
||||
<div className="w-full max-w-sm rounded-lg bg-white p-6 shadow-lg dark:bg-nero-800">
|
||||
<div className="flex w-full max-w-sm flex-col justify-between rounded-lg bg-white p-6 shadow-lg dark:bg-nero-800">
|
||||
<h2 className="mb-4 text-xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
{title}
|
||||
</h2>
|
||||
|
||||
<ul className="max-h-48 divide-y divide-gray-300 overflow-y-auto dark:divide-gray-600">
|
||||
<ul className="flex-grow divide-y divide-gray-300 overflow-y-auto dark:divide-gray-600">
|
||||
{players.length > 0 ? (
|
||||
players.map((player) => (
|
||||
<li
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Card, GridList } from '../../../components';
|
||||
import classNames from 'classnames';
|
||||
import { Button, Card, Drawer, GridList } from '../../../components';
|
||||
import { EstimationSessionTicket } from '../../../lib/types/entityModels';
|
||||
import TicketImportForm from './TicketImportForm';
|
||||
import { useState } from 'react';
|
||||
|
||||
interface TaskSidebarProps {
|
||||
className?: string;
|
||||
@@ -16,9 +19,16 @@ const TaskSidebar: React.FC<TaskSidebarProps> = ({
|
||||
onAddTicket,
|
||||
onEditTicket,
|
||||
}) => {
|
||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||
const containerClassName = classNames(
|
||||
className,
|
||||
'flex flex-col justify-between',
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={containerClassName}>
|
||||
<GridList
|
||||
className="no-scrollbar overflow-y-scroll"
|
||||
items={tickets}
|
||||
colNum={1}
|
||||
itemComponent={({ item }) => (
|
||||
@@ -30,8 +40,15 @@ const TaskSidebar: React.FC<TaskSidebarProps> = ({
|
||||
onEdit={() => onEditTicket(item.id)}
|
||||
/>
|
||||
)}
|
||||
addItemLabel="+ Add Ticket"
|
||||
onAddItem={onAddTicket}
|
||||
/>
|
||||
<Button className="mt-2" fullWidth onClick={() => setIsDrawerOpen(true)}>
|
||||
Import Tickets
|
||||
</Button>
|
||||
<Drawer isOpen={isDrawerOpen} onClose={() => setIsDrawerOpen(false)}>
|
||||
<TicketImportForm onTicketsImported={() => setIsDrawerOpen(false)} />
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
87
src/pages/Estimation/components/TicketImportForm.tsx
Normal file
87
src/pages/Estimation/components/TicketImportForm.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
handleTicketFileUpload,
|
||||
TicketFileUploadResponse,
|
||||
} from '../../../lib/parsers/ticketUpload';
|
||||
import { EstimationSessionTicket } from '../../../lib/types/entityModels';
|
||||
import { Button, Card, GridList, Loader } from '../../../components';
|
||||
import HtmlEmbed from '../../../components/HtmlEmbed';
|
||||
import { useEstimationContext } from '../../../lib/context/estimation';
|
||||
|
||||
interface TicketImportFormProps {
|
||||
onTicketsImported: () => void;
|
||||
}
|
||||
|
||||
const TicketImportForm: React.FC<TicketImportFormProps> = ({
|
||||
onTicketsImported,
|
||||
}) => {
|
||||
const [error, setError] = useState<string>('');
|
||||
const [tickets, setTickets] = useState<EstimationSessionTicket[]>([]);
|
||||
const estimationContext = useEstimationContext();
|
||||
|
||||
if (!estimationContext) {
|
||||
return <Loader center fullHeight />;
|
||||
}
|
||||
|
||||
const { createTickets } = estimationContext;
|
||||
|
||||
const onParsedTickets = ({ tickets, error }: TicketFileUploadResponse) => {
|
||||
if (error) {
|
||||
setError(error);
|
||||
}
|
||||
setTickets(tickets);
|
||||
};
|
||||
|
||||
const onCreateTickets = async () => {
|
||||
await createTickets(tickets);
|
||||
onTicketsImported();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="mb-4 text-xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
Upload Ticket List CSV
|
||||
</h2>
|
||||
|
||||
<input
|
||||
type="file"
|
||||
accept=".csv"
|
||||
onChange={(e) => {
|
||||
setTickets([]);
|
||||
setError('');
|
||||
handleTicketFileUpload(e, onParsedTickets);
|
||||
}}
|
||||
className="mb-4 block w-full text-sm text-gray-900 dark:text-gray-100"
|
||||
/>
|
||||
|
||||
{error && <p className="text-red-500">{error}</p>}
|
||||
|
||||
{tickets.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<h3 className="mb-2 mt-2 text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
Tickets to be imported
|
||||
</h3>
|
||||
<GridList
|
||||
className="no-scrollbar overflow-y-scroll"
|
||||
items={tickets}
|
||||
colNum={1}
|
||||
itemComponent={({ item }) => (
|
||||
<Card
|
||||
key={item.id}
|
||||
title={item.name}
|
||||
description={`Estimate: ${item.estimate || 'N/A'}`}
|
||||
>
|
||||
<HtmlEmbed className="h-16 w-full" body={item.content} />
|
||||
</Card>
|
||||
)}
|
||||
/>
|
||||
<Button className="mt-4" fullWidth onClick={onCreateTickets}>
|
||||
Import
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TicketImportForm;
|
||||
Reference in New Issue
Block a user