Skip to content

Commit

Permalink
Merge pull request stakwork#723 from aliraza556/Auto-refresh-Ticket-D…
Browse files Browse the repository at this point in the history
…etails

feat(TicketEditor): Implement Real-Time Ticket Editing with WebSocket Integration
  • Loading branch information
humansinstitute authored Dec 7, 2024
2 parents 2221634 + 3ef5862 commit a79becd
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 89 deletions.
43 changes: 15 additions & 28 deletions src/components/common/TicketEditor/TicketEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react';
import React, { useState } from 'react';
import { observer } from 'mobx-react-lite';
import { useStores } from 'store';
import { EuiGlobalToastList } from '@elastic/eui';
import { phaseTicketStore } from '../../../store/phase';
Expand All @@ -10,38 +11,20 @@ import {
TicketInput,
TicketHeaderInputWrap
} from '../../../pages/tickets/style';
import { TicketStatus } from '../../../store/interface';
import { TicketStatus, Ticket } from '../../../store/interface';
import { Toast } from '../../../people/widgetViews/workspace/interface';

interface TicketEditorProps {
ticketData: {
uuid: string;
feature_uuid: string;
phase_uuid: string;
name: string;
sequence: number;
dependency: string[];
description: string;
status: string;
version: number;
number: number;
};
ticketData: Ticket;
websocketSessionId: string;
}

const TicketEditor = ({ ticketData, websocketSessionId }: TicketEditorProps) => {
const [name, setName] = useState(ticketData.name || 'Ticket');
const [description, setDescription] = useState(ticketData.description || '');
const TicketEditor = observer(({ ticketData, websocketSessionId }: TicketEditorProps) => {
const [toasts, setToasts] = useState<Toast[]>([]);
const { main } = useStores();

useEffect(() => {
const storedTicket = phaseTicketStore.getTicket(ticketData.uuid);
if (storedTicket) {
setDescription(storedTicket.description || '');
setName(storedTicket.name || 'Ticket');
}
}, [ticketData.uuid]);
const currentTicket = phaseTicketStore.getTicket(ticketData.uuid);
const name = currentTicket?.name || 'Ticket';
const description = currentTicket?.description || '';

const addUpdateSuccessToast = () => {
setToasts([
Expand Down Expand Up @@ -151,13 +134,17 @@ const TicketEditor = ({ ticketData, websocketSessionId }: TicketEditorProps) =>
<TicketHeader>Ticket:</TicketHeader>
<TicketInput
value={name}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setName(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
phaseTicketStore.updateTicket(ticketData.uuid, { name: e.target.value })
}
placeholder="Enter ticket name..."
/>
</TicketHeaderInputWrap>
<TicketTextArea
value={description}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setDescription(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
phaseTicketStore.updateTicket(ticketData.uuid, { description: e.target.value })
}
placeholder="Enter ticket details..."
/>
<TicketButtonGroup>
Expand All @@ -179,6 +166,6 @@ const TicketEditor = ({ ticketData, websocketSessionId }: TicketEditorProps) =>
/>
</TicketContainer>
);
};
});

export default TicketEditor;
67 changes: 18 additions & 49 deletions src/people/widgetViews/PhasePlannerView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useCallback, useEffect, useState } from 'react';
import { observer } from 'mobx-react-lite';
import { useParams, useHistory } from 'react-router-dom';
import { Feature, Ticket, TicketMessage } from 'store/interface';
import { Feature, Ticket, TicketMessage, TicketStatus } from 'store/interface';
import MaterialIcon from '@material/react-material-icon';
import TicketEditor from 'components/common/TicketEditor/TicketEditor';
import { useStores } from 'store';
Expand Down Expand Up @@ -30,27 +31,14 @@ interface PhasePlannerParams {
phase_uuid: string;
}

interface TicketData {
uuid: string;
feature_uuid: string;
phase_uuid: string;
name: string;
sequence: number;
dependency: string[];
description: string;
status: string;
version: number;
number: number;
}

const PhasePlannerView: React.FC = () => {
const PhasePlannerView: React.FC = observer(() => {
const { feature_uuid, phase_uuid } = useParams<PhasePlannerParams>();
const [featureData, setFeatureData] = useState<Feature | null>(null);
const [phaseData, setPhaseData] = useState<Phase | null>(null);
const [ticketData, setTicketData] = useState<TicketData[]>([]);
const [websocketSessionId, setWebsocketSessionId] = useState<string>('');
const { main } = useStores();
const history = useHistory();
const tickets = phaseTicketStore.getPhaseTickets(phase_uuid);

useEffect(() => {
const socket = createSocketInstance();
Expand All @@ -62,7 +50,6 @@ const PhasePlannerView: React.FC = () => {
const refreshSingleTicket = async (ticketUuid: string) => {
try {
const ticket = await main.getTicketDetails(ticketUuid);

phaseTicketStore.updateTicket(ticket.uuid, ticket);
} catch (error) {
console.error('Error on refreshing ticket:', error);
Expand Down Expand Up @@ -117,21 +104,18 @@ const PhasePlannerView: React.FC = () => {
const getFeatureData = useCallback(async () => {
if (!feature_uuid) return;
const data = await main.getFeaturesByUuid(feature_uuid);

return data;
}, [feature_uuid, main]);

const getPhaseData = useCallback(async () => {
if (!feature_uuid || !phase_uuid) return;
const data = await main.getFeaturePhaseByUUID(feature_uuid, phase_uuid);

return data;
}, [feature_uuid, phase_uuid, main]);

const getPhaseTickets = useCallback(async () => {
if (!feature_uuid || !phase_uuid) return;
const data = await main.getTicketDataByPhase(feature_uuid, phase_uuid);

return data;
}, [feature_uuid, phase_uuid, main]);

Expand All @@ -145,19 +129,18 @@ const PhasePlannerView: React.FC = () => {
if (!feature || !phase || !Array.isArray(phaseTickets)) {
history.push('/');
} else {
const parsedTicketData: TicketData[] = [];
for (let i = 0; i < phaseTickets.length; i++) {
const ticket = phaseTickets[i];
// Clear existing tickets for this phase before adding new ones
phaseTicketStore.clearPhaseTickets(phase_uuid);

// Add fetched tickets
for (const ticket of phaseTickets) {
if (ticket.UUID) {
ticket.uuid = ticket.UUID;
}

phaseTicketStore.addTicket(ticket);
parsedTicketData.push({ ...ticket });
}
setFeatureData(feature);
setPhaseData(phase);
setTicketData(parsedTicketData);
}
} catch (error) {
console.error('Error fetching data:', error);
Expand All @@ -166,7 +149,7 @@ const PhasePlannerView: React.FC = () => {
};

fetchData();
}, [getFeatureData, getPhaseData, history, getPhaseTickets]);
}, [getFeatureData, getPhaseData, history, getPhaseTickets, phase_uuid]);

const handleClose = () => {
history.push(`/feature/${feature_uuid}`);
Expand All @@ -179,11 +162,12 @@ const PhasePlannerView: React.FC = () => {
feature_uuid,
phase_uuid,
name: '',
sequence: 0,
sequence: tickets.length + 1,
dependency: [],
description: '',
status: 'DRAFT',
version: 1
status: 'DRAFT' as TicketStatus,
version: 1,
number: tickets.length + 1
};

const ticketPayload = {
Expand All @@ -197,21 +181,6 @@ const PhasePlannerView: React.FC = () => {
try {
await main.createUpdateTicket(ticketPayload);
phaseTicketStore.addTicket(initialTicketData as Ticket);
setTicketData((prevTickets: TicketData[]) => [
...prevTickets,
{
uuid: newTicketUuid,
feature_uuid,
phase_uuid,
name: '',
sequence: prevTickets.length + 1,
dependency: [],
description: '',
status: 'DRAFT',
version: 1,
number: prevTickets.length + 1
}
]);
} catch (error) {
console.error('Error registering ticket:', error);
}
Expand Down Expand Up @@ -241,10 +210,10 @@ const PhasePlannerView: React.FC = () => {
<PhaseLabel>
Phase: <LabelValue>{phaseData?.name}</LabelValue>
</PhaseLabel>
{ticketData.map((ticketData: TicketData) => (
{tickets.map((ticket: Ticket) => (
<TicketEditor
key={ticketData.uuid}
ticketData={ticketData}
key={ticket.uuid}
ticketData={ticket}
websocketSessionId={websocketSessionId}
/>
))}
Expand All @@ -262,6 +231,6 @@ const PhasePlannerView: React.FC = () => {
</FeatureDataWrap>
</FeatureBody>
);
};
});

export default PhasePlannerView;
15 changes: 3 additions & 12 deletions src/store/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ import {
FeatureStory,
UpdateFeatureStoryInput,
CreateFeatureStoryInput,
TicketPayload
TicketPayload,
Ticket
} from './interface';

function makeTorSaveURL(host: string, key: string) {
Expand Down Expand Up @@ -3683,17 +3684,7 @@ export class MainStore {
source: string;
id: string;
};
ticket: {
uuid: string;
feature_uuid: string;
phase_uuid: string;
name: string;
sequence: number;
dependency: string[];
description: string;
status: string;
version: number;
};
ticket: Ticket;
}): Promise<any> {
try {
if (!uiStore.meInfo) return [];
Expand Down
9 changes: 9 additions & 0 deletions src/store/phase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface TicketStore {
updateTicket: (uuid: string, ticket: Partial<Ticket>) => void;
getTicket: (uuid: string) => Ticket | undefined;
getPhaseTickets: (phase_uuid: string) => Ticket[];
clearPhaseTickets: (phase_uuid: string) => void;
}

export class PhaseTicketStore implements TicketStore {
Expand Down Expand Up @@ -51,6 +52,14 @@ export class PhaseTicketStore implements TicketStore {
getPhaseTickets(phase_uuid: string): Ticket[] {
return this.phaseTickets[phase_uuid] || [];
}

clearPhaseTickets(phase_uuid: string) {
const phaseTickets = this.phaseTickets[phase_uuid] || [];
phaseTickets.forEach((ticket: Ticket) => {
this.tickets.delete(ticket.uuid);
});
this.phaseTickets[phase_uuid] = [];
}
}

export const phaseTicketStore = new PhaseTicketStore();

0 comments on commit a79becd

Please sign in to comment.