updated services web user selection

This commit is contained in:
Berkay 2025-06-16 15:52:50 +03:00
parent 8573c8021b
commit b73417a625
14 changed files with 117 additions and 62 deletions

View File

@ -344,7 +344,12 @@ class LoginHandler:
# TODO: erase this bypass later
filter_endpoints_and_events = db_session.query(EndpointRestriction.operation_uu_id, Events.function_code
).join(EndpointRestriction, EndpointRestriction.id == Events.endpoint_id).filter().all()
# Get reachable applications
reachable_app_codes = Application2Employee.get_application_codes(employee_id=int(result_with_keys_dict['Employees.id']), db=db_session)
# Get reachable events
reachable_event_codes = {endpoint_name: function_code for endpoint_name, function_code in filter_endpoints_and_events}
add_company = {
"uu_id": str(result_with_keys_dict["Employees.uu_id"]),
"public_name": result_with_keys_dict["Companies.public_name"],
@ -356,7 +361,7 @@ class LoginHandler:
redis_handler = RedisHandlers()
user_uu_id = Users.query.filter(Users.person_id == result_with_keys_dict['People.id']).first().uu_id
redis_result = redis_handler.update_token_at_redis(token=access_token, add_payload=add_company, user_uuid=user_uu_id)
return {"selected_uu_id": data.uuid}
return {"selected_uu_id": data.uuid, "reachable_app_codes": reachable_app_codes}
@classmethod
def handle_occupant_selection(cls, access_token: str, data: Any, token_dict: TokenDictType):
@ -391,7 +396,9 @@ class LoginHandler:
reachable_event_codes = {endpoint_name: function_code for endpoint_name, function_code in filter_endpoints_and_events}
# Get reachable applications
print('selected_build_living_space_first', selected_build_living_space_first.id)
reachable_app_codes = Application2Occupant.get_application_codes(build_living_space_id=selected_build_living_space_first.id, db=db)
print('reachable_app_codes', reachable_app_codes)
build_part = BuildParts.query.filter(BuildParts.id == result_with_keys_dict['BuildParts.id']).first()
if not build_part:
@ -410,7 +417,7 @@ class LoginHandler:
user_uu_id = Users.query.filter(Users.person_id == result_with_keys_dict['People.id']).first().uu_id
redis_handler.update_token_at_redis(token=access_token, add_payload=add_build_living_space, user_uuid=user_uu_id)
return {"selected_uu_id": data.uuid}
return {"selected_uu_id": data.uuid, "reachable_app_codes": reachable_app_codes}
@classmethod
def authentication_select_company_or_occupant_type(cls, request: Any, data: Any):

View File

@ -21,14 +21,14 @@ class PageHandlers:
@classmethod
def retrieve_valid_sites_via_token(cls, access_token: str) -> list:
"""
Retrieve valid pages via token. {"access_token": "string"} | Results: list(sites)
"""
""" Retrieve valid pages via token. {"access_token": "string"} | Results: list(sites) """
if result := RedisHandlers.get_object_from_redis(access_token=access_token):
if result.is_employee:
if result.selected_company and result.selected_company.reachable_app_codes:
return result.selected_company.reachable_app_codes.keys()
elif result.is_occupant:
if result.selected_occupant and result.selected_occupant.reachable_app_codes:
return result.selected_occupant.reachable_app_codes.keys()
if result.is_employee and result.selected_company:
employee_uuid = result.selected_company.get("employee_uu_id", "")
if reachable_app_codes_dict := result.reachable_app_codes:
return reachable_app_codes_dict.get(employee_uuid, {}).keys()
elif result.is_occupant and result.selected_occupant:
living_space_uu_id = result.selected_occupant.get("build_living_space_uu_id", "")
if reachable_app_codes_dict := result.reachable_app_codes:
return reachable_app_codes_dict.get(living_space_uu_id, {}).keys()
raise ValueError("EYS_0013")

View File

@ -211,19 +211,11 @@ class Service2Application(CrudCollection):
__tablename__ = "services2applications"
__exclude__fields__ = []
application_id: Mapped[int] = mapped_column(
ForeignKey("applications.id"), nullable=False
)
application_uu_id: Mapped[str] = mapped_column(
String, nullable=False, comment="Application UUID"
)
application_id: Mapped[int] = mapped_column(ForeignKey("applications.id"), nullable=False)
application_uu_id: Mapped[str] = mapped_column(String, nullable=False, comment="Application UUID")
service_id: Mapped[int] = mapped_column(ForeignKey("services.id"), nullable=False)
service_uu_id: Mapped[str] = mapped_column(
String, nullable=False, comment="Service UUID"
)
application_code: Mapped[str] = mapped_column(
String, nullable=False, comment="Application Code"
)
service_uu_id: Mapped[str] = mapped_column(String, nullable=False, comment="Service UUID")
application_code: Mapped[str] = mapped_column(String, nullable=False, comment="Application Code")
site_url: Mapped[str] = mapped_column(String, nullable=False, comment="Site URL")
__table_args__ = {"comment": "Service2Applications Information"}
@ -237,13 +229,9 @@ class Event2OccupantExtra(CrudCollection):
build_living_space_id: Mapped[int] = mapped_column(
ForeignKey("build_living_space.id"), nullable=False
)
build_living_space_uu_id: Mapped[str] = mapped_column(
String, nullable=False, comment="Build Living Space UUID"
)
build_living_space_uu_id: Mapped[str] = mapped_column(String, nullable=False, comment="Build Living Space UUID")
event_id: Mapped[int] = mapped_column(ForeignKey("events.id"), nullable=False)
event_uu_id: Mapped[str] = mapped_column(
String, nullable=False, comment="Event UUID"
)
event_uu_id: Mapped[str] = mapped_column(String, nullable=False, comment="Event UUID")
__table_args__ = (
Index(
@ -265,14 +253,10 @@ class Event2EmployeeExtra(CrudCollection):
__exclude__fields__ = []
employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id"), nullable=False)
employee_uu_id: Mapped[str] = mapped_column(
String, nullable=False, comment="Employee UUID"
)
employee_uu_id: Mapped[str] = mapped_column(String, nullable=False, comment="Employee UUID")
event_id: Mapped[int] = mapped_column(ForeignKey("events.id"), nullable=False)
event_uu_id: Mapped[str] = mapped_column(
String, nullable=False, comment="Event UUID"
)
event_uu_id: Mapped[str] = mapped_column(String, nullable=False, comment="Event UUID")
__table_args__ = (
Index(
@ -447,12 +431,8 @@ class Application2Occupant(CrudCollection):
__tablename__ = "application2occupant"
__exclude__fields__ = []
build_living_space_id: Mapped[int] = mapped_column(
ForeignKey("build_living_space.id"), nullable=False
)
build_living_space_uu_id = mapped_column(
String, nullable=False, comment="Build Living Space UUID"
)
build_living_space_id: Mapped[int] = mapped_column(ForeignKey("build_living_space.id"), nullable=False)
build_living_space_uu_id = mapped_column(String, nullable=False, comment="Build Living Space UUID")
service_id: Mapped[int] = mapped_column(ForeignKey("services.id"), nullable=False)
service_uu_id = mapped_column(String, nullable=False, comment="Service UUID")
@ -462,6 +442,7 @@ class Application2Occupant(CrudCollection):
Service2Application.set_session(db)
Applications.set_session(db)
Application2OccupantExtra.set_session(db)
occupant_services = cls.query.filter(cls.build_living_space_id == build_living_space_id).all()
service_ids = [service.service_id for service in occupant_services]
active_applications = Service2Application.query.filter(Service2Application.service_id.in_(service_ids)).all()

View File

@ -8,6 +8,7 @@ import { NextResponse } from "next/server";
export async function GET() {
try {
const menu = await getMenuFromRedis();
console.log("menu", menu);
return NextResponse.json({
status: 200,
data: menu,

View File

@ -1,5 +1,8 @@
import { NextResponse } from "next/server";
import { loginViaAccessKeys, initFirstSelection } from "@/fetchers/custom/login/login";
import {
loginViaAccessKeys,
initFirstSelection,
} from "@/fetchers/custom/login/login";
import { loginSchemaEmail } from "@/pages/single/auth/login/schemas";
export async function POST(req: Request): Promise<NextResponse> {
@ -20,7 +23,11 @@ export async function POST(req: Request): Promise<NextResponse> {
}
const userLogin = await loginViaAccessKeys(dataValidated);
await initFirstSelection(userLogin.data?.firstSelection, userLogin.data?.userType);
console.log("userLogin", userLogin);
await initFirstSelection(
userLogin.data?.firstSelection,
userLogin.data?.userType
);
if (userLogin.status === 200 || userLogin.status === 202) {
return NextResponse.json({
status: 200,

View File

@ -6,9 +6,10 @@ interface ClientSelectionSectionProps {
selectionData: any;
refreshSelection: () => Promise<void>;
updateSelection: (newSelection: any) => Promise<boolean>;
refreshMenu: () => Promise<void>;
}
const ClientSelectionSection: FC<ClientSelectionSectionProps> = ({ selectionData, refreshSelection, updateSelection }) => {
const ClientSelectionSection: FC<ClientSelectionSectionProps> = ({ selectionData, refreshSelection, updateSelection, refreshMenu }) => {
const [expandedBuilds, setExpandedBuilds] = useState<{ [key: string]: boolean }>({});
if (!selectionData || !selectionData.selectionList) { return null }
@ -21,7 +22,7 @@ const ClientSelectionSection: FC<ClientSelectionSectionProps> = ({ selectionData
selectionData.selectionList.map((client: any, index: number) => {
return (
<div key={client.uu_id || client.id || `client-${index}`}>
{client && renderOneClientSelection({ item: client, selectedItem: selectionData.activeSelection, updateSelection, refreshSelection })}
{client && renderOneClientSelection({ item: client, selectedItem: selectionData.activeSelection, updateSelection, refreshSelection, refreshMenu })}
</div>
);
})
@ -63,7 +64,7 @@ const ClientSelectionSection: FC<ClientSelectionSectionProps> = ({ selectionData
{buildData.occupants && Array.isArray(buildData.occupants) ? (
buildData.occupants.map((client: any, index: number) => (
<div key={client.occupant_uu_id || client.part_uu_id || `occupant-${index}`}>
{client && renderOneClientSelection({ item: client, selectedItem: selectionData.activeSelection, updateSelection, refreshSelection })}
{client && renderOneClientSelection({ item: client, selectedItem: selectionData.activeSelection, updateSelection, refreshSelection, refreshMenu })}
</div>
))
) : (<div>No occupants found</div>)}

View File

@ -8,7 +8,7 @@ import ClientSelectionSection from "./clientSelectionSection";
import MenuItemsSection from "./menuItemsSection";
import MenuLoadingState from "./menuLoadingState";
import MenuErrorState from "./menuErrorState";
import MenuEmptyState from "./menuEmptyState";
import LoadingContent from "@/components/mutual/loader/component";
const MenuComponent: FC<MenuProps> = ({
@ -20,7 +20,6 @@ const MenuComponent: FC<MenuProps> = ({
}) => {
if (menuLoading) { return <MenuLoadingState /> }
if (menuError) { return <MenuErrorState error={menuError} />; }
if (availableApplications.length === 0) { return <MenuEmptyState />; }
const lang = onlineData?.lang as LanguageTypes || 'en';
return (
@ -32,7 +31,7 @@ const MenuComponent: FC<MenuProps> = ({
</Suspense>
{/* Client Selection Section */}
<Suspense fallback={<div><LoadingContent height="h-16" size="w-36 h-48" key={"loading-content"} plane="h-full w-full" /></div>}>
<ClientSelectionSection selectionData={selectionData} refreshSelection={refreshSelection} updateSelection={updateSelection} />
<ClientSelectionSection selectionData={selectionData} refreshSelection={refreshSelection} updateSelection={updateSelection} refreshMenu={refreshMenu} />
</Suspense>
{/* Menu Items Section */}
<Suspense fallback={<div><LoadingContent height="h-16" size="w-36 h-48" key={"loading-content"} plane="h-full w-full" /></div>}>

View File

@ -11,7 +11,7 @@ import {
MenuStructure,
MenuItemsSectionProps,
} from "./types";
import MenuEmptyState from "./menuEmptyState";
const menuStaticTranslation = {
tr: { menu: "Menü" },
@ -26,6 +26,7 @@ const MenuItemsSection: FC<MenuItemsSectionProps> = ({ availableApplications, ac
const [activeSecondLayer, setActiveSecondLayer] = useState<string | null>(null);
const [activeThirdLayer, setActiveThirdLayer] = useState<string | null>(null);
useEffect(() => {
const newMenuStructure: MenuStructure = {};
const menuTranslationWLang = menuTranslation[lang as keyof typeof menuTranslation];
@ -82,6 +83,8 @@ const MenuItemsSection: FC<MenuItemsSectionProps> = ({ availableApplications, ac
};
const renderFirstLayerItems = () => {
return Object.entries(menuStructure).map(([firstLayerKey, secondLayerItems]) => {
const isActive = activeFirstLayer === firstLayerKey;
const isExpanded = expandedFirstLayer === firstLayerKey;
@ -92,13 +95,35 @@ const MenuItemsSection: FC<MenuItemsSectionProps> = ({ availableApplications, ac
return (
<div key={`${firstLayerKey}-item`} className="mb-2">
<FirstLayerDropdown isActive={isActive} isExpanded={isExpanded} innerText={displayText} onClick={() => handleFirstLayerClick(firstLayerKey)} />
{<FirstLayerDropdown isActive={isActive} isExpanded={isExpanded} innerText={displayText} onClick={() => handleFirstLayerClick(firstLayerKey)} />}
{isExpanded && <div className="mt-1">{renderSecondLayerItems(firstLayerKey, secondLayerItems)}</div>}
</div>
);
});
};
return <div className="mt-1"><h3 className="text-sm font-semibold mb-1">{menuStaticTranslation[lang as keyof typeof menuStaticTranslation].menu}</h3>{renderFirstLayerItems()}</div>;
return (
<>
<div className="flex flex-col">
<div className="mt-1 flex justify-center"><h3 className="text-sm font-semibold mb-1">{menuStaticTranslation[lang as keyof typeof menuStaticTranslation].menu}</h3></div>
<br />
{
availableApplications.length !== 0 && <div className="">{renderFirstLayerItems()}</div>
}
{
availableApplications.length === 0 && <div>
<div className="flex justify-center items-center h-full">
<div className="text-center text-gray-500 dark:text-gray-400">
<p className="text-sm">No menu items available</p>
<p className="text-xs mt-1">Please check your permissions or contact an administrator</p>
</div>
</div>
</div>
}
</div>
</>
)
};
export default MenuItemsSection;

View File

@ -7,15 +7,26 @@ interface Props {
selectedItem: any;
refreshSelection: () => Promise<void>;
updateSelection: (newSelection: any) => Promise<boolean>;
refreshMenu: () => Promise<void>;
}
const selectFromSelectionList = (item: any, selectedItem: any, updateSelection: (newSelection: any) => Promise<boolean>, refreshSelection: () => Promise<void>) => {
const selectFromSelectionList = async (item: any, selectedItem: any, updateSelection: (newSelection: any) => Promise<boolean>, refreshSelection: () => Promise<void>, refreshMenu: () => Promise<void>) => {
const selectedUUID = selectedItem?.uu_id || selectedItem?.build_living_space_uu_id;
const itemUUID = item?.uu_id || item?.build_living_space_uu_id;
if (selectedUUID !== itemUUID) { updateSelection(item); refreshSelection() }
if (selectedUUID !== itemUUID) {
// First update the selection
await updateSelection(item);
// Then refresh the selection
await refreshSelection();
// Wait a moment to ensure the selection update is processed
setTimeout(async () => {
// Finally refresh the menu
await refreshMenu();
}, 300);
}
}
const RenderOneClientSelection: FC<Props> = ({ item, selectedItem, updateSelection, refreshSelection }) => {
const RenderOneClientSelection: FC<Props> = ({ item, selectedItem, updateSelection, refreshSelection, refreshMenu }) => {
const selectedUUID = selectedItem?.uu_id || selectedItem?.build_living_space_uu_id;
const itemUUID = item?.uu_id || item?.build_living_space_uu_id;
const getDisplayName = () => { if (item.public_name) return item.public_name; if (item.part_name) return item.part_name; return "No Name" };
@ -24,7 +35,7 @@ const RenderOneClientSelection: FC<Props> = ({ item, selectedItem, updateSelecti
if (selectedUUID !== itemUUID) {
return (
<div key={itemUUID} onClick={() => { selectFromSelectionList(item, selectedItem, updateSelection, refreshSelection) }}
<div key={itemUUID} onClick={() => { selectFromSelectionList(item, selectedItem, updateSelection, refreshSelection, refreshMenu) }}
className="w-full text-xs bg-white shadow rounded-lg overflow-hidden transition-all hover:shadow-md mb-2 cursor-pointer">
<div className="bg-amber-800 p-2 hover:bg-amber-900 transition-all">
<div className="flex items-center">

View File

@ -12,7 +12,6 @@ interface UserProfileSectionProps {
}
const UserProfileSection: FC<UserProfileSectionProps> = ({ userData, onlineData, selectionData }) => {
console.log("selectionData", selectionData)
if (!selectionData?.activeSelection) return null;
let selectionDefinitionFirst = "";
let selectionDefinitionSecond = "";

View File

@ -54,6 +54,8 @@ interface UseMenuResult {
export function useMenu(): UseMenuResult {
const { data, availableItems, isLoading, error, refresh, update } =
useContextMenu();
console.log("data", data);
console.log("availableItems", availableItems);
return {
menuData: data,

View File

@ -17,7 +17,7 @@ const getMenuFromRedis = async (): Promise<ClientMenu> => {
} catch (error) { if (error instanceof AuthError) { throw error } else { throw new AuthError(error instanceof Error ? error.message : "Unknown error") } }
}
const setMenuToRedis = async (menuObject: ClientMenu) => {
const setMenuToRedis = async (menuObject: string[]) => {
try {
const decrpytUserSelection = await functionRetrieveUserSelection()
if (!decrpytUserSelection) throw new AuthError("No user selection found");
@ -26,7 +26,12 @@ const setMenuToRedis = async (menuObject: ClientMenu) => {
if (!menuObject) throw new AuthError("No menu object provided");
const oldData = await getCompleteFromRedis();
if (!oldData) throw new AuthError("No old data found in redis");
await setCompleteToRedis({ ...oldData, menu: menuObject });
await setCompleteToRedis({
...oldData, menu: {
...oldData.menu,
selectionList: menuObject,
}
});
return true;
} catch (error) { if (error instanceof AuthError) { throw error } else { throw new AuthError(error instanceof Error ? error.message : "Unknown error") } }
}

View File

@ -16,6 +16,7 @@ import { retrievePageList } from "@/fetchers/mutual/cookies/token";
import { deleteAllCookies } from "@/fetchers/mutual/cookies/cookie-actions";
import { setMenuToRedis } from "@/fetchers/custom/context/page/menu/fetch";
import { LoginViaAccessKeys, LoginSelect } from "@/fetchers/types/login/validations";
import { retrieveValidUrlsOfRestriction } from "../restriction/fetch";
async function logoutActiveSession() {
const response = await fetchDataWithToken(urlLogoutEndpoint, {}, "GET", false);
@ -157,7 +158,8 @@ async function loginSelectEmployee(payload: LoginSelect) {
if (selectResponse.status === 200 || selectResponse.status === 202) {
try {
console.log("selectResponse", selectResponse) // Get Menu URL's of Employee
const validUrls = await retrieveValidUrlsOfRestriction()
setMenuToRedis(validUrls)
} catch (error) { }
}
return selectResponse;
@ -169,6 +171,8 @@ async function loginSelectOccupant(payload: LoginSelect) {
if (selectResponse.status === 200 || selectResponse.status === 202) {
try {
console.log("selectResponse", selectResponse) // Get Menu URL's of Occupant
const validUrls = await retrieveValidUrlsOfRestriction()
setMenuToRedis(validUrls)
} catch (error) { }
}
return selectResponse;

View File

@ -0,0 +1,13 @@
"use server";
import { fetchDataWithToken } from "@/fetchers/fecther";
import { fetchResponseStatus } from "@/fetchers/utils";
import { urlSiteUrls } from "@/fetchers/index";
async function retrieveValidUrlsOfRestriction() {
const response = await fetchDataWithToken(urlSiteUrls, {}, "GET", false);
if (!fetchResponseStatus(response)) throw new Error("No valid urls found");
const data = response.data as any;
return data.sites;
}
export { retrieveValidUrlsOfRestriction }