diff --git a/CareTogetherCMS.sln b/CareTogetherCMS.sln index a94538ae..7e849d13 100644 --- a/CareTogetherCMS.sln +++ b/CareTogetherCMS.sln @@ -1,10 +1,10 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.32014.148 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deploy", "deploy", "{B04053C5-CA17-41B1-BC52-8B5393090D45}" - ProjectSection(SolutionItems) = preProject + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32014.148 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deploy", "deploy", "{B04053C5-CA17-41B1-BC52-8B5393090D45}" + ProjectSection(SolutionItems) = preProject .github\workflows\cd-prod-dotnet.yml = .github\workflows\cd-prod-dotnet.yml .github\workflows\cd-prod-react.yml = .github\workflows\cd-prod-react.yml .github\workflows\ci-pr-dotnet.yml = .github\workflows\ci-pr-dotnet.yml @@ -13,198 +13,198 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deploy", "deploy", "{B04053 .github\workflows\playwright.yml = .github\workflows\playwright.yml EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CareTogether.Api", "src\CareTogether.Api\CareTogether.Api.csproj", "{67F0E369-C927-4278-A7D0-DDFBE029003F}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{465BA74E-CEF3-4608-8809-8C7A2E979DAC}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "b2c", "b2c", "{7787CCCA-D8C1-4E53-8EBA-1A07B5DEA5AB}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CustomPolicies", "CustomPolicies", "{E477275E-8530-49B4-ADBB-D61DD5E79BD6}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dev", "Dev", "{7B8B0240-E608-4349-89AD-AB5E410BD273}" - ProjectSection(SolutionItems) = preProject - src\b2c\CustomPolicies\Dev\PasswordReset.xml = src\b2c\CustomPolicies\Dev\PasswordReset.xml - src\b2c\CustomPolicies\Dev\ProfileEdit.xml = src\b2c\CustomPolicies\Dev\ProfileEdit.xml - src\b2c\CustomPolicies\Dev\readme.md = src\b2c\CustomPolicies\Dev\readme.md - src\b2c\CustomPolicies\Dev\SignUpOrSignin.xml = src\b2c\CustomPolicies\Dev\SignUpOrSignin.xml - src\b2c\CustomPolicies\Dev\TrustFrameworkBase.xml = src\b2c\CustomPolicies\Dev\TrustFrameworkBase.xml - src\b2c\CustomPolicies\Dev\TrustFrameworkExtensions.xml = src\b2c\CustomPolicies\Dev\TrustFrameworkExtensions.xml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Prod", "Prod", "{2DC7B8F3-2B29-4EC9-BFFF-CC49C817469C}" - ProjectSection(SolutionItems) = preProject - src\b2c\CustomPolicies\Prod\PasswordReset.xml = src\b2c\CustomPolicies\Prod\PasswordReset.xml - src\b2c\CustomPolicies\Prod\ProfileEdit.xml = src\b2c\CustomPolicies\Prod\ProfileEdit.xml - src\b2c\CustomPolicies\Prod\readme.md = src\b2c\CustomPolicies\Prod\readme.md - src\b2c\CustomPolicies\Prod\SignUpOrSignin.xml = src\b2c\CustomPolicies\Prod\SignUpOrSignin.xml - src\b2c\CustomPolicies\Prod\TrustFrameworkBase.xml = src\b2c\CustomPolicies\Prod\TrustFrameworkBase.xml - src\b2c\CustomPolicies\Prod\TrustFrameworkExtensions.xml = src\b2c\CustomPolicies\Prod\TrustFrameworkExtensions.xml - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CareTogether.Core", "src\CareTogether.Core\CareTogether.Core.csproj", "{82B69D6F-DA25-452B-883D-71B9AE91DF23}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CareTogether.Core.Test", "test\CareTogether.Core.Test\CareTogether.Core.Test.csproj", "{B2CA1F10-216E-404E-A3C2-70423D5BCF5D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{42D43EAF-EE89-479A-B304-3B5DF56A3494}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4911A81B-E06B-4090-9252-4359580C3C8E}" - ProjectSection(SolutionItems) = preProject - .github\CODEOWNERS = .github\CODEOWNERS - LICENSE = LICENSE - README.md = README.md - swagger.json = swagger.json - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CareTogether.TestData", "test\CareTogether.TestData\CareTogether.TestData.csproj", "{7DC7327F-6F01-4561-943D-D385FB5637E1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Timelines", "src\Timelines\Timelines.csproj", "{97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Timelines.Test", "test\Timelines.Test\Timelines.Test.csproj", "{3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CareTogether.AppHost", "src\CareTogether.AppHost\CareTogether.AppHost.csproj", "{7AB05778-70B4-4B95-BE67-D07802D0B57B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CareTogether.ServiceDefaults", "src\CareTogether.ServiceDefaults\CareTogether.ServiceDefaults.csproj", "{3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CareTogether.AppHost.Tests", "test\CareTogether.AppHost.Tests\CareTogether.AppHost.Tests.csproj", "{E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {67F0E369-C927-4278-A7D0-DDFBE029003F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {67F0E369-C927-4278-A7D0-DDFBE029003F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {67F0E369-C927-4278-A7D0-DDFBE029003F}.Debug|x64.ActiveCfg = Debug|Any CPU - {67F0E369-C927-4278-A7D0-DDFBE029003F}.Debug|x64.Build.0 = Debug|Any CPU - {67F0E369-C927-4278-A7D0-DDFBE029003F}.Debug|x86.ActiveCfg = Debug|Any CPU - {67F0E369-C927-4278-A7D0-DDFBE029003F}.Debug|x86.Build.0 = Debug|Any CPU - {67F0E369-C927-4278-A7D0-DDFBE029003F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {67F0E369-C927-4278-A7D0-DDFBE029003F}.Release|Any CPU.Build.0 = Release|Any CPU - {67F0E369-C927-4278-A7D0-DDFBE029003F}.Release|x64.ActiveCfg = Release|Any CPU - {67F0E369-C927-4278-A7D0-DDFBE029003F}.Release|x64.Build.0 = Release|Any CPU - {67F0E369-C927-4278-A7D0-DDFBE029003F}.Release|x86.ActiveCfg = Release|Any CPU - {67F0E369-C927-4278-A7D0-DDFBE029003F}.Release|x86.Build.0 = Release|Any CPU - {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Debug|Any CPU.Build.0 = Debug|Any CPU - {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Debug|x64.ActiveCfg = Debug|Any CPU - {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Debug|x64.Build.0 = Debug|Any CPU - {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Debug|x86.ActiveCfg = Debug|Any CPU - {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Debug|x86.Build.0 = Debug|Any CPU - {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Release|Any CPU.ActiveCfg = Release|Any CPU - {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Release|Any CPU.Build.0 = Release|Any CPU - {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Release|x64.ActiveCfg = Release|Any CPU - {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Release|x64.Build.0 = Release|Any CPU - {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Release|x86.ActiveCfg = Release|Any CPU - {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Release|x86.Build.0 = Release|Any CPU - {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Debug|x64.ActiveCfg = Debug|Any CPU - {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Debug|x64.Build.0 = Debug|Any CPU - {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Debug|x86.ActiveCfg = Debug|Any CPU - {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Debug|x86.Build.0 = Debug|Any CPU - {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Release|Any CPU.Build.0 = Release|Any CPU - {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Release|x64.ActiveCfg = Release|Any CPU - {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Release|x64.Build.0 = Release|Any CPU - {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Release|x86.ActiveCfg = Release|Any CPU - {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Release|x86.Build.0 = Release|Any CPU - {7DC7327F-6F01-4561-943D-D385FB5637E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7DC7327F-6F01-4561-943D-D385FB5637E1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7DC7327F-6F01-4561-943D-D385FB5637E1}.Debug|x64.ActiveCfg = Debug|Any CPU - {7DC7327F-6F01-4561-943D-D385FB5637E1}.Debug|x64.Build.0 = Debug|Any CPU - {7DC7327F-6F01-4561-943D-D385FB5637E1}.Debug|x86.ActiveCfg = Debug|Any CPU - {7DC7327F-6F01-4561-943D-D385FB5637E1}.Debug|x86.Build.0 = Debug|Any CPU - {7DC7327F-6F01-4561-943D-D385FB5637E1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7DC7327F-6F01-4561-943D-D385FB5637E1}.Release|Any CPU.Build.0 = Release|Any CPU - {7DC7327F-6F01-4561-943D-D385FB5637E1}.Release|x64.ActiveCfg = Release|Any CPU - {7DC7327F-6F01-4561-943D-D385FB5637E1}.Release|x64.Build.0 = Release|Any CPU - {7DC7327F-6F01-4561-943D-D385FB5637E1}.Release|x86.ActiveCfg = Release|Any CPU - {7DC7327F-6F01-4561-943D-D385FB5637E1}.Release|x86.Build.0 = Release|Any CPU - {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Debug|x64.ActiveCfg = Debug|Any CPU - {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Debug|x64.Build.0 = Debug|Any CPU - {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Debug|x86.ActiveCfg = Debug|Any CPU - {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Debug|x86.Build.0 = Debug|Any CPU - {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Release|Any CPU.Build.0 = Release|Any CPU - {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Release|x64.ActiveCfg = Release|Any CPU - {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Release|x64.Build.0 = Release|Any CPU - {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Release|x86.ActiveCfg = Release|Any CPU - {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Release|x86.Build.0 = Release|Any CPU - {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Debug|x64.ActiveCfg = Debug|Any CPU - {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Debug|x64.Build.0 = Debug|Any CPU - {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Debug|x86.ActiveCfg = Debug|Any CPU - {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Debug|x86.Build.0 = Debug|Any CPU - {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Release|Any CPU.Build.0 = Release|Any CPU - {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Release|x64.ActiveCfg = Release|Any CPU - {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Release|x64.Build.0 = Release|Any CPU - {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Release|x86.ActiveCfg = Release|Any CPU - {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Release|x86.Build.0 = Release|Any CPU +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CareTogether.Api", "src\CareTogether.Api\CareTogether.Api.csproj", "{67F0E369-C927-4278-A7D0-DDFBE029003F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{465BA74E-CEF3-4608-8809-8C7A2E979DAC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "b2c", "b2c", "{7787CCCA-D8C1-4E53-8EBA-1A07B5DEA5AB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CustomPolicies", "CustomPolicies", "{E477275E-8530-49B4-ADBB-D61DD5E79BD6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dev", "Dev", "{7B8B0240-E608-4349-89AD-AB5E410BD273}" + ProjectSection(SolutionItems) = preProject + src\b2c\CustomPolicies\Dev\PasswordReset.xml = src\b2c\CustomPolicies\Dev\PasswordReset.xml + src\b2c\CustomPolicies\Dev\ProfileEdit.xml = src\b2c\CustomPolicies\Dev\ProfileEdit.xml + src\b2c\CustomPolicies\Dev\readme.md = src\b2c\CustomPolicies\Dev\readme.md + src\b2c\CustomPolicies\Dev\SignUpOrSignin.xml = src\b2c\CustomPolicies\Dev\SignUpOrSignin.xml + src\b2c\CustomPolicies\Dev\TrustFrameworkBase.xml = src\b2c\CustomPolicies\Dev\TrustFrameworkBase.xml + src\b2c\CustomPolicies\Dev\TrustFrameworkExtensions.xml = src\b2c\CustomPolicies\Dev\TrustFrameworkExtensions.xml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Prod", "Prod", "{2DC7B8F3-2B29-4EC9-BFFF-CC49C817469C}" + ProjectSection(SolutionItems) = preProject + src\b2c\CustomPolicies\Prod\PasswordReset.xml = src\b2c\CustomPolicies\Prod\PasswordReset.xml + src\b2c\CustomPolicies\Prod\ProfileEdit.xml = src\b2c\CustomPolicies\Prod\ProfileEdit.xml + src\b2c\CustomPolicies\Prod\readme.md = src\b2c\CustomPolicies\Prod\readme.md + src\b2c\CustomPolicies\Prod\SignUpOrSignin.xml = src\b2c\CustomPolicies\Prod\SignUpOrSignin.xml + src\b2c\CustomPolicies\Prod\TrustFrameworkBase.xml = src\b2c\CustomPolicies\Prod\TrustFrameworkBase.xml + src\b2c\CustomPolicies\Prod\TrustFrameworkExtensions.xml = src\b2c\CustomPolicies\Prod\TrustFrameworkExtensions.xml + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CareTogether.Core", "src\CareTogether.Core\CareTogether.Core.csproj", "{82B69D6F-DA25-452B-883D-71B9AE91DF23}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CareTogether.Core.Test", "test\CareTogether.Core.Test\CareTogether.Core.Test.csproj", "{B2CA1F10-216E-404E-A3C2-70423D5BCF5D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{42D43EAF-EE89-479A-B304-3B5DF56A3494}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4911A81B-E06B-4090-9252-4359580C3C8E}" + ProjectSection(SolutionItems) = preProject + .github\CODEOWNERS = .github\CODEOWNERS + LICENSE = LICENSE + README.md = README.md + swagger.json = swagger.json + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CareTogether.TestData", "test\CareTogether.TestData\CareTogether.TestData.csproj", "{7DC7327F-6F01-4561-943D-D385FB5637E1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Timelines", "src\Timelines\Timelines.csproj", "{97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Timelines.Test", "test\Timelines.Test\Timelines.Test.csproj", "{3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CareTogether.AppHost", "src\CareTogether.AppHost\CareTogether.AppHost.csproj", "{7AB05778-70B4-4B95-BE67-D07802D0B57B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CareTogether.ServiceDefaults", "src\CareTogether.ServiceDefaults\CareTogether.ServiceDefaults.csproj", "{3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CareTogether.AppHost.Tests", "test\CareTogether.AppHost.Tests\CareTogether.AppHost.Tests.csproj", "{E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {67F0E369-C927-4278-A7D0-DDFBE029003F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67F0E369-C927-4278-A7D0-DDFBE029003F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67F0E369-C927-4278-A7D0-DDFBE029003F}.Debug|x64.ActiveCfg = Debug|Any CPU + {67F0E369-C927-4278-A7D0-DDFBE029003F}.Debug|x64.Build.0 = Debug|Any CPU + {67F0E369-C927-4278-A7D0-DDFBE029003F}.Debug|x86.ActiveCfg = Debug|Any CPU + {67F0E369-C927-4278-A7D0-DDFBE029003F}.Debug|x86.Build.0 = Debug|Any CPU + {67F0E369-C927-4278-A7D0-DDFBE029003F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67F0E369-C927-4278-A7D0-DDFBE029003F}.Release|Any CPU.Build.0 = Release|Any CPU + {67F0E369-C927-4278-A7D0-DDFBE029003F}.Release|x64.ActiveCfg = Release|Any CPU + {67F0E369-C927-4278-A7D0-DDFBE029003F}.Release|x64.Build.0 = Release|Any CPU + {67F0E369-C927-4278-A7D0-DDFBE029003F}.Release|x86.ActiveCfg = Release|Any CPU + {67F0E369-C927-4278-A7D0-DDFBE029003F}.Release|x86.Build.0 = Release|Any CPU + {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Debug|Any CPU.Build.0 = Debug|Any CPU + {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Debug|x64.ActiveCfg = Debug|Any CPU + {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Debug|x64.Build.0 = Debug|Any CPU + {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Debug|x86.ActiveCfg = Debug|Any CPU + {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Debug|x86.Build.0 = Debug|Any CPU + {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Release|Any CPU.ActiveCfg = Release|Any CPU + {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Release|Any CPU.Build.0 = Release|Any CPU + {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Release|x64.ActiveCfg = Release|Any CPU + {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Release|x64.Build.0 = Release|Any CPU + {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Release|x86.ActiveCfg = Release|Any CPU + {82B69D6F-DA25-452B-883D-71B9AE91DF23}.Release|x86.Build.0 = Release|Any CPU + {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Debug|x64.ActiveCfg = Debug|Any CPU + {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Debug|x64.Build.0 = Debug|Any CPU + {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Debug|x86.ActiveCfg = Debug|Any CPU + {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Debug|x86.Build.0 = Debug|Any CPU + {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Release|Any CPU.Build.0 = Release|Any CPU + {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Release|x64.ActiveCfg = Release|Any CPU + {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Release|x64.Build.0 = Release|Any CPU + {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Release|x86.ActiveCfg = Release|Any CPU + {B2CA1F10-216E-404E-A3C2-70423D5BCF5D}.Release|x86.Build.0 = Release|Any CPU + {7DC7327F-6F01-4561-943D-D385FB5637E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7DC7327F-6F01-4561-943D-D385FB5637E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7DC7327F-6F01-4561-943D-D385FB5637E1}.Debug|x64.ActiveCfg = Debug|Any CPU + {7DC7327F-6F01-4561-943D-D385FB5637E1}.Debug|x64.Build.0 = Debug|Any CPU + {7DC7327F-6F01-4561-943D-D385FB5637E1}.Debug|x86.ActiveCfg = Debug|Any CPU + {7DC7327F-6F01-4561-943D-D385FB5637E1}.Debug|x86.Build.0 = Debug|Any CPU + {7DC7327F-6F01-4561-943D-D385FB5637E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7DC7327F-6F01-4561-943D-D385FB5637E1}.Release|Any CPU.Build.0 = Release|Any CPU + {7DC7327F-6F01-4561-943D-D385FB5637E1}.Release|x64.ActiveCfg = Release|Any CPU + {7DC7327F-6F01-4561-943D-D385FB5637E1}.Release|x64.Build.0 = Release|Any CPU + {7DC7327F-6F01-4561-943D-D385FB5637E1}.Release|x86.ActiveCfg = Release|Any CPU + {7DC7327F-6F01-4561-943D-D385FB5637E1}.Release|x86.Build.0 = Release|Any CPU + {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Debug|x64.ActiveCfg = Debug|Any CPU + {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Debug|x64.Build.0 = Debug|Any CPU + {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Debug|x86.ActiveCfg = Debug|Any CPU + {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Debug|x86.Build.0 = Debug|Any CPU + {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Release|Any CPU.Build.0 = Release|Any CPU + {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Release|x64.ActiveCfg = Release|Any CPU + {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Release|x64.Build.0 = Release|Any CPU + {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Release|x86.ActiveCfg = Release|Any CPU + {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9}.Release|x86.Build.0 = Release|Any CPU + {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Debug|x64.ActiveCfg = Debug|Any CPU + {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Debug|x64.Build.0 = Debug|Any CPU + {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Debug|x86.ActiveCfg = Debug|Any CPU + {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Debug|x86.Build.0 = Debug|Any CPU + {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Release|Any CPU.Build.0 = Release|Any CPU + {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Release|x64.ActiveCfg = Release|Any CPU + {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Release|x64.Build.0 = Release|Any CPU + {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Release|x86.ActiveCfg = Release|Any CPU + {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA}.Release|x86.Build.0 = Release|Any CPU {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Debug|Any CPU.Build.0 = Debug|Any CPU {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Debug|x64.ActiveCfg = Debug|Any CPU - {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Debug|x64.Build.0 = Debug|Any CPU - {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Debug|x86.ActiveCfg = Debug|Any CPU - {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Debug|x86.Build.0 = Debug|Any CPU - {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Release|Any CPU.Build.0 = Release|Any CPU - {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Release|x64.ActiveCfg = Release|Any CPU - {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Release|x64.Build.0 = Release|Any CPU - {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Release|x86.ActiveCfg = Release|Any CPU - {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Release|x86.Build.0 = Release|Any CPU - {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Debug|x64.ActiveCfg = Debug|Any CPU - {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Debug|x64.Build.0 = Debug|Any CPU - {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Debug|x86.ActiveCfg = Debug|Any CPU - {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Debug|x86.Build.0 = Debug|Any CPU - {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Release|Any CPU.Build.0 = Release|Any CPU - {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Release|x64.ActiveCfg = Release|Any CPU - {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Release|x64.Build.0 = Release|Any CPU - {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Release|x86.ActiveCfg = Release|Any CPU - {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Release|x86.Build.0 = Release|Any CPU - {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}.Debug|x64.ActiveCfg = Debug|Any CPU - {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}.Debug|x64.Build.0 = Debug|Any CPU - {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}.Debug|x86.ActiveCfg = Debug|Any CPU - {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}.Debug|x86.Build.0 = Debug|Any CPU - {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}.Release|Any CPU.Build.0 = Release|Any CPU + {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Debug|x64.Build.0 = Debug|Any CPU + {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Debug|x86.ActiveCfg = Debug|Any CPU + {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Debug|x86.Build.0 = Debug|Any CPU + {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Release|Any CPU.Build.0 = Release|Any CPU + {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Release|x64.ActiveCfg = Release|Any CPU + {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Release|x64.Build.0 = Release|Any CPU + {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Release|x86.ActiveCfg = Release|Any CPU + {7AB05778-70B4-4B95-BE67-D07802D0B57B}.Release|x86.Build.0 = Release|Any CPU + {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Debug|x64.ActiveCfg = Debug|Any CPU + {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Debug|x64.Build.0 = Debug|Any CPU + {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Debug|x86.ActiveCfg = Debug|Any CPU + {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Debug|x86.Build.0 = Debug|Any CPU + {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Release|Any CPU.Build.0 = Release|Any CPU + {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Release|x64.ActiveCfg = Release|Any CPU + {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Release|x64.Build.0 = Release|Any CPU + {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Release|x86.ActiveCfg = Release|Any CPU + {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4}.Release|x86.Build.0 = Release|Any CPU + {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}.Debug|x64.ActiveCfg = Debug|Any CPU + {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}.Debug|x64.Build.0 = Debug|Any CPU + {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}.Debug|x86.ActiveCfg = Debug|Any CPU + {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}.Debug|x86.Build.0 = Debug|Any CPU + {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}.Release|Any CPU.Build.0 = Release|Any CPU {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}.Release|x64.ActiveCfg = Release|Any CPU {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}.Release|x64.Build.0 = Release|Any CPU {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}.Release|x86.ActiveCfg = Release|Any CPU {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {67F0E369-C927-4278-A7D0-DDFBE029003F} = {465BA74E-CEF3-4608-8809-8C7A2E979DAC} - {7787CCCA-D8C1-4E53-8EBA-1A07B5DEA5AB} = {465BA74E-CEF3-4608-8809-8C7A2E979DAC} - {E477275E-8530-49B4-ADBB-D61DD5E79BD6} = {7787CCCA-D8C1-4E53-8EBA-1A07B5DEA5AB} - {7B8B0240-E608-4349-89AD-AB5E410BD273} = {E477275E-8530-49B4-ADBB-D61DD5E79BD6} - {2DC7B8F3-2B29-4EC9-BFFF-CC49C817469C} = {E477275E-8530-49B4-ADBB-D61DD5E79BD6} - {82B69D6F-DA25-452B-883D-71B9AE91DF23} = {465BA74E-CEF3-4608-8809-8C7A2E979DAC} - {B2CA1F10-216E-404E-A3C2-70423D5BCF5D} = {42D43EAF-EE89-479A-B304-3B5DF56A3494} - {7DC7327F-6F01-4561-943D-D385FB5637E1} = {42D43EAF-EE89-479A-B304-3B5DF56A3494} - {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9} = {465BA74E-CEF3-4608-8809-8C7A2E979DAC} - {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA} = {42D43EAF-EE89-479A-B304-3B5DF56A3494} - {7AB05778-70B4-4B95-BE67-D07802D0B57B} = {465BA74E-CEF3-4608-8809-8C7A2E979DAC} - {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4} = {465BA74E-CEF3-4608-8809-8C7A2E979DAC} - {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220} = {42D43EAF-EE89-479A-B304-3B5DF56A3494} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {E09CB79A-6E5B-4BAD-B356-64D949892A7E} - EndGlobalSection -EndGlobal + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {67F0E369-C927-4278-A7D0-DDFBE029003F} = {465BA74E-CEF3-4608-8809-8C7A2E979DAC} + {7787CCCA-D8C1-4E53-8EBA-1A07B5DEA5AB} = {465BA74E-CEF3-4608-8809-8C7A2E979DAC} + {E477275E-8530-49B4-ADBB-D61DD5E79BD6} = {7787CCCA-D8C1-4E53-8EBA-1A07B5DEA5AB} + {7B8B0240-E608-4349-89AD-AB5E410BD273} = {E477275E-8530-49B4-ADBB-D61DD5E79BD6} + {2DC7B8F3-2B29-4EC9-BFFF-CC49C817469C} = {E477275E-8530-49B4-ADBB-D61DD5E79BD6} + {82B69D6F-DA25-452B-883D-71B9AE91DF23} = {465BA74E-CEF3-4608-8809-8C7A2E979DAC} + {B2CA1F10-216E-404E-A3C2-70423D5BCF5D} = {42D43EAF-EE89-479A-B304-3B5DF56A3494} + {7DC7327F-6F01-4561-943D-D385FB5637E1} = {42D43EAF-EE89-479A-B304-3B5DF56A3494} + {97B1250A-5BD6-4C12-8D9B-63EEBAFB8DC9} = {465BA74E-CEF3-4608-8809-8C7A2E979DAC} + {3C0F8F56-17BB-4BF7-8D59-51D8A5F635CA} = {42D43EAF-EE89-479A-B304-3B5DF56A3494} + {7AB05778-70B4-4B95-BE67-D07802D0B57B} = {465BA74E-CEF3-4608-8809-8C7A2E979DAC} + {3C6180D9-8BFB-45CE-AD0F-967EBDE7C0F4} = {465BA74E-CEF3-4608-8809-8C7A2E979DAC} + {E0AB6E03-C1C4-490F-A56A-8EE45C0C7220} = {42D43EAF-EE89-479A-B304-3B5DF56A3494} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E09CB79A-6E5B-4BAD-B356-64D949892A7E} + EndGlobalSection +EndGlobal diff --git a/src/caretogether-pwa/src/AppRoutes.tsx b/src/caretogether-pwa/src/AppRoutes.tsx index 023c32ac..9ec95470 100644 --- a/src/caretogether-pwa/src/AppRoutes.tsx +++ b/src/caretogether-pwa/src/AppRoutes.tsx @@ -33,7 +33,7 @@ import { InboxScreen } from './Inbox/InboxScreen'; import { FamilyScreenRoute } from './Families/FamilyScreenRoute'; import { ClientsScreenRoute } from './V1Cases/ClientsScreenRoute'; import { V1Referrals } from './V1Referrals/V1Referrals'; -import { Volunteers } from './Volunteers/Volunteers'; +import { VolunteersScreenRoute } from './Volunteers/VolunteersScreenRoute'; import { Communities } from './Communities/Communities'; import { Reports } from './Reports/Reports'; import { Settings } from './Settings/SettingsRoutes'; @@ -191,7 +191,7 @@ function AuthorizedLocationContextWrapper({ } /> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/src/caretogether-pwa/src/Shell/shellLayoutConstants.ts b/src/caretogether-pwa/src/Shell/shellLayoutConstants.ts index 75fa5d59..39cb34eb 100644 --- a/src/caretogether-pwa/src/Shell/shellLayoutConstants.ts +++ b/src/caretogether-pwa/src/Shell/shellLayoutConstants.ts @@ -1,11 +1,11 @@ -const CHAT_WIDGET_SAFE_HEIGHT = 96; -const MOBILE_BOTTOM_NAV_HEIGHT = 56; +const CHAT_WIDGET_SAFE_HEIGHT = 90; const DESKTOP_APP_BAR_HEIGHT = 48; const TABLET_APP_BAR_HEIGHT = 64; const MOBILE_APP_BAR_HEIGHT = 56; export const EXPANDED_DRAWER_WIDTH = 190; export const COLLAPSED_DRAWER_WIDTH = 48; +export const MOBILE_BOTTOM_NAV_HEIGHT = 56; export const DESKTOP_BOTTOM_SAFE_AREA = CHAT_WIDGET_SAFE_HEIGHT; export const MOBILE_BOTTOM_SAFE_AREA = diff --git a/src/caretogether-pwa/src/Utilities/wideTablePageSx.ts b/src/caretogether-pwa/src/Utilities/wideTablePageSx.ts index 8186edcd..0134ba1b 100644 --- a/src/caretogether-pwa/src/Utilities/wideTablePageSx.ts +++ b/src/caretogether-pwa/src/Utilities/wideTablePageSx.ts @@ -1,18 +1,20 @@ -const FEATUREBASE_CHAT_WIDGET_SAFE_HEIGHT = 96; -const MOBILE_BOTTOM_NAVIGATION_HEIGHT = 56; -const MOBILE_BOTTOM_SAFE_HEIGHT = - FEATUREBASE_CHAT_WIDGET_SAFE_HEIGHT + MOBILE_BOTTOM_NAVIGATION_HEIGHT; +import { + DESKTOP_BOTTOM_SAFE_AREA, + MOBILE_BOTTOM_NAV_HEIGHT, + MOBILE_BOTTOM_SAFE_AREA, + SHELL_APP_BAR_HEIGHT, +} from '../Shell/shellLayoutConstants'; function wideTablePageSx(hasFeaturebaseChat: boolean) { const bottomOffset = hasFeaturebaseChat ? { - xs: `${MOBILE_BOTTOM_SAFE_HEIGHT}px`, - sm: `${MOBILE_BOTTOM_SAFE_HEIGHT}px`, - md: `${FEATUREBASE_CHAT_WIDGET_SAFE_HEIGHT}px`, + xs: `${MOBILE_BOTTOM_SAFE_AREA}px`, + sm: `${MOBILE_BOTTOM_SAFE_AREA}px`, + md: `${DESKTOP_BOTTOM_SAFE_AREA}px`, } : { - xs: `${MOBILE_BOTTOM_NAVIGATION_HEIGHT}px`, - sm: `${MOBILE_BOTTOM_NAVIGATION_HEIGHT}px`, + xs: `${MOBILE_BOTTOM_NAV_HEIGHT}px`, + sm: `${MOBILE_BOTTOM_NAV_HEIGHT}px`, md: '0px', }; @@ -21,9 +23,9 @@ function wideTablePageSx(hasFeaturebaseChat: boolean) { flexDirection: 'column', flexWrap: 'nowrap', height: { - xs: `calc(100dvh - 56px - ${bottomOffset.xs})`, - sm: `calc(100dvh - 64px - ${bottomOffset.sm})`, - md: `calc(100dvh - 48px - ${bottomOffset.md})`, + xs: `calc(100dvh - ${SHELL_APP_BAR_HEIGHT.xs} - ${bottomOffset.xs})`, + sm: `calc(100dvh - ${SHELL_APP_BAR_HEIGHT.sm} - ${bottomOffset.sm})`, + md: `calc(100dvh - ${SHELL_APP_BAR_HEIGHT.md} - ${bottomOffset.md})`, }, // ShellRootLayout always reserves Featurebase space; reclaim it when chat is hidden. minHeight: 0, @@ -36,7 +38,7 @@ function wideTablePageSx(hasFeaturebaseChat: boolean) { return { ...sx, - marginBottom: `-${FEATUREBASE_CHAT_WIDGET_SAFE_HEIGHT}px`, + marginBottom: `-${DESKTOP_BOTTOM_SAFE_AREA}px`, } as const; } diff --git a/src/caretogether-pwa/src/V1Cases/ClientsBrowserToolbarV2.tsx b/src/caretogether-pwa/src/V1Cases/ClientsBrowserToolbarV2.tsx index 909fa95b..76e6ec46 100644 --- a/src/caretogether-pwa/src/V1Cases/ClientsBrowserToolbarV2.tsx +++ b/src/caretogether-pwa/src/V1Cases/ClientsBrowserToolbarV2.tsx @@ -4,7 +4,6 @@ import { Sort as SortIcon, } from '@mui/icons-material'; import { - Box, Button, FormControl, InputAdornment, @@ -15,13 +14,8 @@ import { TextField, } from '@mui/material'; import type { SelectChangeEvent } from '@mui/material/Select'; -import type { - AssignedIndividualVolunteer, - Person, -} from '../GeneratedClient'; -import type { - AssignmentFilterSelectionsByRole, -} from '../FunctionAssignments/assignmentRoleColumns'; +import type { AssignedIndividualVolunteer, Person } from '../GeneratedClient'; +import type { AssignmentFilterSelectionsByRole } from '../FunctionAssignments/assignmentRoleColumns'; import { AssignmentRoleFilters } from '../FunctionAssignments/AssignmentRoleFilters'; import { PartneringFamiliesSortMode } from './PartneringFamilies/sortPartneringFamilies'; import { ArrangementsFilter } from './PartneringFamilies/types'; @@ -88,14 +82,16 @@ export function ClientsBrowserToolbarV2({ assignmentRoles.length > 0 && !!assignmentPersonLookup && !!onAssignmentFilterChange; - const shouldRenderMoreFilters = - customFieldCount > 0 && !!onMoreFiltersClick; + const shouldRenderMoreFilters = customFieldCount > 0 && !!onMoreFiltersClick; return ( )} - ); } diff --git a/src/caretogether-pwa/src/V1Cases/ClientsScreenV2.tsx b/src/caretogether-pwa/src/V1Cases/ClientsScreenV2.tsx index 0f09bd06..e822d981 100644 --- a/src/caretogether-pwa/src/V1Cases/ClientsScreenV2.tsx +++ b/src/caretogether-pwa/src/V1Cases/ClientsScreenV2.tsx @@ -160,17 +160,15 @@ export function ClientsScreenV2() { - + Clients + {roles.map((role) => ( + + ))} + + ); +} + +export function VolunteerApprovalRolesCellV2({ roles }: Props) { + return ( + + + Family: + + + + Individual: + + + + ); +} diff --git a/src/caretogether-pwa/src/Volunteers/VolunteerApprovalTab/VolunteerApprovalTableItem.tsx b/src/caretogether-pwa/src/Volunteers/VolunteerApprovalTab/VolunteerApprovalTableItem.tsx index 4f061a7e..0913c14f 100644 --- a/src/caretogether-pwa/src/Volunteers/VolunteerApprovalTab/VolunteerApprovalTableItem.tsx +++ b/src/caretogether-pwa/src/Volunteers/VolunteerApprovalTab/VolunteerApprovalTableItem.tsx @@ -2,7 +2,6 @@ import Grid from '../../Generic/GridLegacyCompat'; import { Box, Checkbox, - Chip, TableCell, TableRow, Typography, @@ -17,7 +16,7 @@ import { VolunteerRoleApprovalStatusChip } from '../VolunteerRoleApprovalStatusC import { familyLastName } from './familyLastName'; import { LazyLoadMountTrigger } from '../../Utilities/LazyLoadMountTrigger'; import { LazyLoad } from '../../Utilities/reactLazyLoadInterop'; -import { sortByPolicyOrder } from '../../Generic/sortByPolicyOrder'; +import { renderVolunteerCustomFieldValue } from './volunteerCustomFieldPresentation'; type VolunteerApprovalTableItemProps = { volunteerFamily: CombinedFamilyInfo; @@ -43,35 +42,6 @@ function getRowGroupHeight( return 39 + (activeAdultsCount + activeChildrenCount) * 33; } -function renderCustomFieldValue( - value: unknown, - validValues?: string[] -): React.ReactNode { - if (value === null || typeof value === 'undefined') { - return ''; - } - if (value === true) { - return 'Yes'; - } - if (value === false) { - return 'No'; - } - if (Array.isArray(value)) { - const sortedValue = - validValues && validValues.length > 0 - ? sortByPolicyOrder(value.map(String), validValues) - : value; - return ( - - {sortedValue.map((item) => ( - - ))} - - ); - } - return String(value); -} - function VolunteerApprovalPlaceholderRow( props: Pick< VolunteerApprovalTableItemProps, @@ -330,7 +300,7 @@ function VolunteerApprovalTableRows(props: VolunteerApprovalTableItemProps) { familyCustomField?.value ?? volunteerFamilyCustomField?.value; return ( - {renderCustomFieldValue( + {renderVolunteerCustomFieldValue( familyCustomFieldValue, fieldPolicy?.validValues )} diff --git a/src/caretogether-pwa/src/Volunteers/VolunteerApprovalTab/VolunteerFilter.tsx b/src/caretogether-pwa/src/Volunteers/VolunteerApprovalTab/VolunteerFilter.tsx index b4e7f55c..563d0a22 100644 --- a/src/caretogether-pwa/src/Volunteers/VolunteerApprovalTab/VolunteerFilter.tsx +++ b/src/caretogether-pwa/src/Volunteers/VolunteerApprovalTab/VolunteerFilter.tsx @@ -1,13 +1,6 @@ -import { - Checkbox, - FormControl, - InputBase, - ListItemText, - MenuItem, - Select, - SelectChangeEvent, -} from '@mui/material'; -import { FilterList as FilterListIcon } from '@mui/icons-material'; +import { Checkbox, ListItemText, Menu, MenuItem } from '@mui/material'; +import { useState } from 'react'; +import { VolunteerBrowserFilterButtonV2 } from '../VolunteerBrowserFilterButtonV2'; import { filterOption } from './filterOption'; type VolunteerFilterProps = { label: string; @@ -20,55 +13,34 @@ export function VolunteerFilter({ options, setSelected, }: VolunteerFilterProps) { + const [anchorEl, setAnchorEl] = useState(null); const selectedCount = options.filter((option) => option.selected).length; - const displayText = - selectedCount === options.length - ? label - : `${label} (${selectedCount}/${options.length})`; - const handleChange = (event: SelectChangeEvent) => { - const allOptionKeysPlusSelectedOptionValue = event.target.value; - setSelected(allOptionKeysPlusSelectedOptionValue); - }; + function handleOptionClick(option: filterOption) { + setSelected(options.map((option) => option.key).concat(option.value ?? '')); + } return ( - - - + + ); } diff --git a/src/caretogether-pwa/src/Volunteers/VolunteerApprovalTab/volunteerApprovalRolePresentation.ts b/src/caretogether-pwa/src/Volunteers/VolunteerApprovalTab/volunteerApprovalRolePresentation.ts new file mode 100644 index 00000000..b4185062 --- /dev/null +++ b/src/caretogether-pwa/src/Volunteers/VolunteerApprovalTab/volunteerApprovalRolePresentation.ts @@ -0,0 +1,58 @@ +import { + CombinedFamilyInfo, + DateOnlyTimelineOfRoleApprovalStatus, +} from '../../GeneratedClient'; +import { filterOption } from './filterOption'; + +export type VolunteerApprovalRoleChipPresentation = { + roleName: string; + status?: DateOnlyTimelineOfRoleApprovalStatus; +}; + +export type VolunteerApprovalRolesPresentation = { + familyRoles: VolunteerApprovalRoleChipPresentation[]; + individualRoles: VolunteerApprovalRoleChipPresentation[]; +}; + +export function buildVolunteerApprovalRolesPresentation( + family: CombinedFamilyInfo, + roleFilters: Pick[] +): VolunteerApprovalRolesPresentation { + const familyRoles = roleFilters.map((roleFilter) => ({ + roleName: roleFilter.key, + status: + family.volunteerFamilyInfo?.familyRoleApprovals?.[roleFilter.key] + ?.effectiveRoleApprovalStatus, + })); + const individualRolesByName = new Map< + string, + VolunteerApprovalRoleChipPresentation + >(); + + family.family?.adults?.forEach((adult) => { + const adultId = adult.item1?.id; + + if (!adultId) { + return; + } + + Object.entries( + family.volunteerFamilyInfo?.individualVolunteers?.[adultId] + ?.approvalStatusByRole ?? {} + ).forEach(([roleName, roleApprovalStatus]) => { + if (individualRolesByName.has(roleName)) { + return; + } + + individualRolesByName.set(roleName, { + roleName, + status: roleApprovalStatus.effectiveRoleApprovalStatus, + }); + }); + }); + + return { + familyRoles, + individualRoles: Array.from(individualRolesByName.values()), + }; +} diff --git a/src/caretogether-pwa/src/Volunteers/VolunteerApprovalTab/volunteerApprovalRoleStatusFilters.ts b/src/caretogether-pwa/src/Volunteers/VolunteerApprovalTab/volunteerApprovalRoleStatusFilters.ts new file mode 100644 index 00000000..75731145 --- /dev/null +++ b/src/caretogether-pwa/src/Volunteers/VolunteerApprovalTab/volunteerApprovalRoleStatusFilters.ts @@ -0,0 +1,263 @@ +import { CombinedFamilyInfo, VolunteerInfo } from '../../GeneratedClient'; +import { checkStatusEquivalence } from './checkStatusEquivalence'; +import { notAppliedLabel } from './catchAllLabel'; +import { filterOption } from './filterOption'; +import { filterType } from './filterType'; + +function selectedFamilyRoleKeys(roleFilters: filterOption[]) { + return roleFilters + .filter( + (filterOption) => + filterOption.selected && filterOption.type !== filterType.Individual + ) + .map((filterOption) => filterOption.key); +} + +function selectedIndividualRoleKeys(roleFilters: filterOption[]) { + return roleFilters + .filter( + (filterOption) => + filterOption.selected && filterOption.type !== filterType.Family + ) + .map((filterOption) => filterOption.key); +} + +function selectedStatusKeys(statusFilters: filterOption[]) { + return statusFilters + .filter((filterOption) => filterOption.selected) + .map((filterOption) => filterOption.value); +} + +function getFamilyMembers(family: CombinedFamilyInfo) { + return ( + (family.volunteerFamilyInfo?.individualVolunteers && + Object.entries(family.volunteerFamilyInfo?.individualVolunteers)) || + [] + ); +} + +function familyHasNotAppliedForAnyRoles(family: CombinedFamilyInfo) { + const familyRoleApprovals = + family.volunteerFamilyInfo?.familyRoleApprovals ?? {}; + + const familyHasAppliedRole = Object.values(familyRoleApprovals).some( + (roleApproval) => roleApproval.currentStatus != null + ); + + if (familyHasAppliedRole) { + return false; + } + + return getFamilyMembers(family).every(([, volunteer]) => { + const individualRoleApprovals = volunteer.approvalStatusByRole ?? {}; + return Object.values(individualRoleApprovals).every( + (roleApproval) => roleApproval.currentStatus == null + ); + }); +} + +function familyHasNoValidStatuses( + family: CombinedFamilyInfo, + roleFilters: filterOption[] +) { + return roleFilters + .filter((filterOption) => filterOption.key !== notAppliedLabel) + .every( + (filterOption) => + family.volunteerFamilyInfo?.familyRoleApprovals?.[filterOption.key] === + undefined + ); +} + +function familyHasSpecificRoleInValidStatus( + family: CombinedFamilyInfo, + roleName: string, + statusFilters: filterOption[] +) { + return statusFilters + .filter((filterOption) => filterOption.key !== notAppliedLabel) + .some((status) => + checkStatusEquivalence( + status.value, + family.volunteerFamilyInfo?.familyRoleApprovals?.[roleName] + ?.currentStatus + ) + ); +} + +function familyMeetsFilterCriteria( + family: CombinedFamilyInfo, + roleFilters: filterOption[], + statusFilters: filterOption[] +) { + const familyRoleKeys = selectedFamilyRoleKeys(roleFilters); + const individualRoleKeys = selectedIndividualRoleKeys(roleFilters); + const statusKeys = selectedStatusKeys(statusFilters); + + if (!familyRoleKeys.length) { + if (!statusKeys.length) { + return individualRoleKeys.length === 0; + } + return statusKeys.some((status) => + status === notAppliedLabel + ? familyHasNoValidStatuses(family, roleFilters) + : roleFilters.some((roleFilter) => + checkStatusEquivalence( + status, + family.volunteerFamilyInfo?.familyRoleApprovals?.[roleFilter.key] + ?.currentStatus + ) + ) + ); + } + return familyRoleKeys.some((roleName) => { + if (roleName === notAppliedLabel) { + return familyHasNotAppliedForAnyRoles(family); + } + + const familyHasRole = + family.volunteerFamilyInfo?.familyRoleApprovals?.[roleName] !== + undefined; + if (!familyHasRole) { + return familyHasRole; + } + if (statusKeys.length === 0) { + return familyHasSpecificRoleInValidStatus( + family, + roleName, + statusFilters + ); + } + return statusKeys.some((status) => + checkStatusEquivalence( + status, + family.volunteerFamilyInfo?.familyRoleApprovals?.[roleName] + ?.currentStatus + ) + ); + }); +} + +function familyMemberHasNoValidStatuses( + volunteer: VolunteerInfo, + roleFilters: filterOption[] +) { + return roleFilters + .filter((filterOption) => filterOption.key !== notAppliedLabel) + .every((filterOption) => + checkStatusEquivalence( + volunteer.approvalStatusByRole?.[filterOption.key]?.currentStatus, + null + ) + ); +} + +function familyMemberHasSpecificRoleInValidStatus( + volunteer: VolunteerInfo, + roleName: string, + statusFilters: filterOption[] +) { + return statusFilters + .filter((filterOption) => filterOption.key !== notAppliedLabel) + .some((status) => + checkStatusEquivalence( + status.value, + volunteer.approvalStatusByRole?.[roleName]?.currentStatus + ) + ); +} + +function familyMemberHasARoleInSelectedStatus( + volunteer: VolunteerInfo, + status: string, + roleFilters: filterOption[] +) { + return status === notAppliedLabel + ? familyMemberHasNoValidStatuses(volunteer, roleFilters) + : roleFilters.some((roleFilter) => + checkStatusEquivalence( + volunteer.approvalStatusByRole?.[roleFilter.key]?.currentStatus, + status + ) + ); +} + +function familyMembersMeetFilterCriteria( + family: CombinedFamilyInfo, + roleFilters: filterOption[], + statusFilters: filterOption[] +) { + const familyMembers = getFamilyMembers(family); + const familyRoleKeys = selectedFamilyRoleKeys(roleFilters); + const individualRoleKeys = selectedIndividualRoleKeys(roleFilters); + const statusKeys = selectedStatusKeys(statusFilters); + + if (!individualRoleKeys.length) { + if (!statusKeys.length) { + return !familyRoleKeys.length; + } + return statusKeys.some( + (status) => + familyMembers.filter(([, volunteer]) => + familyMemberHasARoleInSelectedStatus( + volunteer, + status ? status : notAppliedLabel, + roleFilters + ) + ).length > 0 + ); + } + return individualRoleKeys.some((roleName) => { + if (roleName === notAppliedLabel) { + return familyHasNotAppliedForAnyRoles(family); + } + + return familyMembers.some(([, volunteer]) => { + if (!statusKeys.length) { + return familyMemberHasSpecificRoleInValidStatus( + volunteer, + roleName, + statusFilters + ); + } + return statusKeys.some((status) => + checkStatusEquivalence( + status, + volunteer.approvalStatusByRole?.[roleName]?.currentStatus + ) + ); + }); + }); +} + +export function familyOrFamilyMembersMeetRoleStatusFilterCriteria( + family: CombinedFamilyInfo, + roleFilters: filterOption[], + statusFilters: filterOption[] +) { + const familyMeetsRoleCriteria = familyMeetsFilterCriteria( + family, + roleFilters, + statusFilters + ); + const familyMembersMeetRoleCriteria = familyMembersMeetFilterCriteria( + family, + roleFilters, + statusFilters + ); + const familyRolesSelected = selectedFamilyRoleKeys(roleFilters).length > 0; + const individualRolesSelected = + selectedIndividualRoleKeys(roleFilters).length > 0; + const statusesSelected = selectedStatusKeys(statusFilters).length > 0; + let result = true; + if (familyRolesSelected && individualRolesSelected) { + result = familyMeetsRoleCriteria || familyMembersMeetRoleCriteria; + } else if (familyRolesSelected) { + result = familyMeetsRoleCriteria; + } else if (individualRolesSelected) { + result = familyMembersMeetRoleCriteria; + } else if (statusesSelected) { + result = familyMeetsRoleCriteria || familyMembersMeetRoleCriteria; + } + return result; +} diff --git a/src/caretogether-pwa/src/Volunteers/VolunteerApprovalTab/volunteerCustomFieldPresentation.tsx b/src/caretogether-pwa/src/Volunteers/VolunteerApprovalTab/volunteerCustomFieldPresentation.tsx new file mode 100644 index 00000000..4bb99a3c --- /dev/null +++ b/src/caretogether-pwa/src/Volunteers/VolunteerApprovalTab/volunteerCustomFieldPresentation.tsx @@ -0,0 +1,32 @@ +import { Box, Chip } from '@mui/material'; +import { ReactNode } from 'react'; +import { sortByPolicyOrder } from '../../Generic/sortByPolicyOrder'; + +export function renderVolunteerCustomFieldValue( + value: unknown, + validValues?: string[] +): ReactNode { + if (value === null || typeof value === 'undefined') { + return ''; + } + if (value === true) { + return 'Yes'; + } + if (value === false) { + return 'No'; + } + if (Array.isArray(value)) { + const sortedValue = + validValues && validValues.length > 0 + ? sortByPolicyOrder(value.map(String), validValues) + : value; + return ( + + {sortedValue.map((item) => ( + + ))} + + ); + } + return String(value); +} diff --git a/src/caretogether-pwa/src/Volunteers/VolunteerApprovalTab/volunteerMissingRequirementsPresentation.ts b/src/caretogether-pwa/src/Volunteers/VolunteerApprovalTab/volunteerMissingRequirementsPresentation.ts new file mode 100644 index 00000000..5be76b2f --- /dev/null +++ b/src/caretogether-pwa/src/Volunteers/VolunteerApprovalTab/volunteerMissingRequirementsPresentation.ts @@ -0,0 +1,161 @@ +import { CombinedFamilyInfo } from '../../GeneratedClient'; +import { personNameString } from '../../Families/PersonName'; +import { notAppliedLabel } from './catchAllLabel'; +import { filterOption } from './filterOption'; +import { filterType } from './filterType'; + +export const missingRequirementFilterValue = '__missing__'; +export const completeRequirementFilterValue = '__complete__'; + +export type RequirementFilterValue = + | typeof completeRequirementFilterValue + | typeof missingRequirementFilterValue + | string; + +export type VolunteerMissingRequirementGroup = { + label: string; + requirements: string[]; +}; + +function selectedRoleFilters(roleFilters: filterOption[]) { + return roleFilters.filter( + (roleFilter) => roleFilter.selected && roleFilter.key !== notAppliedLabel + ); +} + +function selectedFamilyRoleNames(roleFilters: filterOption[]) { + return selectedRoleFilters(roleFilters) + .filter((roleFilter) => roleFilter.type !== filterType.Individual) + .map((roleFilter) => roleFilter.key); +} + +function selectedIndividualRoleNames(roleFilters: filterOption[]) { + return selectedRoleFilters(roleFilters) + .filter((roleFilter) => roleFilter.type !== filterType.Family) + .map((roleFilter) => roleFilter.key); +} + +function requirementNames( + missingRequirements: + | NonNullable< + CombinedFamilyInfo['volunteerFamilyInfo'] + >['missingRequirements'] + | undefined +) { + return Array.from( + new Set( + (missingRequirements ?? []) + .map((requirement) => requirement.item1) + .filter((requirement): requirement is string => Boolean(requirement)) + ) + ); +} + +function shouldIncludeFamilyRequirements( + family: CombinedFamilyInfo, + familyRoleNames: string[], + individualRoleNames: string[] +) { + if (!familyRoleNames.length && !individualRoleNames.length) { + return true; + } + + return familyRoleNames.some( + (roleName) => + family.volunteerFamilyInfo?.familyRoleApprovals?.[roleName] !== undefined + ); +} + +function shouldIncludeIndividualRequirements( + approvalStatusByRole: + | NonNullable< + NonNullable< + CombinedFamilyInfo['volunteerFamilyInfo'] + >['individualVolunteers'] + >[string]['approvalStatusByRole'] + | undefined, + familyRoleNames: string[], + individualRoleNames: string[] +) { + if (!familyRoleNames.length && !individualRoleNames.length) { + return true; + } + + return individualRoleNames.some( + (roleName) => approvalStatusByRole?.[roleName] !== undefined + ); +} + +export function buildVolunteerMissingRequirementGroups( + family: CombinedFamilyInfo, + roleFilters: filterOption[] +): VolunteerMissingRequirementGroup[] { + const familyRoleNames = selectedFamilyRoleNames(roleFilters); + const individualRoleNames = selectedIndividualRoleNames(roleFilters); + const familyRequirements = shouldIncludeFamilyRequirements( + family, + familyRoleNames, + individualRoleNames + ) + ? requirementNames(family.volunteerFamilyInfo?.missingRequirements) + : []; + const familyGroups: VolunteerMissingRequirementGroup[] = + familyRequirements.length > 0 + ? [{ label: 'Family', requirements: familyRequirements }] + : []; + const individualGroups = Object.entries( + family.volunteerFamilyInfo?.individualVolunteers ?? {} + ).flatMap(([personId, volunteer]) => { + const requirements = shouldIncludeIndividualRequirements( + volunteer.approvalStatusByRole, + familyRoleNames, + individualRoleNames + ) + ? requirementNames(volunteer.missingRequirements) + : []; + + if (!requirements.length) { + return []; + } + + const person = family.family?.adults?.find( + (adult) => adult.item1?.id === personId + )?.item1; + + return [ + { + label: person ? personNameString(person) : 'Volunteer', + requirements, + }, + ]; + }); + + return familyGroups.concat(individualGroups); +} + +export function familyHasMissingRequirements( + family: CombinedFamilyInfo, + roleFilters: filterOption[], + selectedRequirement: RequirementFilterValue | undefined +) { + if (!selectedRequirement) { + return true; + } + + const groups = buildVolunteerMissingRequirementGroups(family, roleFilters); + const hasMissingRequirements = groups.some( + (group) => group.requirements.length > 0 + ); + + if (selectedRequirement === missingRequirementFilterValue) { + return hasMissingRequirements; + } + + if (selectedRequirement === completeRequirementFilterValue) { + return !hasMissingRequirements; + } + + return groups.some((group) => + group.requirements.includes(selectedRequirement) + ); +} diff --git a/src/caretogether-pwa/src/Volunteers/VolunteerBrowserFilterButtonV2.tsx b/src/caretogether-pwa/src/Volunteers/VolunteerBrowserFilterButtonV2.tsx new file mode 100644 index 00000000..0b5b8f87 --- /dev/null +++ b/src/caretogether-pwa/src/Volunteers/VolunteerBrowserFilterButtonV2.tsx @@ -0,0 +1,44 @@ +import { + ArrowDropDown as ArrowDropDownIcon, + FilterList as FilterListIcon, +} from '@mui/icons-material'; +import { Button } from '@mui/material'; + +type VolunteerBrowserFilterButtonV2Props = { + activeCount?: number; + label: string; + selectedLabel?: string; + totalCount?: number; + size?: 'small' | 'medium'; + onClick: (event: React.MouseEvent) => void; +}; + +export function VolunteerBrowserFilterButtonV2({ + activeCount, + label, + selectedLabel, + totalCount, + size, + onClick, +}: VolunteerBrowserFilterButtonV2Props) { + const displayText = + selectedLabel ?? `${label} (${activeCount ?? 0}/${totalCount ?? 0})`; + + return ( + + ); +} diff --git a/src/caretogether-pwa/src/Volunteers/VolunteersBrowserV2.tsx b/src/caretogether-pwa/src/Volunteers/VolunteersBrowserV2.tsx new file mode 100644 index 00000000..c40e7ffd --- /dev/null +++ b/src/caretogether-pwa/src/Volunteers/VolunteersBrowserV2.tsx @@ -0,0 +1,281 @@ +import { Box, Stack, Typography } from '@mui/material'; +import type { GridFilterModel, GridRowSelectionModel } from '@mui/x-data-grid'; +import { useMemo, useState } from 'react'; +import { useFeatureFlagEnabled } from 'posthog-js/react'; +import { useRecoilValue } from 'recoil'; +import { EmailAddress, Permission } from '../GeneratedClient'; +import { useAppNavigate } from '../Hooks/useAppNavigate'; +import { useGlobalSnackBar } from '../Hooks/useGlobalSnackBar'; +import { useLoadable } from '../Hooks/useLoadable'; +import { useSidePanel } from '../Hooks/useSidePanel'; +import { v2Typography } from '../Families/v2Typography'; +import { selectedLocationContextState } from '../Model/Data'; +import { organizationConfigurationQuery } from '../Model/ConfigurationModel'; +import { useAllVolunteerFamiliesPermissions } from '../Model/SessionModel'; +import { BulkSmsSideSheet } from './BulkSmsSideSheet'; +import { CreateVolunteerFamilyDrawer } from './CreateVolunteerFamilyDrawer'; +import { VolunteerAssignmentFiltersSidePanel } from './VolunteerApprovalTab/VolunteerAssignmentFiltersSidePanel'; +import { VolunteerCustomFieldFiltersSidePanel } from './VolunteerApprovalTab/VolunteerCustomFieldFiltersSidePanel'; +import { VolunteersDataGridV2 } from './VolunteersDataGridV2'; +import { VolunteersToolbarV2 } from './VolunteersToolbarV2'; +import { useVolunteersBrowserViewModel } from './useVolunteersBrowserViewModel'; +import { filterOption } from './VolunteerApprovalTab/filterOption'; +import { + gridFilterModelFromVolunteerFilters, + volunteerFiltersFromGridFilterModel, +} from './volunteersGridFilterAdapter'; + +function selectedFilterValues(filters: filterOption[]) { + return filters + .filter((filter) => filter.selected && filter.value !== undefined) + .map((filter) => filter.value!); +} + +export function VolunteersBrowserV2() { + const appNavigate = useAppNavigate(); + const permissions = useAllVolunteerFamiliesPermissions(); + const updateTestFamilyFlagEnabled = useFeatureFlagEnabled( + 'updateTestFamilyFlag' + ); + const { setAndShowGlobalSnackBar } = useGlobalSnackBar(); + const { locationId } = useRecoilValue(selectedLocationContextState); + const organizationConfiguration = useLoadable(organizationConfigurationQuery); + const [createVolunteerFamilyDrawerOpen, setCreateVolunteerFamilyDrawerOpen] = + useState(false); + const [smsMode, setSmsMode] = useState(false); + const [selectedFamilyIds, setSelectedFamilyIds] = useState([]); + + const { + activeAssignmentFilterCount, + activeCustomFieldFilterCount, + arrangementTypes, + assignmentFilters, + customFieldCount, + customFieldFilters, + customFields, + getCustomFieldFilterOptionsForField, + loading, + requirementFilter, + requirementFilterOptions, + roleFilters, + rows, + searchValue, + setAssignmentFilter, + setCustomFieldFilter, + setRequirementFilter, + setRoleFilterValues, + setSearchValue, + setStatusFilterValues, + statusFilters, + visibleVolunteerFamilies, + } = useVolunteersBrowserViewModel(); + const { + SidePanel: AssignmentFiltersSidePanel, + openSidePanel: openAssignmentFiltersSidePanel, + closeSidePanel: closeAssignmentFiltersSidePanel, + } = useSidePanel(); + const { + SidePanel: CustomFieldFiltersSidePanel, + openSidePanel: openCustomFieldFiltersSidePanel, + closeSidePanel: closeCustomFieldFiltersSidePanel, + } = useSidePanel(); + const canCreateVolunteerFamily = + permissions(Permission.EditFamilyInfo) && + permissions(Permission.ActivateVolunteerFamily); + const smsSourcePhoneNumbers = organizationConfiguration?.locations?.find( + (location) => location.id === locationId + )?.smsSourcePhoneNumbers; + const canUseBulkEmail = permissions(Permission.SendBulkSms); + const canUseBulkSms = + permissions(Permission.SendBulkSms) && + Boolean(smsSourcePhoneNumbers && smsSourcePhoneNumbers.length > 0); + const visibleRowIds = useMemo(() => rows.map((row) => row.id), [rows]); + const rowSelectionModel = useMemo( + () => ({ + type: 'include', + ids: new Set( + selectedFamilyIds.filter((id) => visibleRowIds.includes(id)) + ), + }), + [selectedFamilyIds, visibleRowIds] + ); + const selectedVolunteerCount = rowSelectionModel.ids.size; + const selectedVolunteerFamilies = useMemo(() => { + const selectedFamilyIdSet = new Set(selectedFamilyIds); + + return visibleVolunteerFamilies.filter((family) => + selectedFamilyIdSet.has(family.family!.id!) + ); + }, [selectedFamilyIds, visibleVolunteerFamilies]); + const filterModel = useMemo( + () => + gridFilterModelFromVolunteerFilters({ + logicOperator: 'and', + requirementFilter, + roleFilters: selectedFilterValues(roleFilters), + statusFilters: selectedFilterValues(statusFilters), + }), + [requirementFilter, roleFilters, statusFilters] + ); + + function selectedFamilyContactEmails() { + return selectedVolunteerFamilies + .map((family) => { + const primaryContactPerson = family.family?.adults?.find( + (adult) => + adult.item1?.id === family.family?.primaryFamilyContactPersonId + ); + const preferredEmailAddress = + primaryContactPerson?.item1?.emailAddresses?.find( + (email) => + email.id === primaryContactPerson.item1?.preferredEmailAddressId + ); + return preferredEmailAddress; + }) + .filter((email) => typeof email !== 'undefined') as EmailAddress[]; + } + + function copyEmailAddresses() { + const emailAddresses = selectedFamilyContactEmails(); + navigator.clipboard.writeText( + emailAddresses.map((email) => email.address).join('; ') + ); + setAndShowGlobalSnackBar( + `Found and copied ${emailAddresses.length} email addresses for ${selectedVolunteerFamilies.length} selected families to clipboard` + ); + } + + function clearSelection() { + setSelectedFamilyIds([]); + } + + function handleRowSelectionModelChange(model: GridRowSelectionModel) { + const visibleRowIdSet = new Set(visibleRowIds); + + if (model.type === 'exclude') { + setSelectedFamilyIds(visibleRowIds.filter((id) => !model.ids.has(id))); + return; + } + + setSelectedFamilyIds( + Array.from(model.ids) + .map(String) + .filter((id) => visibleRowIdSet.has(id)) + ); + } + + function handleAssignmentFilterChange( + arrangementType: string, + selectedValues: Parameters[1] + ) { + clearSelection(); + setAssignmentFilter(arrangementType, selectedValues); + } + + function handleCustomFieldFilterChange( + fieldName: string, + selectedValues: Parameters[1] + ) { + clearSelection(); + setCustomFieldFilter(fieldName, selectedValues); + } + + function handleSearchChange(value: string) { + clearSelection(); + setSearchValue(value); + } + + function handleFilterModelChange(model: GridFilterModel) { + const filters = volunteerFiltersFromGridFilterModel(model); + + clearSelection(); + setRoleFilterValues(filters.roleFilters); + setStatusFilterValues(filters.statusFilters); + setRequirementFilter( + filters.requirementFilter as Parameters[0] + ); + } + + return ( + + + + Volunteers + + + Review volunteer families. + + + setCreateVolunteerFamilyDrawerOpen(true)} + onCustomFieldFiltersClick={openCustomFieldFiltersSidePanel} + onToggleBulkSms={() => setSmsMode(!smsMode)} + /> + + + + + + + + appNavigate.family(row.id)} + onRowSelectionModelChange={handleRowSelectionModelChange} + requirementFilterOptions={requirementFilterOptions} + roleFilters={roleFilters} + rowSelectionModel={rowSelectionModel} + rows={rows} + statusFilters={statusFilters} + updateTestFamilyFlagEnabled={updateTestFamilyFlagEnabled} + /> + + {createVolunteerFamilyDrawerOpen && ( + { + setCreateVolunteerFamilyDrawerOpen(false); + if (!volunteerFamilyId) return; + appNavigate.family(volunteerFamilyId); + }} + /> + )} + {smsMode && ( + setSmsMode(false)} + /> + )} + + ); +} diff --git a/src/caretogether-pwa/src/Volunteers/VolunteersDataGridV2.tsx b/src/caretogether-pwa/src/Volunteers/VolunteersDataGridV2.tsx new file mode 100644 index 00000000..b8be14aa --- /dev/null +++ b/src/caretogether-pwa/src/Volunteers/VolunteersDataGridV2.tsx @@ -0,0 +1,325 @@ +import { + Box, + Chip, + Stack, + Tooltip, + Typography, + useTheme, +} from '@mui/material'; +import { Check as CheckIcon } from '@mui/icons-material'; +import { + DataGrid, + GridColDef, + GridFilterModel, + GridRowSelectionModel, +} from '@mui/x-data-grid'; +import { useMemo } from 'react'; +import { CustomField } from '../GeneratedClient'; +import { familyLastName } from '../Families/FamilyUtils'; +import { TestFamilyBadge } from '../Families/TestFamilyBadge'; +import { v2DataGridStyles } from '../Families/v2DataGridStyles'; +import { v2Typography } from '../Families/v2Typography'; +import { filterOption } from './VolunteerApprovalTab/filterOption'; +import { VolunteerApprovalRolesCellV2 } from './VolunteerApprovalRolesCellV2'; +import { renderVolunteerCustomFieldValue } from './VolunteerApprovalTab/volunteerCustomFieldPresentation'; +import { + completeRequirementFilterValue, + missingRequirementFilterValue, +} from './VolunteerApprovalTab/volunteerMissingRequirementsPresentation'; +import { VolunteerBrowserRowV2 } from './useVolunteersBrowserViewModel'; + +const MAX_VISIBLE_MISSING_REQUIREMENTS = 4; +const BULK_ACTION_SELECTION_TOOLTIP = 'Select families to enable bulk actions.'; + +function MissingRequirementsCell({ row }: { row: VolunteerBrowserRowV2 }) { + const missingRequirements = row.missingRequirementGroups.flatMap( + (group) => group.requirements + ); + + if (!missingRequirements.length) { + return ( + } + label="Complete" + size="small" + variant="outlined" + /> + ); + } + + let visibleCount = 0; + const visibleGroups = row.missingRequirementGroups.flatMap((group) => { + if (visibleCount >= MAX_VISIBLE_MISSING_REQUIREMENTS) { + return []; + } + + const requirements = group.requirements.slice( + 0, + MAX_VISIBLE_MISSING_REQUIREMENTS - visibleCount + ); + visibleCount += requirements.length; + + return [{ ...group, requirements }]; + }); + const hiddenCount = missingRequirements.length - visibleCount; + const tooltipTitle = row.missingRequirementGroups + .map((group) => `${group.label}:\n${group.requirements.join('\n')}`) + .join('\n'); + + return ( + {tooltipTitle}} + > + + {visibleGroups.map((group) => ( + + + {group.label} + + {group.requirements.map((requirement) => ( + + • {requirement} + + ))} + + ))} + {hiddenCount > 0 && ( + + +{hiddenCount} more + + )} + + + ); +} + +type VolunteersDataGridV2Props = { + customFields: CustomField[]; + filterModel: GridFilterModel; + loading?: boolean; + onFilterModelChange: (model: GridFilterModel) => void; + onRowClick: (row: VolunteerBrowserRowV2) => void; + onRowSelectionModelChange: (model: GridRowSelectionModel) => void; + requirementFilterOptions: string[]; + roleFilters: filterOption[]; + rowSelectionModel: GridRowSelectionModel; + rows: VolunteerBrowserRowV2[]; + statusFilters: filterOption[]; + updateTestFamilyFlagEnabled?: boolean; +}; + +function displayValues(values: string[]) { + return values.length > 0 ? values.join(', ') : '-'; +} + +function filterValueOptions(filters: filterOption[]) { + return filters + .filter((filter) => filter.value !== undefined) + .map((filter) => ({ + label: filter.key, + value: filter.value!, + })); +} + +function requirementValueOptions(requirementNames: string[]) { + return [ + { label: 'Missing', value: missingRequirementFilterValue }, + { label: 'Complete', value: completeRequirementFilterValue }, + ].concat( + requirementNames.map((requirementName) => ({ + label: requirementName, + value: requirementName, + })) + ); +} + +function compareByFamilyLastName( + firstRow: VolunteerBrowserRowV2 | null, + secondRow: VolunteerBrowserRowV2 | null +) { + const firstFamilyName = firstRow ? familyLastName(firstRow.sourceFamily) : ''; + const secondFamilyName = secondRow + ? familyLastName(secondRow.sourceFamily) + : ''; + const familyNameComparison = firstFamilyName.localeCompare(secondFamilyName); + + return familyNameComparison !== 0 + ? familyNameComparison + : (firstRow?.id ?? '').localeCompare(secondRow?.id ?? ''); +} + +function buildColumns( + customFields: CustomField[], + roleFilters: filterOption[], + statusFilters: filterOption[], + requirementFilterOptions: string[], + updateTestFamilyFlagEnabled?: boolean +): GridColDef[] { + const fixedColumns: GridColDef[] = [ + { + field: 'family', + headerName: 'Family Name', + flex: 1, + minWidth: 220, + filterable: false, + sortComparator: (_value1, _value2, cellParams1, cellParams2) => + compareByFamilyLastName( + cellParams1.api.getRow( + cellParams1.id + ) as VolunteerBrowserRowV2 | null, + cellParams2.api.getRow(cellParams2.id) as VolunteerBrowserRowV2 | null + ), + renderCell: ({ row }) => ( + + + {row.family} + + {updateTestFamilyFlagEnabled && ( + + )} + + ), + }, + { + field: 'primaryContact', + headerName: 'Primary Contact', + flex: 0.7, + minWidth: 160, + filterable: false, + renderCell: ({ row }) => ( + + {row.primaryContact} + + ), + }, + { + field: 'roles', + headerName: 'Roles', + flex: 1.5, + minWidth: 360, + type: 'singleSelect', + valueGetter: (_value, row) => row.roleFilterValues.join(', '), + valueOptions: filterValueOptions(roleFilters), + renderCell: ({ row }) => , + }, + { + field: 'status', + headerName: 'Status', + flex: 0.6, + minWidth: 160, + type: 'singleSelect', + valueGetter: (_value, row) => row.statusFilterValues.join(', '), + valueOptions: filterValueOptions(statusFilters), + renderCell: ({ row }) => ( + + {displayValues(row.statusLabels)} + + ), + }, + { + field: 'missingRequirements', + headerName: 'Missing Requirements', + flex: 1, + minWidth: 280, + type: 'singleSelect', + valueGetter: (_value, row) => row.requirementFilterValues.join(', '), + valueOptions: requirementValueOptions(requirementFilterOptions), + renderCell: ({ row }) => , + }, + ]; + + const customFieldColumns: GridColDef[] = + customFields.map((customField) => ({ + field: `customField:${customField.name}`, + headerName: customField.name, + flex: 0.7, + minWidth: 160, + filterable: false, + renderCell: ({ row }) => + renderVolunteerCustomFieldValue( + row.customFieldValues[customField.name], + customField.validValues + ), + })); + + return fixedColumns.concat(customFieldColumns); +} + +export function VolunteersDataGridV2({ + customFields, + filterModel, + loading = false, + onFilterModelChange, + onRowClick, + onRowSelectionModelChange, + requirementFilterOptions, + roleFilters, + rowSelectionModel, + rows, + statusFilters, + updateTestFamilyFlagEnabled, +}: VolunteersDataGridV2Props) { + const theme = useTheme(); + const columns = useMemo( + () => + buildColumns( + customFields, + roleFilters, + statusFilters, + requirementFilterOptions, + updateTestFamilyFlagEnabled + ), + [ + customFields, + requirementFilterOptions, + roleFilters, + statusFilters, + updateTestFamilyFlagEnabled, + ] + ); + + return ( + + 88} + getRowHeight={() => 'auto'} + columnHeaderHeight={42} + checkboxSelection + disableRowSelectionOnClick + disableRowSelectionExcludeModel + filterMode="server" + filterModel={filterModel} + onRowClick={({ row }) => onRowClick(row)} + onFilterModelChange={onFilterModelChange} + onRowSelectionModelChange={onRowSelectionModelChange} + pageSizeOptions={[25, 50, 100]} + rowSelectionModel={rowSelectionModel} + slotProps={{ + baseCheckbox: { + slotProps: { + htmlInput: { + title: BULK_ACTION_SELECTION_TOOLTIP, + }, + }, + }, + }} + initialState={{ + pagination: { + paginationModel: { pageSize: 100 }, + }, + sorting: { + sortModel: [{ field: 'family', sort: 'asc' }], + }, + }} + /> + + ); +} diff --git a/src/caretogether-pwa/src/Volunteers/VolunteersScreenRoute.tsx b/src/caretogether-pwa/src/Volunteers/VolunteersScreenRoute.tsx new file mode 100644 index 00000000..a16003ac --- /dev/null +++ b/src/caretogether-pwa/src/Volunteers/VolunteersScreenRoute.tsx @@ -0,0 +1,36 @@ +import { useEffect, useState } from 'react'; +import { useFeatureFlagEnabled, usePostHog } from 'posthog-js/react'; +import { FAMILY_SCREEN_V2_EARLY_ACCESS_FEATURE_FLAG } from '../featureFlags'; +import { ProgressBackdrop } from '../Shell/ProgressBackdrop'; +import { Volunteers } from './Volunteers'; +import { VolunteersScreenV2 } from './VolunteersScreenV2'; + +export function VolunteersScreenRoute() { + const posthog = usePostHog(); + const earlyAccessEnabled = useFeatureFlagEnabled( + FAMILY_SCREEN_V2_EARLY_ACCESS_FEATURE_FLAG + ); + const [featureFlagsLoaded, setFeatureFlagsLoaded] = useState( + () => posthog.featureFlags.hasLoadedFlags + ); + + useEffect(() => { + setFeatureFlagsLoaded(posthog.featureFlags.hasLoadedFlags); + + return posthog.onFeatureFlags(() => { + setFeatureFlagsLoaded(true); + }); + }, [posthog]); + + if (!featureFlagsLoaded) { + return ( + +

Loading...

+
+ ); + } + + const showVolunteersScreenV2 = earlyAccessEnabled === true; + + return showVolunteersScreenV2 ? : ; +} diff --git a/src/caretogether-pwa/src/Volunteers/VolunteersScreenV2.tsx b/src/caretogether-pwa/src/Volunteers/VolunteersScreenV2.tsx new file mode 100644 index 00000000..016b9dff --- /dev/null +++ b/src/caretogether-pwa/src/Volunteers/VolunteersScreenV2.tsx @@ -0,0 +1,59 @@ +import { Box, Stack } from '@mui/material'; +import { + Navigate, + Route, + Routes, + useLocation, + useParams, +} from 'react-router-dom'; +import { Permission } from '../GeneratedClient'; +import { useGlobalPermissions } from '../Model/SessionModel'; +import { useScreenTitle } from '../Shell/ShellScreenTitle'; +import { wideTablePageSx } from '../Utilities/wideTablePageSx'; +import { VolunteersBrowserV2 } from './VolunteersBrowserV2'; + +function volunteersBasePathFromPathname(pathname: string) { + const volunteersPathMatch = pathname.match(/^(.*\/volunteers)(?:\/.*)?$/); + + return volunteersPathMatch?.[1] ?? pathname; +} + +function VolunteerFamilyRedirect() { + const { familyId } = useParams<{ familyId: string }>(); + + return ; +} + +export function VolunteersScreenV2() { + useScreenTitle('Volunteers'); + const location = useLocation(); + const globalPermissions = useGlobalPermissions(); + const volunteersBasePath = volunteersBasePathFromPathname(location.pathname); + const hasFeaturebaseChat = globalPermissions(Permission.AccessSupportScreen); + + return ( + + + + } /> + } + /> + } + /> + } + /> + + + + ); +} diff --git a/src/caretogether-pwa/src/Volunteers/VolunteersToolbarV2.tsx b/src/caretogether-pwa/src/Volunteers/VolunteersToolbarV2.tsx new file mode 100644 index 00000000..5967b1c6 --- /dev/null +++ b/src/caretogether-pwa/src/Volunteers/VolunteersToolbarV2.tsx @@ -0,0 +1,161 @@ +import { + Add as AddIcon, + Email as EmailIcon, + FilterList as FilterListIcon, + Search as SearchIcon, + Sms as SmsIcon, +} from '@mui/icons-material'; +import { + Button, + IconButton, + InputAdornment, + Stack, + TextField, + Tooltip, + Typography, +} from '@mui/material'; + +type VolunteersToolbarV2Props = { + activeAssignmentFilterCount: number; + activeCustomFieldFilterCount: number; + arrangementTypeCount: number; + customFieldCount: number; + canCreateVolunteerFamily: boolean; + canUseBulkEmail: boolean; + canUseBulkSms: boolean; + searchValue: string; + selectedVolunteerCount: number; + smsMode: boolean; + onSearchChange: (value: string) => void; + onAssignmentFiltersClick: () => void; + onCopyEmailAddresses: () => void; + onCreateVolunteerFamily: () => void; + onCustomFieldFiltersClick: () => void; + onToggleBulkSms: () => void; +}; + +export function VolunteersToolbarV2({ + activeAssignmentFilterCount, + activeCustomFieldFilterCount, + arrangementTypeCount, + canCreateVolunteerFamily, + canUseBulkEmail, + canUseBulkSms, + customFieldCount, + searchValue, + selectedVolunteerCount, + smsMode, + onSearchChange, + onAssignmentFiltersClick, + onCopyEmailAddresses, + onCreateVolunteerFamily, + onCustomFieldFiltersClick, + onToggleBulkSms, +}: VolunteersToolbarV2Props) { + const bulkActionsDisabled = selectedVolunteerCount === 0; + const bulkActionsTooltip = bulkActionsDisabled + ? 'Select one or more families to enable bulk actions.' + : ''; + + return ( + + {canCreateVolunteerFamily && ( + + )} + {arrangementTypeCount > 0 && ( + + )} + {customFieldCount > 0 && ( + + )} + onSearchChange(event.target.value)} + sx={{ width: { xs: '100%', sm: 260 } }} + slotProps={{ + input: { + startAdornment: ( + + + + ), + }, + }} + /> + + + {selectedVolunteerCount} selected + + {canUseBulkEmail && ( + + + + + + + + )} + {canUseBulkSms && ( + + + + + + + + )} + + + ); +} diff --git a/src/caretogether-pwa/src/Volunteers/useVolunteersBrowserViewModel.ts b/src/caretogether-pwa/src/Volunteers/useVolunteersBrowserViewModel.ts new file mode 100644 index 00000000..6300eae4 --- /dev/null +++ b/src/caretogether-pwa/src/Volunteers/useVolunteersBrowserViewModel.ts @@ -0,0 +1,528 @@ +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useRecoilState, useRecoilValue } from 'recoil'; +import { + CombinedFamilyInfo, + CustomField, + RoleApprovalStatus, +} from '../GeneratedClient'; +import { familyNameString } from '../Families/FamilyName'; +import { filterFamiliesByText } from '../Families/FamilyUtils'; +import { personNameString } from '../Families/PersonName'; +import { matchesCustomFieldFilters } from '../Generic/CustomFieldsFilter/matchesCustomFieldFilters'; +import { + CustomFieldFilterOption, + CustomFieldFilterSelectionsByField, + CustomFieldFilterValue, +} from '../Generic/CustomFieldsFilter/types'; +import { useCustomFieldFilters } from '../Generic/CustomFieldsFilter/useCustomFieldFilters'; +import { useLoadable } from '../Hooks/useLoadable'; +import { + allApprovalAndOnboardingRequirementsData, + policyData, +} from '../Model/ConfigurationModel'; +import { volunteerFamiliesData } from '../Model/VolunteersModel'; +import { + AssignmentFilterSelectionsByArrangementType, + AssignmentFilterValue, + matchesAssignmentFilters, +} from './VolunteerApprovalTab/assignmentFilters'; +import { filterOption } from './VolunteerApprovalTab/filterOption'; +import { roleFiltersState } from './VolunteerApprovalTab/roleFiltersState'; +import { statusFiltersState } from './VolunteerApprovalTab/statusFiltersState'; +import { + buildVolunteerApprovalRolesPresentation, + VolunteerApprovalRolesPresentation, +} from './VolunteerApprovalTab/volunteerApprovalRolePresentation'; +import { familyOrFamilyMembersMeetRoleStatusFilterCriteria } from './VolunteerApprovalTab/volunteerApprovalRoleStatusFilters'; +import { + buildVolunteerMissingRequirementGroups, + completeRequirementFilterValue, + familyHasMissingRequirements, + missingRequirementFilterValue, + RequirementFilterValue, + VolunteerMissingRequirementGroup, +} from './VolunteerApprovalTab/volunteerMissingRequirementsPresentation'; + +export type VolunteerBrowserRowV2 = { + customFieldValues: Record; + id: string; + family: string; + requirementFilterValues: string[]; + missingRequirementGroups: VolunteerMissingRequirementGroup[]; + primaryContact: string; + roleFilterValues: string[]; + roles: VolunteerApprovalRolesPresentation; + sourceFamily: CombinedFamilyInfo; + statusFilterValues: string[]; + statusLabels: string[]; +}; + +type VolunteersBrowserViewModel = { + activeAssignmentFilterCount: number; + activeCustomFieldFilterCount: number; + arrangementTypes: string[]; + assignmentFilters: AssignmentFilterSelectionsByArrangementType; + customFieldCount: number; + customFieldFilters: CustomFieldFilterSelectionsByField; + customFields: CustomField[]; + empty: boolean; + getCustomFieldFilterOptionsForField: ( + field: CustomField + ) => CustomFieldFilterOption[]; + loading: boolean; + requirementFilter: RequirementFilterValue | undefined; + requirementFilterOptions: string[]; + roleFilters: filterOption[]; + rows: VolunteerBrowserRowV2[]; + searchValue: string; + setAssignmentFilter: ( + arrangementType: string, + selectedValues: AssignmentFilterValue[] + ) => void; + setCustomFieldFilter: ( + fieldName: string, + selectedValues: CustomFieldFilterValue[] + ) => void; + setRequirementFilter: (value: RequirementFilterValue | undefined) => void; + setRoleFilterValues: (values: string[]) => void; + setSearchValue: (value: string) => void; + setStatusFilterValues: (values: string[]) => void; + statusFilters: filterOption[]; + totalVolunteerFamilies: number; + visibleVolunteerFamilies: CombinedFamilyInfo[]; +}; + +type CustomFieldValuesByFamily = Map>; + +function primaryContactName(family: CombinedFamilyInfo) { + const primaryContact = family.family?.adults?.find( + (adult) => adult.item1?.id === family.family?.primaryFamilyContactPersonId + )?.item1; + + return primaryContact ? personNameString(primaryContact) : ''; +} + +function customFieldValuesForFamily( + family: CombinedFamilyInfo, + customFields: CustomField[], + customFieldValuesByFamily: CustomFieldValuesByFamily +) { + const valuesByName = customFieldValuesByFamily.get(family) ?? {}; + + return Object.fromEntries( + customFields.map((field) => [field.name, valuesByName[field.name]]) + ); +} + +function familyRoleNames(family: CombinedFamilyInfo) { + return Object.keys(family.volunteerFamilyInfo?.familyRoleApprovals ?? {}); +} + +function individualRoleNames(family: CombinedFamilyInfo) { + return Object.values( + family.volunteerFamilyInfo?.individualVolunteers ?? {} + ).flatMap((volunteer) => + Object.keys(volunteer.approvalStatusByRole ?? {}) + ); +} + +function roleFilterValuesForFamily( + family: CombinedFamilyInfo, + roleFilters: filterOption[] +) { + const roleNames = new Set(familyRoleNames(family).concat(individualRoleNames(family))); + + return roleFilters + .filter( + (roleFilter) => + roleFilter.value !== undefined && roleNames.has(roleFilter.key) + ) + .map((roleFilter) => roleFilter.value!); +} + +function statusValue(status: RoleApprovalStatus | null | undefined) { + return status === null || status === undefined ? '0' : status.toString(); +} + +function statusFilterValuesForFamily(family: CombinedFamilyInfo) { + return Array.from( + new Set( + Object.values(family.volunteerFamilyInfo?.familyRoleApprovals ?? {}) + .map((approval) => statusValue(approval.currentStatus)) + .concat( + Object.values( + family.volunteerFamilyInfo?.individualVolunteers ?? {} + ).flatMap((volunteer) => + Object.values(volunteer.approvalStatusByRole ?? {}).map( + (approval) => statusValue(approval.currentStatus) + ) + ) + ) + ) + ); +} + +function statusLabelsForFamily( + family: CombinedFamilyInfo, + statusFilters: filterOption[] +) { + const statusLabelsByValue = new Map( + statusFilters + .filter((statusFilter) => statusFilter.value !== undefined) + .map((statusFilter) => [statusFilter.value!, statusFilter.key]) + ); + + return statusFilterValuesForFamily(family) + .map((value) => statusLabelsByValue.get(value)) + .filter((label): label is string => Boolean(label)); +} + +function requirementFilterValuesForGroups( + missingRequirementGroups: VolunteerMissingRequirementGroup[] +) { + const missingRequirements = missingRequirementGroups.flatMap( + (group) => group.requirements + ); + + if (!missingRequirements.length) { + return [completeRequirementFilterValue]; + } + + return Array.from(new Set([missingRequirementFilterValue, ...missingRequirements])); +} + +function toVolunteerBrowserRow( + family: CombinedFamilyInfo, + customFields: CustomField[], + roleFilters: filterOption[], + statusFilters: filterOption[], + customFieldValuesByFamily: CustomFieldValuesByFamily +) { + const missingRequirementGroups = buildVolunteerMissingRequirementGroups( + family, + roleFilters + ); + + return { + customFieldValues: customFieldValuesForFamily( + family, + customFields, + customFieldValuesByFamily + ), + id: family.family!.id!, + family: familyNameString(family), + requirementFilterValues: + requirementFilterValuesForGroups(missingRequirementGroups), + missingRequirementGroups, + primaryContact: primaryContactName(family), + roleFilterValues: roleFilterValuesForFamily(family, roleFilters), + roles: buildVolunteerApprovalRolesPresentation(family, roleFilters), + sourceFamily: family, + statusFilterValues: statusFilterValuesForFamily(family), + statusLabels: statusLabelsForFamily(family, statusFilters), + }; +} + +function volunteerCustomFields( + policyCustomFields: CustomField[] | undefined, + volunteerCustomFields: CustomField[] | undefined +) { + return (policyCustomFields ?? []).concat(volunteerCustomFields ?? []); +} + +function buildCustomFieldValuesByFamily(volunteerFamilies: CombinedFamilyInfo[]) { + return new Map( + volunteerFamilies.map((family) => [ + family, + customFieldValuesByName(family), + ]) + ); +} + +function customFieldValuesByName(family: CombinedFamilyInfo) { + const valuesByName: Record = {}; + + family.volunteerFamilyInfo?.completedCustomFields?.forEach((customField) => { + valuesByName[customField.customFieldName] = customField.value; + }); + family.family?.completedCustomFields?.forEach((customField) => { + if (customField.value === undefined || customField.value === null) { + return; + } + + valuesByName[customField.customFieldName] = customField.value; + }); + + return valuesByName; +} + +function customFieldValueFromLookup( + customFieldValuesByFamily: CustomFieldValuesByFamily, + family: CombinedFamilyInfo, + fieldName: string +) { + return customFieldValuesByFamily.get(family)?.[fieldName]; +} + +function customFieldIsBlank(value: unknown) { + return value === undefined || value === null; +} + +function sourceVolunteerFamilies( + volunteerFamilies: CombinedFamilyInfo[] | null | undefined +) { + return volunteerFamilies ?? []; +} + +function applySearchStage( + volunteerFamilies: CombinedFamilyInfo[], + searchValue: string +) { + return filterFamiliesByText(volunteerFamilies, searchValue); +} + +function applyFilterStage( + volunteerFamilies: CombinedFamilyInfo[], + roleFilters: filterOption[], + statusFilters: filterOption[], + assignmentFilters: AssignmentFilterSelectionsByArrangementType, + customFields: CustomField[], + customFieldFilters: CustomFieldFilterSelectionsByField, + customFieldValuesByFamily: CustomFieldValuesByFamily, + requirementFilter: RequirementFilterValue | undefined +) { + return volunteerFamilies.filter( + (family) => + familyOrFamilyMembersMeetRoleStatusFilterCriteria( + family, + roleFilters, + statusFilters + ) && + familyHasMissingRequirements( + family, + roleFilters, + requirementFilter + ) && + matchesAssignmentFilters(family, assignmentFilters) && + matchesCustomFieldFilters({ + item: family, + customFields, + selectedValuesByField: customFieldFilters, + isBlank: (item, fieldName) => + customFieldIsBlank( + customFieldValueFromLookup( + customFieldValuesByFamily, + item, + fieldName + ) + ), + getValue: (item, fieldName) => + customFieldValueFromLookup( + customFieldValuesByFamily, + item, + fieldName + ), + }) + ); +} + +function mapRows( + volunteerFamilies: CombinedFamilyInfo[], + customFields: CustomField[], + roleFilters: filterOption[], + statusFilters: filterOption[], + customFieldValuesByFamily: CustomFieldValuesByFamily +) { + return volunteerFamilies.map((family) => + toVolunteerBrowserRow( + family, + customFields, + roleFilters, + statusFilters, + customFieldValuesByFamily + ) + ); +} + +function withSelectedFilterValues( + filters: filterOption[], + selectedValues: string[] +) { + const selectedValueSet = new Set(selectedValues); + + return filters.map((filter) => ({ + ...filter, + selected: + filter.value !== undefined && selectedValueSet.has(filter.value), + })); +} + +export function useVolunteersBrowserViewModel(): VolunteersBrowserViewModel { + const volunteerFamilies = useLoadable(volunteerFamiliesData); + const requirementNames = useLoadable(allApprovalAndOnboardingRequirementsData); + const policy = useRecoilValue(policyData); + const [roleFilters, setRoleFilters] = useRecoilState(roleFiltersState); + const [statusFilters, setStatusFilters] = useRecoilState(statusFiltersState); + const [assignmentFilters, setAssignmentFilters] = + useState({}); + const [searchValue, setSearchValue] = useState(''); + const [requirementFilter, setRequirementFilter] = + useState(); + const loading = volunteerFamilies == null; + const sourceFamilies = sourceVolunteerFamilies(volunteerFamilies); + const arrangementTypes = useMemo( + () => + Array.from( + new Set( + (policy.referralPolicy?.arrangementPolicies ?? []) + .map((arrangementPolicy) => arrangementPolicy.arrangementType) + .filter( + (arrangementType): arrangementType is string => !!arrangementType + ) + ) + ), + [policy.referralPolicy?.arrangementPolicies] + ); + const activeAssignmentFilterCount = Object.values(assignmentFilters).filter( + (selectedValues) => selectedValues.length > 0 + ).length; + const customFields = useMemo( + () => + volunteerCustomFields( + policy.customFamilyFields, + policy.volunteerPolicy?.customFields + ), + [policy.customFamilyFields, policy.volunteerPolicy?.customFields] + ); + const customFieldValuesByFamily = useMemo( + () => buildCustomFieldValuesByFamily(sourceFamilies), + [sourceFamilies] + ); + const getCustomFieldValue = useCallback( + (family: CombinedFamilyInfo, fieldName: string) => + customFieldValueFromLookup(customFieldValuesByFamily, family, fieldName), + [customFieldValuesByFamily] + ); + const isCustomFieldBlank = useCallback( + (family: CombinedFamilyInfo, fieldName: string) => + customFieldIsBlank(getCustomFieldValue(family, fieldName)), + [getCustomFieldValue] + ); + const { + selectedValuesByField: customFieldFilters, + setSelectedValuesForField: setCustomFieldFilter, + getOptionsForField: getCustomFieldFilterOptionsForField, + } = useCustomFieldFilters({ + customFields, + items: sourceFamilies, + isBlank: isCustomFieldBlank, + getValue: getCustomFieldValue, + }); + const customFieldCount = customFields.length; + const activeCustomFieldFilterCount = Object.values( + customFieldFilters + ).filter((selectedValues) => selectedValues.length > 0).length; + + useEffect(() => { + setAssignmentFilters((currentFilters) => { + const validFilters = Object.fromEntries( + Object.entries(currentFilters).filter(([arrangementType]) => + arrangementTypes.includes(arrangementType) + ) + ); + + return Object.keys(validFilters).length === + Object.keys(currentFilters).length + ? currentFilters + : validFilters; + }); + }, [arrangementTypes]); + + function setAssignmentFilter( + arrangementType: string, + selectedValues: AssignmentFilterValue[] + ) { + setAssignmentFilters((previous) => ({ + ...previous, + [arrangementType]: selectedValues, + })); + } + + function setRoleFilterValues(values: string[]) { + setRoleFilters(withSelectedFilterValues(roleFilters, values)); + } + + function setStatusFilterValues(values: string[]) { + setStatusFilters(withSelectedFilterValues(statusFilters, values)); + } + + const visibleVolunteerFamilies = useMemo(() => { + const searchedFamilies = applySearchStage(sourceFamilies, searchValue); + const filteredFamilies = applyFilterStage( + searchedFamilies, + roleFilters, + statusFilters, + assignmentFilters, + customFields, + customFieldFilters, + customFieldValuesByFamily, + requirementFilter + ); + + return filteredFamilies; + }, [ + assignmentFilters, + customFieldFilters, + customFieldValuesByFamily, + customFields, + requirementFilter, + roleFilters, + searchValue, + sourceFamilies, + statusFilters, + ]); + const rows = useMemo( + () => + mapRows( + visibleVolunteerFamilies, + customFields, + roleFilters, + statusFilters, + customFieldValuesByFamily + ), + [ + customFieldValuesByFamily, + customFields, + roleFilters, + statusFilters, + visibleVolunteerFamilies, + ] + ); + + return { + activeAssignmentFilterCount, + activeCustomFieldFilterCount, + arrangementTypes, + assignmentFilters, + customFieldCount, + customFieldFilters, + customFields, + empty: !loading && rows.length === 0, + getCustomFieldFilterOptionsForField, + loading, + requirementFilter, + requirementFilterOptions: requirementNames ?? [], + roleFilters, + rows, + searchValue, + setAssignmentFilter, + setCustomFieldFilter, + setRequirementFilter, + setRoleFilterValues, + setSearchValue, + setStatusFilterValues, + statusFilters, + totalVolunteerFamilies: sourceFamilies.length, + visibleVolunteerFamilies, + }; +} diff --git a/src/caretogether-pwa/src/Volunteers/volunteersGridFilterAdapter.ts b/src/caretogether-pwa/src/Volunteers/volunteersGridFilterAdapter.ts new file mode 100644 index 00000000..08c2630f --- /dev/null +++ b/src/caretogether-pwa/src/Volunteers/volunteersGridFilterAdapter.ts @@ -0,0 +1,109 @@ +import { + GridLogicOperator, + type GridFilterModel, +} from '@mui/x-data-grid'; + +export const VOLUNTEER_ROLES_FILTER_FIELD = 'roles'; +export const VOLUNTEER_STATUS_FILTER_FIELD = 'status'; +export const VOLUNTEER_REQUIREMENTS_FILTER_FIELD = 'missingRequirements'; + +export type VolunteersGridFilterLogicOperator = 'and' | 'or'; + +export type VolunteersGridFilters = { + logicOperator: VolunteersGridFilterLogicOperator; + requirementFilter: string | undefined; + roleFilters: string[]; + statusFilters: string[]; +}; + +const MULTI_VALUE_FILTER_OPERATOR = 'isAnyOf'; +const SINGLE_VALUE_FILTER_OPERATOR = 'is'; + +function stringValuesFromFilterValue(value: unknown): string[] { + if (Array.isArray(value)) { + return value.map(String).filter(Boolean); + } + + return typeof value === 'string' && value ? [value] : []; +} + +function stringValueFromFilterValue(value: unknown): string | undefined { + return typeof value === 'string' && value ? value : undefined; +} + +function filterValueForField( + filterModel: GridFilterModel, + field: string +): unknown { + return filterModel.items.find((item) => item.field === field)?.value; +} + +function logicOperatorFromGridFilterModel( + filterModel: GridFilterModel +): VolunteersGridFilterLogicOperator { + return filterModel.logicOperator === GridLogicOperator.Or ? 'or' : 'and'; +} + +function gridLogicOperatorFromVolunteerFilters( + logicOperator: VolunteersGridFilterLogicOperator +) { + return logicOperator === 'or' ? GridLogicOperator.Or : GridLogicOperator.And; +} + +export function volunteerFiltersFromGridFilterModel( + filterModel: GridFilterModel +): VolunteersGridFilters { + return { + logicOperator: logicOperatorFromGridFilterModel(filterModel), + requirementFilter: stringValueFromFilterValue( + filterValueForField(filterModel, VOLUNTEER_REQUIREMENTS_FILTER_FIELD) + ), + roleFilters: stringValuesFromFilterValue( + filterValueForField(filterModel, VOLUNTEER_ROLES_FILTER_FIELD) + ), + statusFilters: stringValuesFromFilterValue( + filterValueForField(filterModel, VOLUNTEER_STATUS_FILTER_FIELD) + ), + }; +} + +export function gridFilterModelFromVolunteerFilters({ + logicOperator, + requirementFilter, + roleFilters, + statusFilters, +}: VolunteersGridFilters): GridFilterModel { + const items: GridFilterModel['items'] = []; + + if (roleFilters.length > 0) { + items.push({ + field: VOLUNTEER_ROLES_FILTER_FIELD, + id: VOLUNTEER_ROLES_FILTER_FIELD, + operator: MULTI_VALUE_FILTER_OPERATOR, + value: roleFilters, + }); + } + + if (statusFilters.length > 0) { + items.push({ + field: VOLUNTEER_STATUS_FILTER_FIELD, + id: VOLUNTEER_STATUS_FILTER_FIELD, + operator: MULTI_VALUE_FILTER_OPERATOR, + value: statusFilters, + }); + } + + if (requirementFilter) { + items.push({ + field: VOLUNTEER_REQUIREMENTS_FILTER_FIELD, + id: VOLUNTEER_REQUIREMENTS_FILTER_FIELD, + operator: SINGLE_VALUE_FILTER_OPERATOR, + value: requirementFilter, + }); + } + + return { + items, + logicOperator: gridLogicOperatorFromVolunteerFilters(logicOperator), + }; +}