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

Add project selection to updates #970

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/app/harbor/shipyard/new-ship-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ export default function NewShipForm({
fetchProjects()
}, [ships])

useEffect(() => {
setUsedRepos(ships.map((ship) => ship.repoUrl))
}, [ships])

const handleForm = async (formData: FormData) => {
setStaging(true)

Expand Down Expand Up @@ -165,6 +169,8 @@ export default function NewShipForm({
description:
"If you're shipping an update to a project, use the 'ship an update' button instead.",
})
setStaging(false)
return
}

const screenshotUrl = formData.get('screenshot_url') as string
Expand Down
122 changes: 112 additions & 10 deletions src/app/harbor/shipyard/new-update-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,53 @@ import { createShipUpdate } from './ship-utils'
import type { Ship } from '@/app/utils/data'
import { Button } from '@/components/ui/button'
import JSConfetti from 'js-confetti'
import { useCallback, useEffect, useRef, useState } from 'react'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { getWakaSessions } from '@/app/utils/waka'
import Icon from '@hackclub/icons'
import { MultiSelect } from '@/components/ui/multi-select'

export default function NewUpdateForm({
shipChain,
canvasRef,
closeForm,
session,
setShips,
ships,
}: {
shipChain: Ship[]
canvasRef: any
closeForm: any
session: any
setShips: any
ships: Ship[]
}) {
const [staging, setStaging] = useState(false)
const [loading, setLoading] = useState(true)
const confettiRef = useRef<JSConfetti | null>(null)
const [projectHours, setProjectHours] = useState<number>(0)
const [projects, setProjects] = useState<
{ key: string; total: number }[] | null
>(null)
const [selectedProjects, setSelectedProjects] = useState<
| [
{
key: string
total: number
},
]
| null
>(null)

const newWakatimeProjects = selectedProjects?.join('$$xXseparatorXx$$') ?? ''
const prevWakatimeProjects =
shipChain[shipChain.length - 1].wakatimeProjectNames?.join(
'$$xXseparatorXx$$',
) ?? ''
let wakatimeProjectNames = prevWakatimeProjects
if (newWakatimeProjects && newWakatimeProjects !== '') {
wakatimeProjectNames =
prevWakatimeProjects + '$$xXseparatorXx$$' + newWakatimeProjects
}

// Initialize confetti on mount
useEffect(() => {
Expand All @@ -40,56 +66,95 @@ export default function NewUpdateForm({
}
}, [])

// Fetch projects from the API using the Slack ID
useEffect(() => {
async function fetchProjects() {
try {
if (sessionStorage.getItem('tutorial') === 'true') {
setProjects([{ key: 'hack-club-site', total: 123 * 60 * 60 }])
} else {
const res = await getWakaSessions()
const shippedShips = ships
.filter((s) => s.shipStatus !== 'deleted')
.flatMap((s) => s.wakatimeProjectNames)
setProjects(
res.projects.filter(
(p: { key: string; total: number }) =>
p.key !== '<<LAST_PROJECT>>' && !shippedShips.includes(p.key),
),
)
}
} catch (error) {
console.error('Error fetching projects:', error)
}
}
fetchProjects()
}, [ships])

const calculateCreditedTime = useCallback(
(
projects: {
key: string
total: number
}[],
newProjects: string[] | null,
): number => {
const shipChainTotalHours = shipChain.reduce(
(acc, curr) => (acc += curr.credited_hours ?? 0),
0,
)
console.log({ shipChain, shipChainTotalHours })
const newProjectsHours = projects
.filter((p) => newProjects?.includes(p.key))
.reduce((acc, curr) => (acc += curr.total ?? 0), 0)

const ps = projects.filter((p) =>
(shipChain[0].wakatimeProjectNames || []).includes(p.key),
(shipChain[shipChain.length - 1].wakatimeProjectNames || []).includes(
p.key,
),
)

if (!ps || ps.length === 0) return 0

const total = ps.reduce((acc, curr) => (acc += curr.total), 0)
const total =
ps.reduce((acc, curr) => (acc += curr.total), 0) + newProjectsHours
const creditedTime = total / 3600 - shipChainTotalHours
return Math.round(creditedTime * 1000) / 1000
},
[shipChain],
)

useEffect(() => {
async function fetchAndSetProjectHours() {
const fetchAndSetProjectHours = useCallback(
async (newProjects: string[] | null) => {
setLoading(true)
const res = await fetchWakaSessions()

if (res && shipChain[0].total_hours) {
let creditedTime = calculateCreditedTime(res.projects)
let creditedTime = calculateCreditedTime(res.projects, newProjects)
console.log('Flow one', { ps: res.projects, creditedTime })

if (creditedTime < 0) {
const anyScopeRes = await fetchWakaSessions('any')
if (anyScopeRes) {
creditedTime = calculateCreditedTime(anyScopeRes.projects)
creditedTime = calculateCreditedTime(
anyScopeRes.projects,
newProjects,
)
console.error('fetchAndSetProjectHours::Flow two', { creditedTime })
}
}

setProjectHours(creditedTime)
}
setLoading(false)
}
},
[fetchWakaSessions, calculateCreditedTime, shipChain],
)

fetchAndSetProjectHours()
}, [fetchWakaSessions, calculateCreditedTime, shipChain])
// Use fetchAndSetProjectHours in useEffect
useEffect(() => {
fetchAndSetProjectHours(null)
}, [fetchAndSetProjectHours])

const handleForm = async (formData: FormData) => {
setStaging(true)
Expand Down Expand Up @@ -122,6 +187,12 @@ export default function NewUpdateForm({
}
}

const projectDropdownList = projects?.map((p: any) => ({
label: `${p.key} (${(p.total / 60 / 60).toFixed(2)} hrs)`,
value: p.key,
icon: () => <Icon glyph="clock" size={24} />,
}))

return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-2">
Expand Down Expand Up @@ -152,6 +223,37 @@ export default function NewUpdateForm({
className="w-full p-2 rounded bg-white/50"
/>

{/* Project Dropdown */}
<div id="project-field">
<label htmlFor="project" className="leading-0">
Select Additional Project
</label>

{projects ? (
<MultiSelect
options={projectDropdownList}
onValueChange={async (p) => {
setSelectedProjects(p)
await fetchAndSetProjectHours(p)
}}
defaultValue={[]}
placeholder="Select projects..."
variant="inverted"
maxCount={3}
/>
) : (
<p>Loading projects...</p>
)}

{/* Hidden input to include in formData */}
<input
type="hidden"
id="wakatime-project-name"
name="wakatime_project_name"
value={wakatimeProjectNames ?? ''}
/>
</div>

<Button
type="submit"
className="w-full"
Expand Down
10 changes: 8 additions & 2 deletions src/app/harbor/shipyard/ship-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ export async function createShip(formData: FormData, isTutorial: boolean) {
update_description: isShipUpdate
? formData.get('updateDescription')
: null,
wakatime_project_name: formData.get('wakatime_project_name'),
wakatime_project_name: formData
.get('wakatime_project_name')
?.toString(),
project_source: isTutorial ? 'tutorial' : 'high_seas',
for_ysws,
},
Expand Down Expand Up @@ -122,6 +124,9 @@ export async function createShipUpdate(
* Secondly, the reshipped_to field on the reshipped ship should be updated to be the new update ship's record ID.
*/

const wakatimeProjectNames =
formData.get('wakatime_project_name')?.toString() || ''

// Step 1:
const res: { id: string; fields: any } = await new Promise(
(resolve, reject) => {
Expand All @@ -142,6 +147,7 @@ export async function createShipUpdate(
: [reshippedFromShip.id],
credited_hours,
for_ysws: reshippedFromShip.yswsType,
wakatime_project_name: wakatimeProjectNames,
},
},
],
Expand Down Expand Up @@ -208,7 +214,7 @@ export async function createShipUpdate(
: [reshippedFromShip.id],
credited_hours,
total_hours: (reshippedFromShip.total_hours ?? 0) + credited_hours,
wakatimeProjectNames: reshippedFromShip.wakatimeProjectNames,
wakatimeProjectNames: wakatimeProjectNames?.split('$$xXseparatorXx$$'),
for_ysws,
}
})
Expand Down
1 change: 1 addition & 0 deletions src/app/harbor/shipyard/ships.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ export default function Ships({
closeForm={() => setNewUpdateShip(null)}
setShips={setShips}
session={session}
ships={ships}
/>
</Modal>

Expand Down
Loading