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 User Status: Online & Last Seen Timestamps #9781

Closed
Closed
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
2 changes: 2 additions & 0 deletions public/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,7 @@
"nationality": "Nationality",
"nearby_facilities": "Nearby Facilities",
"never": "never",
"never_logged_in": "never logged in",
"new_password": "New Password",
"new_password_confirmation": "Confirm New Password",
"new_password_same_as_old": "New password is same as old password, please enter a different new password.",
Expand Down Expand Up @@ -1287,6 +1288,7 @@
"on": "on",
"on_emergency_basis": " on emergency basis",
"ongoing_medications": "Ongoing Medications",
"online": "online",
"only_indian_mobile_numbers_supported": "Currently only Indian numbers are supported",
"op_encounter": "OP Encounter",
"op_file_closed": "OP file closed",
Expand Down
6 changes: 4 additions & 2 deletions public/locale/hi.json
Original file line number Diff line number Diff line change
Expand Up @@ -807,5 +807,7 @@
"yet_to_be_decided": "अभी निर्णय होना बाकी",
"you_need_at_least_a_location_to_create_an_assest": "संपत्ति बनाने के लिए आपको कम से कम एक स्थान की आवश्यकता होगी।",
"zoom_in": "ज़ूम इन",
"zoom_out": "ज़ूम आउट"
}
"zoom_out": "ज़ूम आउट",
"never_logged_in": "कभी लॉग इन नहीं किया",
"online": "ऑनलाइन"
}
6 changes: 4 additions & 2 deletions public/locale/kn.json
Original file line number Diff line number Diff line change
Expand Up @@ -808,5 +808,7 @@
"yet_to_be_decided": "ಇನ್ನೂ ನಿರ್ಧರಿಸಬೇಕಿದೆ",
"you_need_at_least_a_location_to_create_an_assest": "ಆಸ್ತಿಯನ್ನು ರಚಿಸಲು ನಿಮಗೆ ಕನಿಷ್ಠ ಸ್ಥಳದ ಅಗತ್ಯವಿದೆ.",
"zoom_in": "ಜೂಮ್ ಇನ್",
"zoom_out": "ಜೂಮ್ ಔಟ್"
}
"zoom_out": "ಜೂಮ್ ಔಟ್",
"never_logged_in": "ಎಂದಿಗೂ ಲಾಗ್ ಇನ್ ಆಗಿಲ್ಲ",
"online": "ಆನ್ಲೈನ್"
}
6 changes: 4 additions & 2 deletions public/locale/ml.json
Original file line number Diff line number Diff line change
Expand Up @@ -808,5 +808,7 @@
"yet_to_be_decided": "ഇനിയും തീരുമാനമായിട്ടില്ല",
"you_need_at_least_a_location_to_create_an_assest": "ഒരു അസസ്‌റ്റ് സൃഷ്‌ടിക്കാൻ നിങ്ങൾക്ക് ഒരു ലൊക്കേഷനെങ്കിലും ആവശ്യമാണ്.",
"zoom_in": "സൂം ഇൻ ചെയ്യുക",
"zoom_out": "സൂം ഔട്ട്"
}
"zoom_out": "സൂം ഔട്ട്",
"never_logged_in": "ഒരിക്കലും ലോഗിൻ ചെയ്തിട്ടില്ല",
"online": "ഓൺലൈൻ"
}
4 changes: 3 additions & 1 deletion public/locale/mr.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,7 @@
"present_health": "सध्याचे आरोग्य",
"ongoing_medications": "चालू औषधे",
"allergies": "अॅलर्जी",
"is_pregnant": "गर्भवती आहे"
"is_pregnant": "गर्भवती आहे",
"never_logged_in": "कधीही लॉग इन केले नाही",
"online": "ऑनलाइन"
}
6 changes: 4 additions & 2 deletions public/locale/ta.json
Original file line number Diff line number Diff line change
Expand Up @@ -807,5 +807,7 @@
"yet_to_be_decided": "இன்னும் முடிவு செய்யப்படவில்லை",
"you_need_at_least_a_location_to_create_an_assest": "ஒரு அசெஸ்ட்டை உருவாக்க குறைந்தபட்சம் ஒரு இருப்பிடமாவது தேவை.",
"zoom_in": "பெரிதாக்கவும்",
"zoom_out": "பெரிதாக்கவும்"
}
"zoom_out": "பெரிதாக்கவும்",
"never_logged_in": "உள்நுழைந்ததில்லை",
"online": "ஆன்லைன்"
}
1 change: 0 additions & 1 deletion src/components/Users/UserBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export default function UserBanner({ userData }: { userData: UserBase }) {
<UserStatusIndicator user={userData} addPadding />
</div>
</div>

<span
id="username"
className="text-sm font-light leading-relaxed text-secondary-600"
Expand Down
90 changes: 42 additions & 48 deletions src/components/Users/UserListAndCard.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import dayjs from "dayjs";
import { navigate } from "raviger";
import { useTranslation } from "react-i18next";

import Card from "@/CAREUI/display/Card";
import CareIcon from "@/CAREUI/icons/CareIcon";

import { Badge } from "@/components/ui/badge";

import { Avatar } from "@/components/Common/Avatar";
import Tabs from "@/components/Common/Tabs";
import SearchInput from "@/components/Form/SearchInput";
Expand All @@ -14,12 +17,7 @@ import useWindowDimensions from "@/hooks/useWindowDimensions";

import { USER_TYPE_OPTIONS } from "@/common/constants";

import {
classNames,
formatName,
isUserOnline,
relativeTime,
} from "@/Utils/utils";
import { formatName, isUserOnline } from "@/Utils/utils";
import { UserBase } from "@/types/user/user";

export const GetUserTypes = () => {
Expand Down Expand Up @@ -65,11 +63,7 @@ const GetDetailsButton = (username: string) => {
</div>
);
};
const getNameAndStatusCard = (
user: UserBase,
cur_online: boolean,
showDetailsButton = false,
) => {
const getNameAndStatusCard = (user: UserBase, showDetailsButton = false) => {
return (
<div>
<div className="flex flex-row justify-between gap-x-3">
Expand All @@ -78,14 +72,7 @@ const getNameAndStatusCard = (
<h1 id={`name-${user.username}`} className="text-base font-bold">
{formatName(user)}
</h1>
<div
className={classNames(
"flex items-center gap-2 rounded-full px-3 py-1",
cur_online ? "bg-green-100" : "bg-gray-100",
)}
>
<UserStatusIndicator user={user} />
</div>
<UserStatusIndicator user={user} />
</div>
<span
className="text-sm text-gray-500"
Expand All @@ -102,7 +89,6 @@ const getNameAndStatusCard = (

export const UserStatusIndicator = ({
user,
className,
addPadding = false,
}: {
user: UserBase;
Expand All @@ -111,37 +97,45 @@ export const UserStatusIndicator = ({
}) => {
const authUser = useAuthUser();
const isAuthUser = user.id === authUser.external_id;
const isOnline = isUserOnline(user) || isAuthUser;
const { t } = useTranslation();

return (
<div
className={classNames(
"inline-flex items-center gap-2 rounded-full",
addPadding ? "px-3 py-1" : "py-px",
isOnline ? "bg-green-100" : "bg-gray-100",
className,
)}
<span
title={`${new Date(user.last_login).toLocaleString()}`}
className={addPadding ? "px-3 py-1" : "py-px"}
>
<span
className={classNames(
"inline-block h-2 w-2 shrink-0 rounded-full",
isOnline ? "bg-green-500" : "bg-gray-400",
)}
></span>
<span
className={classNames(
"whitespace-nowrap text-xs",
isOnline ? "text-green-700" : "text-gray-500",
)}
>
{isOnline
? t("online")
: user.last_login
? relativeTime(user.last_login)
: t("never")}
</span>
</div>
{isUserOnline(user) || isAuthUser ? (
<Badge variant="secondary" className="bg-green-100 whitespace-nowrap">
<span className="inline-block h-2 w-2 shrink-0 rounded-full bg-green-500 mr-2" />
<span className="text-xs text-green-700">{t("online")}</span>
</Badge>
) : user.last_login ? (
<Badge variant="secondary" className="bg-yellow-100 whitespace-nowrap">
<span className="inline-block h-2 w-2 shrink-0 rounded-full bg-yellow-500 mr-2" />
<span className="text-xs text-yellow-700">
last seen{" "}
{(() => {
const diffInMinutes = dayjs().diff(user.last_login, "minutes");
const diffInHours = dayjs().diff(user.last_login, "hours");
const diffInDays = dayjs().diff(user.last_login, "days");

if (diffInMinutes < 60) {
return `${diffInMinutes} minutes ago`;
} else if (diffInHours < 24) {
return `${diffInHours} hour${diffInHours > 1 ? "s" : ""} ago`;
} else {
return `${diffInDays} days ago`;
}
})()}
</span>
</Badge>
) : (
<Badge variant="secondary" className="bg-gray-100 whitespace-nowrap">
<span className="inline-block h-2 w-2 shrink-0 rounded-full bg-gray-500 mr-2" />
<span className="text-xs text-gray-700">{t("never_logged_in")}</span>
</Badge>
)}
</span>
);
};
const UserCard = ({ user }: { user: UserBase }) => {
Expand Down Expand Up @@ -173,7 +167,7 @@ const UserCard = ({ user }: { user: UserBase }) => {
</div>
<div className="flex flex-col w-full">
{!isMediumScreen &&
getNameAndStatusCard(user, userOnline, !isLessThanXLargeScreen)}
getNameAndStatusCard(user, !isLessThanXLargeScreen)}
<div className="mt-4 grid grid-cols-2 gap-x-4 gap-y-4">
<div className="text-sm">
<div className="text-gray-500">{t("role")}</div>
Expand Down
20 changes: 5 additions & 15 deletions src/pages/FacilityOrganization/FacilityOrganizationUsers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { useQueryParams } from "raviger";

import CareIcon from "@/CAREUI/icons/CareIcon";

import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";

import { Avatar } from "@/components/Common/Avatar";
import { UserStatusIndicator } from "@/components/Users/UserListAndCard";

import routes from "@/Utils/request/api";
import query from "@/Utils/request/query";
Expand Down Expand Up @@ -117,20 +117,10 @@ export default function FacilityOrganizationUsers({ id, facilityId }: Props) {
<h1 className="text-base font-bold break-words pr-2">
{userRole.user.first_name} {userRole.user.last_name}
</h1>
<div className="flex items-center gap-2 flex-wrap">
<span className="text-sm text-gray-500 truncate">
{userRole.user.username}
</span>
<Badge
variant="secondary"
className="bg-green-100 whitespace-nowrap"
>
<span className="inline-block h-2 w-2 shrink-0 rounded-full bg-green-500 mr-2" />
<span className="text-xs text-green-700">
online
</span>
</Badge>
</div>
<span className="text-sm text-gray-500">
{userRole.user.username}
<UserStatusIndicator user={userRole.user} />
</span>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
} from "@/components/ui/sheet";

import { Avatar } from "@/components/Common/Avatar";
import { UserStatusIndicator } from "@/components/Users/UserListAndCard";

import routes from "@/Utils/request/api";
import mutate from "@/Utils/request/mutate";
Expand Down Expand Up @@ -161,16 +162,11 @@ export default function EditUserRoleSheet({
<p className="text-sm font-medium">{userRole.role.name}</p>
</div>
<div>
<span className="text-sm text-gray-500">Last Login</span>
<p className="text-sm font-medium">
{userRole.user.last_login
? new Date(userRole.user.last_login).toLocaleDateString()
: "Never"}
</p>
<span className="text-sm text-gray-500">Last Login </span>
<UserStatusIndicator user={userRole.user} />
</div>
</div>
</div>

<div className="space-y-2">
<label className="text-sm font-medium">Select New Role</label>
<Select value={selectedRole} onValueChange={setSelectedRole}>
Expand Down
13 changes: 2 additions & 11 deletions src/pages/Organization/OrganizationUsers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { useQueryParams } from "raviger";

import CareIcon from "@/CAREUI/icons/CareIcon";

import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";

import { Avatar } from "@/components/Common/Avatar";
import { UserStatusIndicator } from "@/components/Users/UserListAndCard";

import query from "@/Utils/request/query";
import { OrganizationUserRole } from "@/types/organization/organization";
Expand Down Expand Up @@ -110,7 +110,6 @@ export default function OrganizationUsers({ id, navOrganizationId }: Props) {
imageUrl={userRole.user.profile_picture_url}
className="h-12 w-12 sm:h-16 sm:w-16 text-xl sm:text-2xl flex-shrink-0"
/>

<div className="flex flex-col min-w-0 flex-1">
<div className="flex flex-col gap-1">
<h1 className="text-base font-bold break-words pr-2">
Expand All @@ -120,15 +119,7 @@ export default function OrganizationUsers({ id, navOrganizationId }: Props) {
<span className="text-sm text-gray-500 truncate">
{userRole.user.username}
</span>
<Badge
variant="secondary"
className="bg-green-100 whitespace-nowrap"
>
<span className="inline-block h-2 w-2 shrink-0 rounded-full bg-green-500 mr-2" />
<span className="text-xs text-green-700">
online
</span>
</Badge>
<UserStatusIndicator user={userRole.user} />
</div>
</div>
</div>
Expand Down
9 changes: 3 additions & 6 deletions src/pages/Organization/components/EditUserRoleSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
} from "@/components/ui/sheet";

import { Avatar } from "@/components/Common/Avatar";
import { UserStatusIndicator } from "@/components/Users/UserListAndCard";

import routes from "@/Utils/request/api";
import mutate from "@/Utils/request/mutate";
Expand Down Expand Up @@ -152,12 +153,8 @@ export default function EditUserRoleSheet({
<p className="text-sm font-medium">{userRole.role.name}</p>
</div>
<div>
<span className="text-sm text-gray-500">Last Login</span>
<p className="text-sm font-medium">
{userRole.user.last_login
? new Date(userRole.user.last_login).toLocaleDateString()
: "Never"}
</p>
<span className="text-sm text-gray-500">Last Login </span>
<UserStatusIndicator user={userRole.user} />
</div>
</div>
</div>
Expand Down
Loading