Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
688 changes: 665 additions & 23 deletions CageUI/package-lock.json

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming this file works correctly as i'm not able to test all these nodes.

Ran it through Claude just to double check and the only catch is that it looks like this also deals with the @mui/material issue i mentioned in another comment. This file shows @mui/material resolved to 9.3.1 while @mui/x-data-grid resolved to 8.29.2. x-data-grid's peerDependencies only allow @mui/material 5, 6, and 7. Same as the other comment, feel free to leave this but just keep an eye on any future breakage due to this package combo being unsupported.

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions CageUI/package.json

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Risk here: "@mui/x-data-grid": "^8.28.6",

Apparently this only supports @mui/material 5, 6, or 7 but this project pins @ 9.

Fine to leave this, just know this is unsupported and we'll have to keep an eye on this to verify it doesn't break on any future updates.

Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,20 @@
"author": "Board of Regents of the University of Wisconsin System",
"license": "Apache-2.0",
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@labkey/api": "1.51.5",
"@labkey/components": "7.43.0",
"@mui/icons-material": "^9.0.1",
"@mui/material": "^9.0.1",
"@mui/x-data-grid": "^8.28.6",
"@mui/x-date-pickers": "^9.2.0",
"d3": "^7.9.0",
"dayjs": "^1.11.21",
"react": "~18.3.1",
"react-bootstrap": "~2.10.10",
"react-dom": "~18.3.1",
"react-is": "^18.3.1",
"react-select": "^5.10.2",
"react-svg": "^16.4.2"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,36 @@ select setname, container, 8 as value, 'Caging' as category, 'Ghost Cage' as tit

insert into ehr_lookups.lookups (set_name,container,value, title)
select setname, container, 'ghostCage' as value, '/cageui/static/cage.svg' as title from ehr_lookups.lookup_sets where setname='cageui_svg_urls';

INSERT INTO ehr_lookups.lookup_sets (setname, label, description, keyField, container)
select 'adoption_status' as setname,
'Adoption Status Field Values' as label,
'List of possible adoption progress statuses' as description,
'value' as keyField,
container from ehr_lookups.lookup_sets where setname='ancestry';

insert into ehr_lookups.lookups (set_name,container,value, title)
select setname, container, 0 as value, 'Start' as title from ehr_lookups.lookup_sets where setname='adoption_status';

insert into ehr_lookups.lookups (set_name,container,value, title)
select setname, container, 1 as value, 'End' as title from ehr_lookups.lookup_sets where setname='adoption_status';

insert into ehr_lookups.lookups (set_name,container,value, title)
select setname, container, 2 as value, 'Pause' as title from ehr_lookups.lookup_sets where setname='adoption_status';

insert into ehr_lookups.lookups (set_name,container,value, title)
select setname, container, 3 as value, 'Resume' as title from ehr_lookups.lookup_sets where setname='adoption_status';


INSERT INTO ehr_lookups.lookup_sets (setname, label, description, keyField, container)
select 'adoption_results' as setname,
'Adoption Result Field Values' as label,
'List of possible adoption results' as description,
'value' as keyField,
container from ehr_lookups.lookup_sets where setname='ancestry';

insert into ehr_lookups.lookups (set_name,container,value, title)
select setname, container, 0 as value, 'Success' as title from ehr_lookups.lookup_sets where setname='adoption_results';

insert into ehr_lookups.lookups (set_name,container,value, title)
select setname, container, 1 as value, 'Failure' as title from ehr_lookups.lookup_sets where setname='adoption_results';
17 changes: 17 additions & 0 deletions CageUI/src/client/api/labkeyActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { ActionURL, Ajax, Query, Security, Utils } from '@labkey/api';
import { CageMods, Rack, RackConditionOption, Room, SessionLog } from '../types/typings';
import { buildURL } from '@labkey/components';
import { RackSwitchOption } from '../types/homeTypes';
import { AdoptionData } from '../types/adoptionFormTypes';

export function labkeyActionSelectWithPromise(
options: Query.SelectRowsOptions,
Expand Down Expand Up @@ -193,4 +194,20 @@ export function updateRackConditionStatus(rack: RackSwitchOption, condition: Rac
jsonData: {rack: rack.value.objectId, condition: condition.value},
});
});
}

// This function is for submitting a adoption form.
export function startAdoptionSubmission(animals: AdoptionData[]): Promise<{
success: boolean,
errors: any[]
}> {
return new Promise((resolve, reject) => {
Ajax.request({
url: buildURL('cageui', 'submitAdoptionForm.api'),
method: 'POST',
success: (res) => resolve(JSON.parse(res.response)),
failure: Utils.getCallbackWrapper((error) => reject(error)),
jsonData: {adoptionData: animals},
});
});
}
61 changes: 40 additions & 21 deletions CageUI/src/client/cageui.scss
Original file line number Diff line number Diff line change
Expand Up @@ -673,30 +673,26 @@
}

.loading-overlay {
position: absolute;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.30);
display: flex;
justify-content: center;
align-items: start;
align-items: center;
border-radius: 8px;
z-index: 9999;
backdrop-filter: blur(5px);
}

.loading-content {
position: sticky;
top: 25%;
left: 50%;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
padding-top: 20px;
}

.spinner {
Expand Down Expand Up @@ -1711,9 +1707,6 @@ margin-top: 0px;
box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.2);
}




.modification-editor {

}
Expand All @@ -1724,23 +1717,13 @@ margin-top: 0px;
border-bottom: lightgrey 5px solid;
}

.modification-editor-input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.modification-editor-content {
margin-bottom: 20px;
display: flex;
gap: 10px;
flex-direction: row;
}




@keyframes fadeIn {
from {
opacity: 0;
Expand All @@ -1752,8 +1735,7 @@ margin-top: 0px;
}
}


.animal-editor{
.animal-editor {

}

Expand All @@ -1763,6 +1745,7 @@ margin-top: 0px;
}



/*
Multi Dropdown Css

Expand Down Expand Up @@ -2217,3 +2200,39 @@ Multi Dropdown Css
height: 100% !important;
overflow: hidden !important;
}


.MuiDataGrid-form-container {
display: grid;
grid-template-columns: minmax(0, 1fr);
position: relative;
width: 100%;
gap: 10px;

.MuiDataGrid-root {
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
width: 100%;
overflow: hidden; // Ensure DataGrid handles its own internal overflow

.MuiDataGrid-main {
min-width: 0;
}

.MuiDataGrid-cell:focus-within {
outline: none;
}
}
}

.form-actions {
display: flex;
justify-content: flex-end;
gap: 15px;
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
position: sticky;
bottom: 0;
}
85 changes: 85 additions & 0 deletions CageUI/src/client/components/AutoCompleteEditCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
*
* * Copyright (c) 2026 Board of Regents of the University of Wisconsin System
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
import * as React from 'react';
import { useState } from 'react';
import { GridRenderEditCellParams, useGridApiContext } from '@mui/x-data-grid';
import { Autocomplete, TextField } from '@mui/material';

interface AutoCompleteEditCellParams {
options: any[]
required: boolean;
multiple?: boolean;
disableClearable?: boolean;
returnValueOnly?: boolean;
}

export const AutoCompleteEditCell = (props: GridRenderEditCellParams & AutoCompleteEditCellParams) => {
const { id, field, value, options, required, multiple, disableClearable, returnValueOnly } = props;
const apiRef = useGridApiContext();
const [open, setOpen] = useState(true);

const handleChange = (event: any, newValue: any) => {
const val = returnValueOnly && newValue ? newValue.value : newValue;
apiRef.current.setEditCellValue({ id, field, value: val });
if (!multiple && (newValue || newValue === null)) {
apiRef.current.stopCellEditMode({ id, field });
}
};

const handleKeyDown = (event: React.KeyboardEvent) => {
if (event.key === 'Tab') {
apiRef.current.stopCellEditMode({ id, field });
}
};

const isError = required && (value === null || value === undefined || (Array.isArray(value) && value.length === 0) || value === '');
const selectedOption = multiple ? (value || []) : (options.find(opt => opt.value === value || opt === value || (typeof value === 'object' && value !== null && opt.value === value.value)) || null);

return (
<Autocomplete
options={options}
getOptionLabel={(option) => option.label || ''}
value={selectedOption}
onChange={handleChange}
open={open}
onOpen={() => setOpen(true)}
onClose={(event, reason) => {
if (reason === 'selectOption' || reason === 'blur' || reason === 'escape') {
setOpen(false);
}
}}
fullWidth
multiple={multiple}
disableClearable={disableClearable}
isOptionEqualToValue={(option, value) => {
const val = (value && typeof value === 'object' && 'value' in value) ? value.value : value;
return option.value === val;
}}
renderInput={(params) => (
<TextField
{...params}
autoFocus
variant="standard"
onKeyDown={handleKeyDown}
required={required}
error={isError}
/>
)}
/>
);
};
Loading