Skip to content
Merged
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
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ Data Handling:
- Local storage for persistence
- Email integration via Gmail compose URL

Data Security:
- Web Crypto API for encryption
- AES-GCM encryption algorithm
- PBKDF2 key derivation
- Secure salt generation and storage

Data Storage:
- IndexedDB for encrypted data
- Dexie.js for IndexedDB management
- Encrypted records for all sensitive data
- Automatic key management

## Project Structure

/src
Expand Down Expand Up @@ -93,6 +105,16 @@ Data Handling:
- Responsive design with Tailwind
- State persistence with Zustand

## Security Features

### Encryption
- Uses browser's native Web Crypto API
- AES-GCM encryption for all sensitive data
- Secure key derivation using PBKDF2
- Unique IV (Initialization Vector) per encryption
- Automatic salt generation and management


## License

### MIT License
Expand Down
4 changes: 3 additions & 1 deletion src/components/MeetingCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ interface MeetingCardProps {
meeting: Meeting;
isOverTarget: boolean;
onAction: (action: string, meetingId: string) => void;
displayRank: number;
}

export function MeetingCard({
meeting,
isOverTarget,
onAction,
displayRank,
}: MeetingCardProps) {
const [isExpanded, setIsExpanded] = useState(false);
const { meetingStatus = {} } = useSettingsStore();
Expand Down Expand Up @@ -113,7 +115,7 @@ export function MeetingCard({
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<DragHandleHorizontalIcon className="w-4 h-4 text-gray-400 cursor-move" />
<span className="text-base font-semibold">{meeting.rank}</span>
<span className="text-base font-semibold">#{displayRank}</span>
<span className="text-base">{meeting.title}</span>
{getStatusCount() > 0 && (
<span className="bg-red-100 text-red-600 text-xs px-1.5 py-0.5 rounded-full">
Expand Down
16 changes: 6 additions & 10 deletions src/components/MeetingRater.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,16 @@ import { cn } from "@/lib/utils";
import { Textarea } from "./ui/textarea";
import { Button } from "./ui/button";
import { useSettingsStore } from "../stores/settingsStore";
import type { MeetingComment } from "../types";
import type { MeetingComment, Meeting, MeetingRating } from "../types";

interface MeetingRaterProps {
meeting: {
id: string;
title: string;
startTime: string;
duration: number;
};
meeting: Meeting;
existingRating?: MeetingRating;
}

export function MeetingRater({ meeting }: MeetingRaterProps) {
const [rating, setRating] = useState(0);
const [comment, setComment] = useState("");
export function MeetingRater({ meeting, existingRating }: MeetingRaterProps) {
const [rating, setRating] = useState(existingRating?.rating || 0);
const [comment, setComment] = useState(existingRating?.comment || "");
const [isExpanded, setIsExpanded] = useState(false);
const [editingCommentId, setEditingCommentId] = useState<string | null>(null);
const [editText, setEditText] = useState("");
Expand Down
34 changes: 29 additions & 5 deletions src/components/Plan.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function Plan() {
storedMeetings,
dateRanges.plan.start,
dateRanges.plan.end
);
).sort((a, b) => (a.rank || 0) - (b.rank || 0));
const [meetings, setLocalMeetings] = useState<Meeting[]>(filteredMeetings);
const [stats, setStats] = useState<MeetingStats>({
totalHours: storedMeetings.reduce(
Expand All @@ -46,7 +46,7 @@ export function Plan() {
storedMeetings,
dateRanges.plan.start,
dateRanges.plan.end
);
).sort((a, b) => (a.rank || 0) - (b.rank || 0));
setLocalMeetings(filtered);
}, [storedMeetings]);

Expand Down Expand Up @@ -106,18 +106,41 @@ export function Plan() {
const handleDragEnd = (result: DropResult) => {
if (!result.destination) return;

const store = useSettingsStore.getState();

// Create new items array with updated positions
const items = Array.from(meetings);
const [reorderedItem] = items.splice(result.source.index, 1);
items.splice(result.destination.index, 0, reorderedItem);

// Update ranks starting from a high number to ensure proper sorting
const updatedItems = items.map((item, index) => ({
...item,
rank: index + 1,
rank: (index + 1) * 1000, // Using larger intervals for potential future insertions
}));

// Update store first, preserving the exact order of updatedItems
const finalMeetings = store.meetings.map((meeting) => {
const updatedMeeting = updatedItems.find(
(item) => item.id === meeting.id
);
if (updatedMeeting) {
return updatedMeeting;
}
// If meeting is not in updatedItems, preserve its original state
return {
...meeting,
rank: meeting.rank || 0, // Ensure all meetings have a rank
};
});

// Important: Sort the meetings before updating the store
const sortedMeetings = finalMeetings.sort(
(a, b) => (a.rank || 0) - (b.rank || 0)
);

store.updateMeetings(sortedMeetings);
setLocalMeetings(updatedItems);
useSettingsStore.getState().updateMeetings(updatedItems);
updateStats(updatedItems);
};

return (
Expand Down Expand Up @@ -156,6 +179,7 @@ export function Plan() {
meeting={meeting}
isOverTarget={runningTotal > currentTargetHours}
onAction={handleMeetingAction}
displayRank={index + 1}
/>
</div>
)}
Expand Down
17 changes: 15 additions & 2 deletions src/components/Rate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@ import {
getDateRanges,
filterMeetingsByDateRange,
} from "../services/calendarService";
import { useEffect } from "react";

export function Rate() {
const { meetings } = useSettingsStore();
const { meetings, meetingRatings, initializeStore } = useSettingsStore();

useEffect(() => {
const loadData = async () => {
await initializeStore();
};
loadData();
}, []);

const dateRanges = getDateRanges();
const lastWeekMeetings = filterMeetingsByDateRange(
meetings,
Expand All @@ -21,7 +30,11 @@ export function Rate() {
</h3>
<div className="space-y-2">
{lastWeekMeetings.map((meeting) => (
<MeetingRater key={meeting.id} meeting={meeting} />
<MeetingRater
key={meeting.id}
meeting={meeting}
existingRating={meetingRatings[meeting.id]}
/>
))}
</div>
</div>
Expand Down
Loading
Loading