Skip to content

Commit

Permalink
feat(Estimate): change relation between Goal and Estimate
Browse files Browse the repository at this point in the history
fix(GoalEstimate): `connectOrCreate` instead of `create` new record of `estimate`
fix(EstimateComboBox): correct localized value
feat(GoalHistory): recording changes of estimate
  • Loading branch information
LamaEats committed Jun 2, 2023
1 parent 832587d commit b531f61
Show file tree
Hide file tree
Showing 10 changed files with 276 additions and 67 deletions.
34 changes: 34 additions & 0 deletions prisma/migrations/20230601105643_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
-- DropForeignKey
ALTER TABLE "Estimate" DROP CONSTRAINT "Estimate_goalId_fkey";

-- DropIndex
DROP INDEX "Estimate_goalId_idx";

-- CreateTable
CREATE TABLE "EstimateToGoal" (
"id" SERIAL NOT NULL,
"goalId" TEXT NOT NULL,
"estimateId" INTEGER NOT NULL,
"createadAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "EstimateToGoal_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE INDEX "EstimateToGoal_goalId_idx" ON "EstimateToGoal"("goalId");

-- CreateIndex
CREATE INDEX "EstimateToGoal_estimateId_idx" ON "EstimateToGoal"("estimateId");

-- CreateIndex
CREATE INDEX "Estimate_date_idx" ON "Estimate"("date");

-- AddForeignKey
ALTER TABLE "EstimateToGoal" ADD CONSTRAINT "EstimateToGoal_goalId_fkey" FOREIGN KEY ("goalId") REFERENCES "Goal"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "EstimateToGoal" ADD CONSTRAINT "EstimateToGoal_estimateId_fkey" FOREIGN KEY ("estimateId") REFERENCES "Estimate"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- Fill join table
INSERT INTO "EstimateToGoal" ("goalId", "estimateId", "createdAt")
SELECT "goalId", "id", "createdAt" from "Estimate"
48 changes: 30 additions & 18 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -97,26 +97,26 @@ model Ghost {
}

model Activity {
id String @id @default(cuid())
ghost Ghost? @relation(fields: [ghostId], references: [id])
ghostId String? @unique
id String @id @default(cuid())
ghost Ghost? @relation(fields: [ghostId], references: [id])
ghostId String? @unique
user User?
filters Filter[]
comments Comment[]
reactions Reaction[]
projects Project[]
estimates Estimate[]
projectParticipant Project[] @relation("projectParticipants")
goalParticipant Goal[] @relation("goalParticipants")
goalWatchers Goal[] @relation("goalWatchers")
goalStargizers Goal[] @relation("goalStargizers")
projectWatchers Project[] @relation("projectWatchers")
projectStargizers Project[] @relation("projectStargizers")
filterStargizers Filter[] @relation("filterStargizers")
goalOwner Goal[] @relation("goalOwner")
goalIssuer Goal[] @relation("goalIssuer")
settings Settings @relation(fields: [settingsId], references: [id])
settingsId String @unique
projectParticipant Project[] @relation("projectParticipants")
goalParticipant Goal[] @relation("goalParticipants")
goalWatchers Goal[] @relation("goalWatchers")
goalStargizers Goal[] @relation("goalStargizers")
projectWatchers Project[] @relation("projectWatchers")
projectStargizers Project[] @relation("projectStargizers")
filterStargizers Filter[] @relation("filterStargizers")
goalOwner Goal[] @relation("goalOwner")
goalIssuer Goal[] @relation("goalIssuer")
settings Settings @relation(fields: [settingsId], references: [id])
settingsId String @unique
tags Tag[]
goalActions GoalHistory[] @relation("goalActions")
Expand Down Expand Up @@ -151,20 +151,32 @@ model Estimate {
q String
y String
date String
goal Goal @relation(fields: [goalId], references: [id])
goal EstimateToGoal[]
goalId String
activity Activity @relation(fields: [activityId], references: [id])
activityId String
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
@@index([date])
}

model EstimateToGoal {
id Int @default(autoincrement()) @id
goal Goal @relation(fields: [goalId], references: [id])
goalId String
estimate Estimate @relation(fields: [estimateId], references: [id])
estimateId Int
createadAt DateTime @default(now())
@@index([goalId])
@@index([estimateId])
}

model Goal {
id String @id @default(cuid())
scopeId Int @default(1)
id String @id @default(cuid())
scopeId Int @default(1)
title String
description String
kind String?
Expand All @@ -173,7 +185,7 @@ model Goal {
private Boolean?
archived Boolean? @default(false)
priority String?
estimate Estimate[]
estimate EstimateToGoal[]
project Project? @relation("projectGoals", fields: [projectId], references: [id])
projectId String?
teamId String?
Expand Down
15 changes: 6 additions & 9 deletions src/components/EstimateComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ interface EstimateComboBoxProps {
date: string;
q: string;
y: string;
id: number;
};
defaultValuePlaceholder: {
date: string;
Expand Down Expand Up @@ -114,24 +115,20 @@ export const EstimateComboBox = React.forwardRef<HTMLDivElement, EstimateComboBo
const onQButtonClick = useCallback(
(nextQ: string) => () => {
setSelectedQ(nextQ);

let newDate = createLocaleDate(endOfQuarter(nextQ), { locale });
// this is trick to avoid no zero before month in the EN locale, ex: 2/10/1990
if (newDate.length === 9) {
newDate = `0${newDate}`;
}

setInputState(newDate);
setChanged(true);
},
[locale],
[],
);

const onInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setChanged(true);
setInputState(e.target.value);
}, []);

useEffect(() => {
setInputState(createLocaleDate(endOfQuarter(selectedQ), { locale }));
}, [selectedQ, locale]);

useEffect(() => {
if (isValidDate(inputState)) {
setSelectedQ(quarterFromDate(parseLocaleDate(inputState, { locale })));
Expand Down
2 changes: 1 addition & 1 deletion src/components/GoalEditForm/GoalEditForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const GoalEditForm: React.FC<GoalEditFormProps> = ({ goal, onSubmit }) => {
state={goal.state!}
priority={goal.priority!}
tags={goal.tags}
estimate={goal.estimate?.length ? goal.estimate[goal.estimate.length - 1] : undefined}
estimate={goal._lastEstimate ?? undefined}
onSumbit={updateGoal}
renderActionButton={({ busy, isValid }) => (
<FormAction right inline>
Expand Down
2 changes: 2 additions & 0 deletions src/schema/goal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const goalCommonSchema = z.object({
date: z.string(),
q: z.string(),
y: z.string(),
id: z.number().nullish(),
})
.optional(),
tags: z
Expand Down Expand Up @@ -134,6 +135,7 @@ export const goalUpdateSchema = z.object({
date: z.string(),
q: z.string(),
y: z.string(),
id: z.number().nullish(),
})
.optional(),
tags: z.array(
Expand Down
16 changes: 13 additions & 3 deletions src/utils/dateTime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,21 @@ export enum quarters {
'Q4' = 'Q4',
}

export const createLocaleDate = (date: Date, { locale }: LocaleArg) => new Intl.DateTimeFormat(locale).format(date);
export const createLocaleDate = (date: Date, { locale }: LocaleArg) =>
new Intl.DateTimeFormat(locale, {
day: '2-digit',
month: '2-digit',
year: 'numeric',
}).format(date);

export const yearFromDate = (date: Date) => date.getFullYear();

export const currentLocaleDate = ({ locale }: LocaleArg) => new Intl.DateTimeFormat(locale).format(new Date());
export const currentLocaleDate = ({ locale }: LocaleArg) =>
new Intl.DateTimeFormat(locale, {
day: '2-digit',
month: '2-digit',
year: 'numeric',
}).format(new Date());

export const endOfQuarter = (q: string) => {
const qToM = {
Expand All @@ -25,7 +35,7 @@ export const endOfQuarter = (q: string) => {
[quarters.Q4]: 11,
};

const abstractDate = new Date().setMonth(qToM[q as quarters]);
const abstractDate = new Date().setMonth(qToM[q as quarters], 0);

const qEndDate = (date: number) => {
const d = new Date(date);
Expand Down
64 changes: 59 additions & 5 deletions src/utils/db.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,46 @@
import { nanoid } from 'nanoid';
import { GoalHistory, PrismaClient } from '@prisma/client';

import { GoalCommon } from '../schema/goal';
import { GoalCommon, GoalUpdate } from '../schema/goal';
import { addCalclulatedGoalsFields } from '../../trpc/queries/goals';

import { prisma } from './prisma';

export const findOrCreateEstimate = async (
estimate: GoalCommon['estimate'] | GoalUpdate['estimate'],
activityId: string,
goalId: string,
) => {
if (!estimate) {
return null;
}

let currentEstimate = { ...estimate };

if (estimate.id == null) {
const { id: _, ...whereParams } = currentEstimate;
const realEstimate = await prisma.estimate.findFirst({
where: whereParams,
});

if (realEstimate) {
currentEstimate.id = realEstimate.id;
} else {
currentEstimate = await prisma.estimate.create({
data: {
date: estimate.date,
y: estimate.y,
q: estimate.q,
activityId,
goalId,
},
});
}
}

return currentEstimate;
};

/**
* Type-safe wrapper in raw SQL query.
* This is only one way to create scopeId in one transaction to avoid id constraints.
Expand Down Expand Up @@ -35,6 +70,8 @@ export const createGoal = async (activityId: string, input: GoalCommon) => {
FROM "Goal" WHERE "projectId" = ${input.parent.id};
`;

const correctEstimate = await findOrCreateEstimate(input.estimate, activityId, id);

const goal = await prisma.goal.update({
where: {
id,
Expand All @@ -45,11 +82,14 @@ export const createGoal = async (activityId: string, input: GoalCommon) => {
connect: input.tags,
}
: undefined,
estimate: input.estimate
estimate: correctEstimate?.id
? {
create: {
...input.estimate,
activityId,
estimate: {
connect: {
id: correctEstimate.id,
},
},
},
}
: undefined,
Expand Down Expand Up @@ -89,9 +129,10 @@ const subjectToTableNameMap: Record<string, keyof PrismaClient> = {
owner: 'activity',
participant: 'activity',
state: 'state',
estimate: 'estimate',
};

export const getGoalHistory = async (history: GoalHistory[]) => {
export const getGoalHistory = async (history: GoalHistory[], goalId: string) => {
const needRequestForRecordIndicies = history.reduce<number[]>((acc, { subject }, index) => {
if (subjectToTableNameMap[subject]) {
acc.push(index);
Expand Down Expand Up @@ -143,6 +184,19 @@ export const getGoalHistory = async (history: GoalHistory[]) => {
});
case 'state':
return prisma.state.findMany({ where: query.where });
case 'estimate':
return prisma.estimate.findMany({
where: {
id: {
in: query.where.id.in.map((v) => Number(v)),
},
goal: {
some: {
goalId,
},
},
},
});
default:
throw new Error('query for history record is undefined');
}
Expand Down
Loading

0 comments on commit b531f61

Please sign in to comment.