Skip to content

Commit

Permalink
feat: Refactor agreements and update schema path
Browse files Browse the repository at this point in the history
- refactor: Replace agreements table with card components
- chore: Update biome.json schema to local path
- feat: Filter agreements based on user role
  • Loading branch information
Sampiiiii committed Oct 9, 2024
1 parent abf93b4 commit 3887018
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 50 deletions.
3 changes: 3 additions & 0 deletions .vscode/ignis.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
"tooling/": true,
"packages/": true
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
},
"extensions": {
"recommendations": [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { useUser } from "@/lib/utils";
import { Agreement } from "@ignis/types/root.ts";
import { useNavigate } from "@tanstack/react-router";
import { Badge, BadgeProps } from "@ui/components/ui/badge";
import { Card, CardContent } from "@ui/components/ui/card";
import { Separator } from "@ui/components/ui/separator";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@ui/components/ui/tooltip";
import { motion } from "framer-motion";
import { AlertCircle, Calendar, CheckCircle, ExternalLink, Tag } from "lucide-react";
import { useState } from "react";

interface AgreementCardProps {
agreement: Agreement;
}

export function AgreementCard({ agreement }: AgreementCardProps) {
const [isHovered, setIsHovered] = useState(false);
const navigate = useNavigate();
const user = useUser()!;

const handleViewAgreement = () => {
navigate({ to: "/signin/agreements/$id", params: { id: agreement.id } });
};

const getAgreementStatus = (agreement: Agreement) => {
const user_agreement = user.agreements_signed.find((agreement_) => agreement.id === agreement_.id);

if (user_agreement !== undefined) {
if (user_agreement.version === agreement.version) {
return {
status: "Signed",
badgeVariant: "default",
badgeStyle: "rounded-sm",
};
}
return {
status: "Needs Resigning",
badgeVariant: "warning",
badgeStyle: "rounded-sm",
};
}

return {
status: "Not Signed",
badgeVariant: "warning",
badgeStyle: "rounded-sm",
};
};

const { status, badgeVariant, badgeStyle } = getAgreementStatus(agreement);

return (
<TooltipProvider>
<motion.div
className="relative overflow-hidden rounded-lg cursor-pointer w-full sm:w-[calc(50%-1rem)] lg:w-[40%]"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
onHoverStart={() => setIsHovered(true)}
onHoverEnd={() => setIsHovered(false)}
onClick={handleViewAgreement}
>
<Card className="bg-card dark:bg-card-dark border-border dark:border-border-dark overflow-hidden group w-full h-full">
<CardContent className="p-0 relative w-full h-full">
<div className="absolute top-4 right-4 transition-all duration-200">
<ExternalLink className="h-5 w-5 text-muted-foreground group-hover:text-primary transition-colors duration-200" />
</div>
<div className="flex flex-col w-full h-full">
<div className="w-full p-4 sm:p-6">
<div className="flex flex-col sm:flex-row sm:items-center mb-4">
<h3 className="text-xl sm:text-2xl font-bold text-foreground dark:text-foreground-dark mb-2 sm:mb-0 sm:mr-4">
{agreement.name}
</h3>
<Badge
variant={badgeVariant as BadgeProps["variant"]}
className={`text-sm px-2 py-1 ${badgeStyle} mt-2 sm:mt-0`}
>
{status}
</Badge>
</div>
<div className="flex flex-wrap gap-2 mb-4">
{agreement.reasons.map((reason) => (
<Badge
key={reason.name}
variant="outline"
className="text-primary dark:text-primary-dark border-primary dark:border-primary-dark"
>
<Tag className="mr-2 h-4 w-4" />
{reason.name}
</Badge>
))}
<Badge
variant="outline"
className="text-muted-foreground dark:text-muted-foreground-dark border-muted-foreground dark:border-muted-foreground-dark"
>
<Calendar className="mr-2 h-4 w-4" />
{new Date(agreement.created_at).toLocaleDateString()}
</Badge>
<Badge
variant="outline"
className="text-muted-foreground dark:text-muted-foreground-dark border-muted-foreground dark:border-muted-foreground-dark"
>
v{agreement.version}
</Badge>
</div>
</div>
<Separator orientation="horizontal" className="mt-auto" />
<div className="w-full bg-popover dark:bg-popover-dark p-4 sm:p-6 flex flex-col justify-center items-center">
{status === "Not Signed" ? (
<Tooltip>
<TooltipTrigger>
<div className="bg-warning text-warning-foreground p-3 rounded-lg inline-flex items-center">
<AlertCircle className="h-5 w-5 mr-2" />
Action Required
</div>
</TooltipTrigger>
<TooltipContent>
<p className="text-sm">Review and sign on the agreement page</p>
</TooltipContent>
</Tooltip>
) : status === "Needs Resigning" ? (
<Tooltip>
<TooltipTrigger>
<div className="bg-warning text-warning-foreground p-3 rounded-lg inline-flex items-center">
<AlertCircle className="h-5 w-5 mr-2" />
Needs Resigning
</div>
</TooltipTrigger>
<TooltipContent>
<p className="text-sm">Please review and sign the updated agreement version</p>
</TooltipContent>
</Tooltip>
) : (
<Tooltip>
<TooltipTrigger>
<div className="bg-success text-success-foreground p-3 rounded-lg inline-flex items-center">
<CheckCircle className="h-5 w-5 mr-2" />
No Action Required
</div>
</TooltipTrigger>
<TooltipContent>
<p className="text-sm">This agreement has been signed</p>
</TooltipContent>
</Tooltip>
)}
</div>
</div>
</CardContent>
</Card>
<motion.div
className="absolute inset-0 bg-gray-600/5 dark:bg-gray-500/5 pointer-events-none"
initial={{ opacity: 0 }}
animate={{ opacity: isHovered ? 1 : 0 }}
transition={{ duration: 0.2 }}
/>
</motion.div>
</TooltipProvider>
);
}
62 changes: 17 additions & 45 deletions apps/forge/src/routes/_authenticated/signin/agreements/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { useUser } from "@/lib/utils";
import { getAgreements } from "@/services/root/getAgreements";
import { Agreement } from "@ignis/types/root";
import { useQuery } from "@tanstack/react-query";
import { Link, createFileRoute } from "@tanstack/react-router";
import { Badge } from "@ui/components/ui/badge";
import { Loader } from "@ui/components/ui/loader";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@ui/components/ui/table";
import { AgreementCard } from "@/routes/_authenticated/signin/agreements/-components/AgreementCard";
import { createFileRoute } from "@tanstack/react-router";
import Title from "@/components/title";
import { useUser } from "@/lib/utils";

export default function Component() {
const user = useUser()!;

const {
data: agreements,
isLoading,
Expand All @@ -27,53 +26,26 @@ export default function Component() {
return <div className="text-center">Error loading agreements</div>;
}

const getAgreementStatus = (agreement: Agreement) => {
const user_agreement = user.agreements_signed.find((agreement_) => agreement.id === agreement_.id);
if (user_agreement !== undefined) {
if (user_agreement.version === agreement.version) {
return "Signed";
}
return "Needs Resigning";
// Filter agreements based on user role
const filteredAgreements = agreements.filter((agreement) => {
// If the user is not a Rep, filter out the Rep On Shift agreement
if (!user.roles.find((role) => role.name === "Rep")) {
return !agreement.reasons.some((reason) => reason.name === "Rep On Shift");
}
return "Not Signed";
};
// If the user is a Rep, show all agreements
return true;
});

return (
<>
<Title prompt="User Agreements" />
<h1 className="text-3xl font-bold text-center m-5">Agreements</h1>
<p className="accent-accent text-center">The signable agreements in the iForge.</p>

<div className="flex justify-center mt-2">
<Table className="max-w-xl mx-auto">
<TableHeader className="bg-accent rounded-md">
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Reasons</TableHead>
<TableHead className="text-center">Status</TableHead>
<TableHead className="text-center">Version</TableHead>
<TableHead>Updated at</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{agreements.map((agreement) => (
<Link key={agreement.id} to="/signin/agreements/$id" params={agreement} className="contents">
<TableRow className="hover:bg-accent hover:cursor-pointer">
<TableCell>{agreement.name}</TableCell>
<TableCell>{agreement.reasons.map((reason) => reason.name).join(", ")}</TableCell>
<TableCell>
<div className="flex justify-center">
<Badge variant="outline" className="rounded-md">
{getAgreementStatus(agreement)}
</Badge>
</div>
</TableCell>
<TableCell className="text-center">{agreement.version}</TableCell>
<TableCell>{new Date(agreement.created_at).toLocaleDateString()}</TableCell>
</TableRow>
</Link>
))}
</TableBody>
</Table>
<div className="flex flex-col items-center mt-4 gap-4">
{filteredAgreements.map((agreement) => (
<AgreementCard key={agreement.id} agreement={agreement} />
))}
</div>
</>
);
Expand Down
7 changes: 2 additions & 5 deletions biome.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"$schema": "https://biomejs.dev/schemas/1.2.2/schema.json",
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"organizeImports": {
"enabled": true
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"trailingComma": "all",
"trailingCommas": "all",
"lineWidth": 120,
"indentWidth": 2,
"indentStyle": "space"
Expand Down Expand Up @@ -49,9 +49,6 @@
"correctness": {
"useHookAtTopLevel": "error",
"noUnusedVariables": "warn"
},
"nursery": {
"noDuplicateJsonKeys": "warn"
}
}
}
Expand Down

0 comments on commit 3887018

Please sign in to comment.