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

Apps #3495

Merged
merged 30 commits into from
Jan 4, 2025
Merged

Apps #3495

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
9fdc989
chore(deps): bump nanoid from 5.0.1 to 5.0.9
dependabot[bot] Dec 12, 2024
380288e
feat: toggle timesheet IDs based on checkbox state
Innocent-Akim Dec 13, 2024
6e4a96c
feat: toggle timesheet IDs based on checkbox state
Innocent-Akim Dec 13, 2024
7240d6f
Merge pull request #3423 from ever-co/dependabot/npm_and_yarn/nanoid-…
evereq Dec 13, 2024
3a15c2f
Merge pull request #3424 from ever-co/feat/toggle-timesheet-ids-checkbox
evereq Dec 13, 2024
937fad6
chore(deps): bump nanoid from 3.3.6 to 3.3.8 in /apps/extensions
dependabot[bot] Dec 13, 2024
34accd9
Merge pull request #3425 from ever-co/dependabot/npm_and_yarn/apps/ex…
evereq Dec 20, 2024
7f69d43
[Feat]: Improve AddManuel with form handling and error check (#3426)
Innocent-Akim Dec 23, 2024
a53760c
[Feat]: Add Icons For Member work men hours pending task (#3460)
Innocent-Akim Dec 23, 2024
f7bd527
fix dark colors | groupBy report limits select
CREDO23 Dec 23, 2024
1caeaf0
Merge pull request #3469 from ever-co/3466-bug-weekly-limit--group-by…
evereq Dec 23, 2024
7d514db
Merge pull request #3470 from ever-co/develop
evereq Dec 23, 2024
4d902e8
Merge pull request #3471 from ever-co/stage
evereq Dec 23, 2024
ee35122
chore: update license reference (#3468)
emmanuel-ferdman Dec 23, 2024
d0b4649
Merge pull request #3472 from ever-co/develop
evereq Dec 23, 2024
9d339c4
Merge pull request #3473 from ever-co/stage
evereq Dec 23, 2024
05eaa04
[Fix bug] Daily Plan | Same tasks are shown for different users (#3481)
CREDO23 Dec 26, 2024
13e8403
[Feat]: Timesheet Improvements (#3480)
Innocent-Akim Dec 26, 2024
f8efa83
Merge pull request #3482 from ever-co/develop
evereq Dec 26, 2024
1db8ba4
Merge pull request #3483 from ever-co/stage
evereq Dec 26, 2024
e6a9b33
[Feat]: Improve entry selection logic (#3484)
Innocent-Akim Dec 26, 2024
eacf931
[Feat]: Timesheet-Pagination-Hook-and-Component (#3485)
Innocent-Akim Dec 27, 2024
e9ff213
[Feat]: Add Selection/Deselection Functionality based on ID (#3488)
Innocent-Akim Dec 28, 2024
92e20f8
feat: update offline screen for network interruption
Innocent-Akim Dec 28, 2024
2798295
Merge pull request #3489 from ever-co/feat/update-offline-screen-conn…
evereq Dec 28, 2024
4c163c0
Merge pull request #3490 from ever-co/develop
evereq Dec 28, 2024
5159c2a
Merge pull request #3491 from ever-co/stage
evereq Dec 28, 2024
2f4a6bc
Update Dockerfile
evereq Jan 3, 2025
2dce5a2
Merge pull request #3492 from ever-co/develop
evereq Jan 3, 2025
d519031
Merge pull request #3493 from ever-co/stage
evereq Jan 3, 2025
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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ ENV NEXT_TELEMETRY_DISABLED=1
ENV NEXT_BUILD_OUTPUT_TYPE=standalone
ENV NEXT_SHARP_PATH=/temp/node_modules/sharp

RUN npm i -g npm@latest
RUN npm i -g npm@9
# Install sharp, NextJS image optimization
RUN mkdir /temp && cd /temp && \
npm i sharp
Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Contact: <mailto:[email protected]>

Ever Teams™ follows good security practices, but 100% security cannot be guaranteed in any software!
Ever Teams™ is provided AS IS without any warranty. Use at your own risk!
See more details in the [LICENSE](LICENSE.md).
See more details in the AGPL v3 [LICENSE](LICENSE) file.

In a production setup, all client-side to server-side (backend, APIs) communications should be encrypted using HTTPS/WSS/SSL (REST APIs, GraphQL endpoint, Socket.io WebSockets, etc.).

Expand Down
6 changes: 3 additions & 3 deletions apps/extensions/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4042,9 +4042,9 @@ mz@^2.7.0:
thenify-all "^1.0.0"

nanoid@^3.3.6:
version "3.3.6"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
version "3.3.8"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf"
integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==

napi-build-utils@^1.0.1:
version "1.0.2"
Expand Down
6 changes: 4 additions & 2 deletions apps/web/app/[locale]/page-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

'use client';
import React, { useEffect, useState } from 'react';
import { useOrganizationTeams } from '@app/hooks';
import { useOrganizationTeams, useTimerView } from '@app/hooks';
import { clsxm } from '@app/utils';
import NoTeam from '@components/pages/main/no-team';
import { withAuthentication } from 'lib/app/authenticator';
Expand Down Expand Up @@ -34,6 +34,8 @@ function MainPage() {
const t = useTranslations();
const [headerSize] = useState(10);
const { isTeamMember, isTrackingEnabled, activeTeam } = useOrganizationTeams();
const { timerStatus } = useTimerView();

const [fullWidth, setFullWidth] = useAtom(fullWidthState);
const [view, setView] = useAtom(headerTabs);
const path = usePathname();
Expand All @@ -56,7 +58,7 @@ function MainPage() {
}, [setFullWidth]);

if (!online) {
return <Offline />;
return <Offline showTimer={timerStatus?.running} />;
}
return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export function GroupBySelect({ defaultValues, onChange }: IProps) {
return (
<Listbox multiple value={selected} onChange={handleChange}>
<div className="relative h-[2.2rem] w-[12rem]">
<Listbox.Button className="relative w-full h-full cursor-default rounded-lg bg-white py-2 pl-1 pr-6 text-left border focus:outline-none focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-white/75 focus-visible:ring-offset-2 focus-visible:ring-offset-primary sm:text-sm">
<Listbox.Button className="relative w-full h-full cursor-default rounded-lg bg-white dark:border-gray-700 dark:bg-dark--theme-light py-2 pl-1 pr-6 text-left border focus:outline-none focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-white/75 focus-visible:ring-offset-2 focus-visible:ring-offset-primary sm:text-sm">
<div className=" items-center w-full h-full flex gap-1">
{selected.map((option) => (
<Badge
Expand All @@ -83,18 +83,20 @@ export function GroupBySelect({ defaultValues, onChange }: IProps) {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options className="absolute mt-1 max-h-60 w-full z-[999] overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black/5 focus:outline-none sm:text-sm">
<Listbox.Options className="absolute mt-1 max-h-60 w-full z-[999] overflow-auto rounded-md bg-white dark:bg-dark--theme-light py-1 text-base shadow-lg ring-1 ring-black/5 focus:outline-none sm:text-sm">
{options.map((option, index) => (
<Listbox.Option
disabled={
// Prevents users from clearing all selections, ensuring at least one option is always selected.
selected.includes(option) && selected.length == 1
}
key={index}
className={({ active }) =>
className={({ active, selected }) =>
`relative cursor-default select-none py-2 pl-10 pr-4 ${
active ? 'bg-primary/10 text-primary' : 'text-gray-900'
}`
active
? 'bg-primary/10 text-primary dark:text-white dark:bg-primary/10'
: 'text-gray-900 dark:text-white'
} ${selected && 'dark:bg-primary/10'}`
}
value={option}
>
Expand Down
104 changes: 64 additions & 40 deletions apps/web/app/[locale]/timesheet/[memberId]/components/AddTaskModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@c
import { DatePickerFilter } from './TimesheetFilterDate';
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@components/ui/select';
import { useTimesheet } from '@/app/hooks/features/useTimesheet';
import { toUTC } from '@/app/helpers';
export interface IAddTaskModalProps {
isOpen: boolean;
closeModal: () => void;
Expand All @@ -22,6 +23,18 @@ interface Shift {
totalHours: string;
dateFrom: Date | string,
}
interface FormState {
isBillable: boolean;
notes: string;
projectId: string;
taskId: string;
employeeId: string;
shifts: {
dateFrom: Date;
startTime: string;
endTime: string;
}[];
}

export function AddTaskModal({ closeModal, isOpen }: IAddTaskModalProps) {
const { tasks } = useTeamTasks();
Expand All @@ -32,6 +45,7 @@ export function AddTaskModal({ closeModal, isOpen }: IAddTaskModalProps) {

const timeOptions = generateTimeOptions(5);
const t = useTranslations();

const [formState, setFormState] = React.useState({
notes: '',
isBillable: true,
Expand Down Expand Up @@ -77,37 +91,53 @@ export function AddTaskModal({ closeModal, isOpen }: IAddTaskModalProps) {
},
];

const handleAddTimesheet = async () => {
const createUtcDate = (baseDate: Date, time: string): Date => {
const [hours, minutes] = time.split(':').map(Number);
return new Date(Date.UTC(baseDate.getFullYear(), baseDate.getMonth(), baseDate.getDate(), hours, minutes));
};

const handleAddTimesheet = async (formState: FormState) => {
const payload = {
isBillable: formState.isBillable,
description: formState.notes,
projectId: formState.projectId,
logType: TimeLogType.MANUAL as any,
source: TimerSource.BROWSER as any,
taskId: formState.taskId,
employeeId: formState.employeeId
}
const createUtcDate = (baseDate: Date, time: string): Date => {
const [hours, minutes] = time.split(':').map(Number);
return new Date(Date.UTC(baseDate.getFullYear(), baseDate.getMonth(), baseDate.getDate(), hours, minutes));
employeeId: formState.employeeId,
organizationContactId: null || "",
organizationTeamId: null,
};

try {
await Promise.all(formState.shifts.map(async (shift) => {
const baseDate = shift.dateFrom instanceof Date ? shift.dateFrom : new Date(shift.dateFrom ?? new Date());
const startedAt = createUtcDate(baseDate, shift.startTime.toString().slice(0, 5));
const stoppedAt = createUtcDate(baseDate, shift.endTime.toString().slice(0, 5));
await createTimesheet({
...payload,
startedAt,
stoppedAt,
});
}));
closeModal();
if (!formState.shifts || formState.shifts.length === 0) {
throw new Error('No shifts provided.');
}
await Promise.all(
formState.shifts.map(async (shift) => {
if (!shift.dateFrom || !shift.startTime || !shift.endTime) {
throw new Error('Incomplete shift data.');
}

const baseDate = shift.dateFrom instanceof Date ? shift.dateFrom : new Date(shift.dateFrom);
const start = createUtcDate(baseDate, shift.startTime);
const end = createUtcDate(baseDate, shift.endTime);
const startedAt = toUTC(start).toISOString();
const stoppedAt = toUTC(end).toISOString();
if (stoppedAt <= startedAt) {
throw new Error('End time must be after start time.');
}
await createTimesheet({
...payload,
startedAt: start,
stoppedAt: end,
});
})
);
console.log('Timesheets successfully created.');
} catch (error) {
console.error('Failed to create timesheet:', error);
}
}
};

return (
<Modal
Expand Down Expand Up @@ -152,13 +182,17 @@ export function AddTaskModal({ closeModal, isOpen }: IAddTaskModalProps) {
<span className="text-[#de5505e1] ml-1">*</span>:
</label>
<CustomSelect
classNameGroup='max-h-[40vh] '
valueKey='employeeId'
classNameGroup='max-h-[40vh] dark:!text-white '
ariaLabel='Task issues'
className='w-full font-medium'
options={activeTeam?.members as any}
onChange={(value: any) => updateFormState('employeeId', value.id)}
renderOption={(option: any) => (
className='w-full font-medium dark:text-white'
options={activeTeam?.members || []}
onChange={(value) => {
updateFormState('employeeId', value)
}}
renderOption={(option) => (
<div className="flex items-center gap-x-2">
<img className='h-6 w-6 rounded-full' src={option.employee.user.imageUrl} />
<span>{option.employee.fullName}</span>
</div>
)}
Expand Down Expand Up @@ -235,7 +269,7 @@ export function AddTaskModal({ closeModal, isOpen }: IAddTaskModalProps) {
</button>
<button
disabled={loadingCreateTimesheet}
onClick={handleAddTimesheet}
onClick={() => handleAddTimesheet(formState as any)}
type="submit"
className={clsxm(
'bg-primary dark:bg-primary-light h-[2.3rem] w-[5.5rem] justify-center font-normal flex items-center text-white px-2 rounded-lg',
Expand Down Expand Up @@ -321,21 +355,15 @@ const OptimizedAccordion = ({ setShifts, shifts, timeOptions, t }: {

const handleAddShift = () => {
setShifts([...shifts,
{ startTime: '', endTime: '', totalHours: '00:00h', dateFrom: new Date() },]);
{ startTime: '', endTime: '', totalHours: '00:00:00 h', dateFrom: new Date() },]);
};

const handleRemoveShift = (index: number) => {
const updatedShifts = shifts.filter((_, i) => i !== index);
setShifts(updatedShifts);
};

const convertMinutesToTime = (minutes: number): string => {
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
const period = hours >= 12 ? 'PM' : 'AM';
const formattedHours = hours % 12 || 12;
return `${String(formattedHours).padStart(2, '0')}:${String(mins).padStart(2, '0')} ${period}`;
};


const handleShiftChange = (index: number, field: keyof Shift, value: string) => {
const updatedShifts = [...shifts];
Expand All @@ -346,11 +374,7 @@ const OptimizedAccordion = ({ setShifts, shifts, timeOptions, t }: {

if (!startTime || !endTime) return;

if (startTime === endTime) {
const startMinutes = convertToMinutesHour(startTime);
updatedShifts[index].endTime = convertMinutesToTime(startMinutes + 5);
return;
}

if (convertToMinutesHour(startTime) >= convertToMinutesHour(endTime)) {
return;
}
Expand Down Expand Up @@ -445,15 +469,15 @@ const ShiftManagement = (
<ShiftTimingSelect
label="Start"
timeOptions={timeOptions}
placeholder="00:00"
placeholder="00:00:00"
className="bg-[#30B3661A]"
value={value.startTime}
onChange={(value) => onChange(index, 'startTime', value)}
/>
<ShiftTimingSelect
label="End"
timeOptions={timeOptions}
placeholder="00:00"
placeholder="00:00:00"
className="bg-[#DA27271A]"
value={value.endTime}
onChange={(value) => onChange(index, 'endTime', value)}
Expand Down
Loading
Loading